/*	$OpenBSD: ukcutil.c,v 1.17 2008/03/24 21:35:03 maja Exp $ */

/*
 * Copyright (c) 1999-2001 Mats O Jansson.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef LINT
static	char rcsid[] = "$OpenBSD: ukcutil.c,v 1.17 2008/03/24 21:35:03 maja Exp $";
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <sys/device.h>
#include <limits.h>
#include <nlist.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cmd.h"
#include "exec.h"
#include "ukc.h"
#include "misc.h"

extern	int ukc_mod_kernel;

struct	cfdata *
get_cfdata(int idx)
{
	return((struct cfdata *)(adjust((caddr_t)nl[P_CFDATA].n_value) +
	    idx * sizeof(struct cfdata)));
}

short *
get_locnamp(int idx)
{
	return((short *)(adjust((caddr_t)nl[S_LOCNAMP].n_value) +
	    idx * sizeof(short)));
}

caddr_t *
get_locnames(int idx)
{
	return((caddr_t *)(adjust((caddr_t)nl[P_LOCNAMES].n_value) +
	    idx * sizeof(caddr_t)));
}

int *
get_extraloc(int idx)
{
	return((int *)(adjust((caddr_t)nl[IA_EXTRALOC].n_value) +
	    idx * sizeof(int)));
}

char *
get_pdevnames(int idx)
{
	caddr_t *p;

	p = (caddr_t *)adjust((caddr_t)nl[P_PDEVNAMES].n_value +
	    idx * sizeof(caddr_t));
	return(char *)adjust((caddr_t)*p);

}

struct pdevinit *
get_pdevinit(int idx)
{
	return((struct pdevinit *)(adjust((caddr_t)nl[S_PDEVINIT].n_value) +
	    idx * sizeof(struct pdevinit)));
}

int
more(void)
{
	int quit = 0;
	cmd_t cmd;

	if (cnt != -1) {
		if (cnt > 0 && cnt == lines) {
			printf("--- more ---");
			fflush(stdout);
			ask_cmd(&cmd);
			cnt = 0;
			if (cmd.cmd[0] == 'q' || cmd.cmd[0] == 'Q')
				quit = 1;
		}
		cnt++;
	}
	return (quit);
}

void
pnum(int val)
{
	if (val > -2 && val < 16) {
		printf("%d", val);
		return;
	}

	switch (base) {
	case 8:
		printf("0%o", val);
		break;
	case 10:
		printf("%d", val);
		break;
	case 16:
	default:
		printf("0x%x", val);
		break;
	}
}

void
pdevnam(short devno)
{
	struct cfdata *cd;
	struct cfdriver *cdrv;

	cd = get_cfdata(devno);

	cdrv = (struct cfdriver *)adjust((caddr_t)cd->cf_driver);

#if defined(OLDSCSIBUS)
	if (strlen(adjust((caddr_t)cdrv->cd_name)) == 0)
		printf("oldscsibus");
#endif
	printf("%s", adjust((caddr_t)cdrv->cd_name));

	switch (cd->cf_fstate) {
	case FSTATE_NOTFOUND:
	case FSTATE_DNOTFOUND:
		printf("%d", cd->cf_unit);
		break;
	case FSTATE_FOUND:
		printf("*FOUND*");
		break;
	case FSTATE_STAR:
	case FSTATE_DSTAR:
		printf("*");
		break;
	default:
		printf("*UNKNOWN*");
		break;
	}
}

void
pdev(short devno)
{
	struct pdevinit *pi;
	struct cfdata *cd;
	short	*s, *ln;
	int	*i;
	caddr_t	*p;
	char	c;

	if (nopdev == 0) {
		if (devno > maxdev && devno <= totdev) {
			printf("%3d free slot (for add)\n", devno);
			return;
		}
		if (devno > totdev && devno <= totdev + maxpseudo) {
			pi = get_pdevinit(devno - totdev -1);
			printf("%3d %s count %d", devno,
			    get_pdevnames(devno - totdev - 1),
			    abs(pi->pdev_count));
			if (pi->pdev_count < 0)
				printf(" disable");
			printf(" (pseudo device)\n");
			return;
		}
	}

	if (devno > maxdev) {
		printf("Unknown devno (max is %d)\n", maxdev);
		return;
	}

	cd = get_cfdata(devno);

	printf("%3d ", devno);
	pdevnam(devno);
	printf(" at");

	c = ' ';
	s = (short *)adjust((caddr_t)cd->cf_parents);
	if (*s == -1)
		printf(" root");
	while (*s != -1) {
		printf("%c", c);
		pdevnam(*s);
		c = '|';
		s++;
	}
	switch (cd->cf_fstate) {
	case FSTATE_NOTFOUND:
	case FSTATE_FOUND:
	case FSTATE_STAR:
		break;
	case FSTATE_DNOTFOUND:
	case FSTATE_DSTAR:
		printf(" disable");
		break;
	default:
		printf(" ???");
		break;
	}

	i = (int *)adjust((caddr_t)cd->cf_loc);
	ln = get_locnamp(cd->cf_locnames);
	while (*ln != -1) {
		p = get_locnames(*ln);
		printf(" %s ", adjust((caddr_t)*p));
		ln++;
		pnum(*i);
		i++;
	}
	printf(" flags 0x%x\n", cd->cf_flags);
}

int
number(const char *c, int *val)
{
	int neg = 0, base = 10;
	u_int num = 0;

	if (*c == '-') {
		neg = 1;
		c++;
	}
	if (*c == '0') {
		base = 8;
		c++;
		if (*c == 'x' || *c == 'X') {
			base = 16;
			c++;
		}
	}
	while (*c != '\n' && *c != '\t' && *c != ' ' && *c != '\0') {
		u_char cc = *c;

		if (cc >= '0' && cc <= '9')
			cc = cc - '0';
		else if (cc >= 'a' && cc <= 'f')
			cc = cc - 'a' + 10;
		else if (cc >= 'A' && cc <= 'F')
			cc = cc - 'A' + 10;
		else
			return (-1);

		if (cc > base)
			return (-1);
		num = num * base + cc;
		c++;
	}

	if (neg && num > INT_MAX)	/* overflow */
		return (1);
	*val = neg ? - num : num;
	return (0);
}

int
device(char *cmd, int *len, short *unit, short *state)
{
	short u = 0, s = FSTATE_FOUND;
	int l = 0;
	char *c;

	c = cmd;
	while (*c >= 'a' && *c <= 'z') {
		l++;
		c++;
	}

	if (*c == '*') {
		s = FSTATE_STAR;
		c++;
	} else {
		while (*c >= '0' && *c <= '9') {
			s = FSTATE_NOTFOUND;
			u = u*10 + *c - '0';
			c++;
		}
	}
	while (*c == ' ' || *c == '\t' || *c == '\n')
		c++;

	if (*c == '\0') {
		*len = l;
		*unit = u;
		*state = s;
		return(0);
	}
	return(-1);
}

int
attr(char *cmd, int *val)
{
	short attr = -1, i = 0, l = 0;
	caddr_t *p;
	char *c;

	c = cmd;
	while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') {
		c++;
		l++;
	}

	p = get_locnames(0);

	while (i <= maxlocnames) {
		if (strlen((char *)adjust((caddr_t)*p)) == l) {
			if (strncasecmp(cmd, adjust((caddr_t)*p), l) == 0)
				attr = i;
		}
		p++;
		i++;
	}
	if (attr == -1)
		return (-1);

	*val = attr;
	return(0);
}

void
modify(char *item, int *val)
{
	cmd_t cmd;
	int a;

	ukc_mod_kernel = 1;
	while (1) {
		printf("%s [", item);
		pnum(*val);
		printf("] ? ");
		fflush(stdout);

		ask_cmd(&cmd);

		if (strlen(cmd.cmd) != 0) {
			if (strlen(cmd.args) == 0) {
				if (number(cmd.cmd, &a) == 0) {
					*val = a;
					break;
				} else
					printf("Unknown argument\n");
			} else
				printf("Too many arguments\n");
		} else
			break;
	}
}

void
change(int devno)
{
	int	i, share = 0, *j = NULL, *k = NULL, *l;
	struct cfdata *cd, *c;
	struct pdevinit *pi;
	short	*ln, *lk;
	caddr_t	*p;

	ukc_mod_kernel = 1;
	if (devno <=  maxdev) {
		pdev(devno);
		if (!ask_yn("change"))
			return;

		cd = get_cfdata(devno);

		/*
		 * Search for some other driver sharing this
		 * locator table. if one does, we may need to
		 * replace the locators with a new copy.
		 */
		c = get_cfdata(0);
		for (i = 0; c->cf_driver; i++) {
			if (i != devno && c->cf_loc == cd->cf_loc)
				share = 1;
			c++;
		}

		ln = get_locnamp(cd->cf_locnames);
		l = (int *)adjust((caddr_t)cd->cf_loc);

		if (share) {
			if (oldkernel) {
				printf("Can't do that on this kernel\n");
				return;
			}

			lk = ln;
			i = 0;
			while (*lk != -1) {
				lk++;
				i++;
			}
			lk = ln;

			j = (int *)adjust((caddr_t)nl[I_NEXTRALOC].n_value);
			k = (int *)adjust((caddr_t)nl[I_UEXTRALOC].n_value);
			if ((i + *k) > *j) {
				printf("Not enough space to change device.\n");
				return;
			}

			j = l = get_extraloc(*k);
			bcopy(adjust((caddr_t)cd->cf_loc),
			    l, sizeof(int) * i);
		}

		while (*ln != -1) {
			p = get_locnames(*ln);
			modify((char *)adjust(*p), l);
			ln++;
			l++;
		}
		modify("flags", &cd->cf_flags);

		if (share) {
			if (bcmp(adjust((caddr_t)cd->cf_loc), j,
			    sizeof(int) * i)) {
				cd->cf_loc = (int *)readjust((caddr_t)j);
				*k = *k + i;
			}
		}

		printf("%3d ", devno);
		pdevnam(devno);
		printf(" changed\n");
		pdev(devno);
		return;
	}

	if (nopdev == 0) {
		if (devno > maxdev && devno <= totdev) {
			printf("%3d can't change free slot\n", devno);
			return;
		}

		if (devno > totdev && devno <= totdev + maxpseudo) {
			pdev(devno);
			if (ask_yn("change")) {
				pi = get_pdevinit(devno-totdev-1);
				modify("count", &pi->pdev_count);
				printf("%3d %s changed\n", devno,
				    get_pdevnames(devno - totdev - 1));
				pdev(devno);
			}
			return;
		}
	}

	printf("Unknown devno (max is %d)\n", totdev+maxpseudo);
}

void
change_history(int devno, char *str)
{
	int	i, share = 0, *j = NULL, *k = NULL, *l;
	struct cfdata *cd, *c;
	struct pdevinit *pi;
	short	*ln, *lk;

	ukc_mod_kernel = 1;

	if (devno <= maxdev) {

		pdev(devno);
		cd = get_cfdata(devno);

		/*
		 * Search for some other driver sharing this
		 * locator table. if one does, we may need to
		 * replace the locators with a new copy.
		 */
		c = get_cfdata(0);
		for (i = 0; c->cf_driver; i++) {
			if (i != devno && c->cf_loc == cd->cf_loc)
				share = 1;
			c++;
		}

		ln = get_locnamp(cd->cf_locnames);
		l = (int *)adjust((caddr_t)cd->cf_loc);

		if (share) {
			if (oldkernel) {
				printf("Can't do that on this kernel\n");
				return;
			}

			lk = ln;
			i = 0;
			while (*lk != -1) {
				lk++;
				i++;
			}
			lk = ln;

			j = (int *)adjust((caddr_t)nl[I_NEXTRALOC].n_value);
			k = (int *)adjust((caddr_t)nl[I_UEXTRALOC].n_value);
			if ((i + *k) > *j) {
				printf("Not enough space to change device.\n");
				return;
			}

			j = l = get_extraloc(*k);
			bcopy(adjust((caddr_t)cd->cf_loc),
			    l, sizeof(int) * i);
		}

		while (*ln != -1) {
			*l = atoi(str);
			if (*str == '-')
				str++;
			while (*str >= '0' && *str <= '9')
				str++;
			if (*str == ' ')
				str++;
			ln++;
			l++;
		}

		if (*str) {
			cd->cf_flags = atoi(str);
			if (*str == '-')
				str++;
			while (*str >= '0' && *str <= '9')
				str++;
			if (*str == ' ')
				str++;
		}

		if (share) {
			if (bcmp(adjust((caddr_t)cd->cf_loc),
			    j, sizeof(int) * i)) {
				cd->cf_loc = (int *)readjust((caddr_t)j);
				*k = *k + i;
			}
		}

		printf("%3d ", devno);
		pdevnam(devno);
		printf(" changed\n");
		pdev(devno);
		return;
	}

	if (nopdev == 0) {
		if (devno > maxdev && devno <= totdev) {
			printf("%3d can't change free slot\n", devno);
			return;
		}
		if (devno > totdev && devno <= totdev + maxpseudo) {
			pdev(devno);
			pi = get_pdevinit(devno-totdev-1);

			if (*str) {
				pi->pdev_count = atoi(str);
				if (*str == '-')
					str++;
				while (*str >= '0' && *str <= '9')
					str++;
				if (*str == ' ')
					str++;
			}

			printf("%3d %s changed\n", devno,
			    get_pdevnames(devno - totdev - 1));
			pdev(devno);
			return;
		}
	}

	printf("Unknown devno (max is %d)\n", totdev + maxpseudo);
}

void
disable(int devno)
{
	struct cfdata *cd;
	struct pdevinit *pi;
	int done = 0;

	if (devno <= maxdev) {

		cd = get_cfdata(devno);

		switch (cd->cf_fstate) {
		case FSTATE_NOTFOUND:
			cd->cf_fstate = FSTATE_DNOTFOUND;
			break;
		case FSTATE_STAR:
			cd->cf_fstate = FSTATE_DSTAR;
			break;
		case FSTATE_DNOTFOUND:
		case FSTATE_DSTAR:
			done = 1;
			break;
		default:
			printf("Error unknown state\n");
			break;
		}

		printf("%3d ", devno);
		pdevnam(devno);
		if (done) {
			printf(" already");
		} else {
			ukc_mod_kernel = 1;
		}
		printf(" disabled\n");

		return;
	}

	if (nopdev == 0) {
		if (devno > maxdev && devno <= totdev) {
			printf("%3d can't disable free slot\n", devno);
			return;
		}
		if (devno > totdev && devno <= totdev + maxpseudo) {
			pi = get_pdevinit(devno-totdev-1);

			printf("%3d %s", devno,
				get_pdevnames(devno - totdev - 1));
			if (pi->pdev_count < 1) {
				printf(" already");
			} else {
				ukc_mod_kernel = 1;
				pi->pdev_count*=-1;
			}
			printf(" disabled\n");
			return;
		}
	}

	printf("Unknown devno (max is %d)\n", totdev+maxpseudo);

}

void
enable(int devno)
{
	struct cfdata *cd;
	struct pdevinit *pi;
	int done = 0;

	if (devno <= maxdev) {
		cd = get_cfdata(devno);

		switch (cd->cf_fstate) {
		case FSTATE_DNOTFOUND:
			cd->cf_fstate = FSTATE_NOTFOUND;
			break;
		case FSTATE_DSTAR:
			cd->cf_fstate = FSTATE_STAR;
			break;
		case FSTATE_NOTFOUND:
		case FSTATE_STAR:
			done = 1;
			break;
		default:
			printf("Error unknown state\n");
			break;
		}

		printf("%3d ", devno);
		pdevnam(devno);
		if (done) {
			printf(" already");
		} else {
			ukc_mod_kernel = 1;
		}
		printf(" enabled\n");

		return;
	}

	if (nopdev == 0) {
		if (devno > maxdev && devno <= totdev) {
			printf("%3d can't enable free slot\n", devno);
			return;
		}
		if (devno > totdev && devno <= totdev + maxpseudo) {
			pi = get_pdevinit(devno-totdev-1);

			printf("%3d %s", devno,
				get_pdevnames(devno - totdev - 1));
			if (pi->pdev_count > 0) {
				printf(" already");
			} else {
				ukc_mod_kernel = 1;
				pi->pdev_count*=-1;
			}
			printf(" enabled\n");
			return;
		}
	}

	printf("Unknown devno (max is %d)\n", totdev+maxpseudo);
}

void
show(void)
{
	caddr_t *p;
	int	i = 0;

	cnt = 0;

	p = get_locnames(0);

	while (i <= maxlocnames) {
		if (more())
			break;
		printf("%s\n", (char *)adjust(*p));
		p++;
		i++;
	}

	cnt = -1;
}

void
common_attr_val(short attr, int *val, char routine)
{
	int	i = 0;
	struct cfdata *cd;
	int   *l;
	short *ln;
	int quit = 0;

	cnt = 0;

	cd = get_cfdata(0);

	while (cd->cf_attach != 0) {
		l = (int *)adjust((caddr_t)cd->cf_loc);
		ln = get_locnamp(cd->cf_locnames);
		while (*ln != -1) {
			if (*ln == attr) {
				if (val == NULL) {
					quit = more();
					pdev(i);
				} else {
					if (*val == *l) {
						quit = more();
						switch (routine) {
						case UC_ENABLE:
							enable(i);
							break;
						case UC_DISABLE:
							disable(i);
							break;
						case UC_SHOW:
							pdev(i);
							break;
						default:
							printf("Unknown routine /%c/\n",
							    routine);
							break;
						}
					}
				}
			}
			if (quit)
				break;
			ln++;
			l++;
		}
		if (quit)
			break;
		i++;
		cd++;
	}

	cnt = -1;
}

void
show_attr(char *cmd)
{
	char *c;
	caddr_t *p;
	short attr = -1, i = 0, l = 0;
	int a;

	c = cmd;
	while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') {
		c++;
		l++;
	}
	while (*c == ' ' || *c == '\t' || *c == '\n') {
		c++;
	}

	p = get_locnames(0);

	while (i <= maxlocnames) {
		if (strlen((char *)adjust(*p)) == l) {
			if (strncasecmp(cmd, adjust(*p), l) == 0)
				attr = i;
		}
		p++;
		i++;
	}

	if (attr == -1) {
		printf("Unknown attribute\n");
		return;
	}

	if (*c == '\0') {
		common_attr_val(attr, NULL, UC_SHOW);
	} else {
		if (number(c, &a) == 0) {
			common_attr_val(attr, &a, UC_SHOW);
		} else {
			printf("Unknown argument\n");
		}
	}
}

void
common_dev(char *dev, int len, short unit, short state, char routine)
{
	struct cfdata *cd;
	struct cfdriver *cdrv;
	int i = 0;

	switch (routine) {
	case UC_CHANGE:
		break;
	default:
		cnt = 0;
		break;
	}

	cnt = 0;

	cd = get_cfdata(0);

	while (cd->cf_attach != 0) {
		cdrv = (struct cfdriver *)adjust((caddr_t)cd->cf_driver);

		if (strlen((char *)adjust(cdrv->cd_name)) == len) {
			/*
			 * Ok, if device name is correct
			 *  If state == FSTATE_FOUND, look for "dev"
			 *  If state == FSTATE_STAR, look for "dev*"
			 *  If state == FSTATE_NOTFOUND, look for "dev0"
			 */
			if (!strncasecmp(dev,(char *)adjust(cdrv->cd_name), len) &&
			    (state == FSTATE_FOUND ||
			    (state == FSTATE_STAR &&
			    (cd->cf_fstate == FSTATE_STAR ||
			    cd->cf_fstate == FSTATE_DSTAR)) ||
			    (state == FSTATE_NOTFOUND &&
			    cd->cf_unit == unit &&
			    (cd->cf_fstate == FSTATE_NOTFOUND ||
			    cd->cf_fstate == FSTATE_DNOTFOUND)))) {
				if (more())
					break;
				switch (routine) {
				case UC_CHANGE:
					change(i);
					break;
				case UC_ENABLE:
					enable(i);
					break;
				case UC_DISABLE:
					disable(i);
					break;
				case UC_FIND:
					pdev(i);
					break;
				default:
					printf("Unknown routine /%c/\n",
					    routine);
					break;
				}
			}
		}
		i++;
		cd++;
	}

	if (nopdev == 0) {
		for (i = 0; i < maxpseudo; i++) {
			if (!strncasecmp(dev, (char *)get_pdevnames(i), len) &&
			    state == FSTATE_FOUND) {
				switch (routine) {
				case UC_CHANGE:
					change(totdev+1+i);
					break;
				case UC_ENABLE:
					enable(totdev+1+i);
					break;
				case UC_DISABLE:
					disable(totdev+1+i);
					break;
				case UC_FIND:
					pdev(totdev+1+i);
					break;
				default:
					printf("Unknown pseudo routine /%c/\n",
					    routine);
					break;
				}
			}
		}
	}

	switch (routine) {
	case UC_CHANGE:
		break;
	default:
		cnt = -1;
		break;
	}
}

void
common_attr(char *cmd, int attr, char routine)
{
	char *c;
	short l = 0;
	int a;

	c = cmd;
	while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') {
		c++;
		l++;
	}
	while (*c == ' ' || *c == '\t' || *c == '\n') {
		c++;
	}
	if (*c == '\0') {
		printf("Value missing for attribute\n");
		return;
	}

	if (number(c, &a) == 0) {
		common_attr_val(attr, &a, routine);
	} else {
		printf("Unknown argument\n");
	}
}

void
add_read(char *prompt, char field, char *dev, int len, int *val)
{
	int ok = 0;
	int a;
	cmd_t cmd;
	struct cfdata *cd;
	struct cfdriver *cdrv;

	*val = -1;

	while (!ok) {
		printf("%s ? ", prompt);
		fflush(stdout);

		ask_cmd(&cmd);

		if (strlen(cmd.cmd) != 0) {
			if (number(cmd.cmd, &a) == 0) {
				if (a > maxdev) {
					printf("Unknown devno (max is %d)\n",
					    maxdev);
				} else {
					cd = get_cfdata(a);
					cdrv = (struct cfdriver *)
					  adjust((caddr_t)cd->cf_driver);
					if (strncasecmp(dev,
							(char *)adjust(cdrv->cd_name),
							len) != 0 &&
					    field == 'a') {
						printf("Not same device type\n");
					} else {
						*val = a;
						ok = 1;
					}
				}
			} else if (cmd.cmd[0] == '?') {
				common_dev(dev, len, 0,
				    FSTATE_FOUND, UC_FIND);
			} else if (cmd.cmd[0] == 'q' ||
				   cmd.cmd[0] == 'Q') {
				ok = 1;
			} else {
				printf("Unknown argument\n");
			}
		} else {
			ok = 1;
		}
	}

}

void
add(char *dev, int len, short unit, short state)
{
	int i = 0, found = 0, *p;
	short *pv;
	struct cfdata new, *cd, *cdp;
	struct cfdriver *cdrv;
	int  val, max_unit, star_unit;

	ukc_mod_kernel = 1;

	bzero(&new, sizeof(struct cfdata));

	if (maxdev == totdev) {
		printf("No more space for new devices.\n");
		return;
	}

	if (state == FSTATE_FOUND) {
		printf("Device not complete number or * is missing\n");
		return;
	}

	cd = get_cfdata(0);

	while (cd->cf_attach != 0) {
		cdrv = (struct cfdriver *)adjust((caddr_t)cd->cf_driver);

		if (strlen((char *)adjust(cdrv->cd_name)) == len &&
		    strncasecmp(dev, (char *)adjust(cdrv->cd_name), len) == 0)
			found = 1;
		cd++;
	}

	if (!found) {
		printf("No device of this type exists.\n");
		return;
	}

	add_read("Clone Device (DevNo, 'q' or '?')", 'a', dev, len, &val);

	if (val != -1) {
		cd = get_cfdata(val);
		new = *cd;
		new.cf_unit = unit;
		new.cf_fstate = state;
		add_read("Insert before Device (DevNo, 'q' or '?')",
		    'i', dev, len, &val);
	}

	if (val != -1) {

		/* Insert the new record */
		cdp = cd = get_cfdata(maxdev+1);
		cdp--;
		for (i = maxdev; val <= i; i--) {
			*cd-- = *cdp--;
		}
		cd = get_cfdata(val);
		*cd = new;

		/* Fix indexs in pv */
		p = (int *)adjust((caddr_t)nl[I_PV_SIZE].n_value);
		pv = (short *)adjust((caddr_t)nl[SA_PV].n_value);
		for (i = 0; i < *p; i++) {
			if (*pv != 1 && *pv >= val)
				*pv = *pv + 1;
			pv++;
		}

		/* Fix indexs in cfroots */
		p = (int *)adjust((caddr_t)nl[I_CFROOTS_SIZE].n_value);
		pv = (short *)adjust((caddr_t)nl[SA_CFROOTS].n_value);
		for (i = 0; i < *p; i++) {
			if (*pv != 1 && *pv >= val)
				*pv = *pv + 1;
			pv++;
		}

		maxdev++;

		max_unit = -1;

		/* Find max unit number of the device type */

		cd = get_cfdata(0);

		while (cd->cf_attach != 0) {
			cdrv = (struct cfdriver *)
			  adjust((caddr_t)cd->cf_driver);

			if (strlen((char *)adjust(cdrv->cd_name)) == len &&
			    strncasecmp(dev, (char *)adjust(cdrv->cd_name),
			    len) == 0) {
				switch (cd->cf_fstate) {
				case FSTATE_NOTFOUND:
				case FSTATE_DNOTFOUND:
					if (cd->cf_unit > max_unit)
						max_unit = cd->cf_unit;
					break;
				default:
					break;
				}
			}
			cd++;
		}

		/*
		 * For all * entries set unit number to max+1, and update
		 * cf_starunit1 if necessary.
		 */
		max_unit++;
		star_unit = -1;
		cd = get_cfdata(0);
		while (cd->cf_attach != 0) {
			cdrv = (struct cfdriver *)
			    adjust((caddr_t)cd->cf_driver);

			if (strlen((char *)adjust(cdrv->cd_name)) == len &&
			    strncasecmp(dev, (char *)adjust(cdrv->cd_name),
			    len) == 0) {
				switch (cd->cf_fstate) {
				case FSTATE_NOTFOUND:
				case FSTATE_DNOTFOUND:
					if (cd->cf_unit > star_unit)
						star_unit = cd->cf_unit;
					break;
				default:
					break;
				}
			}
			cd++;
		}
		star_unit++;

		cd = get_cfdata(0);
		while (cd->cf_attach != 0) {
			cdrv = (struct cfdriver *)
			    adjust((caddr_t)cd->cf_driver);

			if (strlen((char *)adjust(cdrv->cd_name)) == len &&
			    strncasecmp(dev, (char *)adjust(cdrv->cd_name),
			    len) == 0) {
				switch (cd->cf_fstate) {
				case FSTATE_STAR:
				case FSTATE_DSTAR:
					cd->cf_unit = max_unit;
					if (cd->cf_starunit1 < star_unit)
						cd->cf_starunit1 = star_unit;
					break;
				default:
					break;
				}
			}
			cd++;
		}

		pdev(val);
	}

	/* cf_attach, cf_driver, cf_unit, cf_fstate, cf_loc, cf_flags,
	   cf_parents, cf_locnames, cf_locnames and cf_ivstubs */
}

void
add_history(int devno, short unit, short state, int newno)
{
	int i = 0, *p;
	short *pv;
	struct cfdata new, *cd, *cdp;
	struct cfdriver *cdrv;
	int  val, max_unit;
	int  len;
	char *dev;

	ukc_mod_kernel = 1;

	bzero(&new, sizeof(struct cfdata));
	cd = get_cfdata(devno);
	new = *cd;
	new.cf_unit = unit;
	new.cf_fstate = state;

	val = newno;

	cdrv = (struct cfdriver *) adjust((caddr_t)cd->cf_driver);
	dev = adjust((caddr_t)cdrv->cd_name);
	len = strlen(dev);

	/* Insert the new record */
	cdp = cd = get_cfdata(maxdev+1);
	cdp--;
	for (i = maxdev; val <= i; i--)
		*cd-- = *cdp--;
	cd = get_cfdata(val);
	*cd = new;

	/* Fix indexs in pv */
	p = (int *)adjust((caddr_t)nl[I_PV_SIZE].n_value);
	pv = (short *)adjust((caddr_t)nl[SA_PV].n_value);
	for (i = 0; i < *p; i++) {
		if (*pv != 1 && *pv >= val)
			*pv = *pv + 1;
		pv++;
	}

	/* Fix indexs in cfroots */
	p = (int *)adjust((caddr_t)nl[I_CFROOTS_SIZE].n_value);
	pv = (short *)adjust((caddr_t)nl[SA_CFROOTS].n_value);
	for (i = 0; i < *p; i++) {
		if (*pv != 1 && *pv >= val)
			*pv = *pv + 1;
		pv++;
	}

	maxdev++;
	max_unit = -1;

	/* Find max unit number of the device type */
	cd = get_cfdata(0);
	while (cd->cf_attach != 0) {
		cdrv = (struct cfdriver *)
		    adjust((caddr_t)cd->cf_driver);

		if (strlen((char *)adjust(cdrv->cd_name)) == len &&
		    strncasecmp(dev, (char *)adjust(cdrv->cd_name),
		    len) == 0) {
			switch (cd->cf_fstate) {
			case FSTATE_NOTFOUND:
			case FSTATE_DNOTFOUND:
				if (cd->cf_unit > max_unit)
					max_unit = cd->cf_unit;
				break;
			default:
				break;
			}
		}
		cd++;
	}

	/* For all * entries set unit number to max+1 */
	max_unit++;
	cd = get_cfdata(0);
	while (cd->cf_attach != 0) {
		cdrv = (struct cfdriver *)
		    adjust((caddr_t)cd->cf_driver);

		if (strlen((char *)adjust(cdrv->cd_name)) == len &&
		    strncasecmp(dev, (char *)adjust(cdrv->cd_name),
		    len) == 0) {
			switch (cd->cf_fstate) {
			case FSTATE_STAR:
			case FSTATE_DSTAR:
				cd->cf_unit = max_unit;
				break;
			default:
				break;
			}
		}
		cd++;
	}

	printf("%3d ", newno);
	pdevnam(newno);
	printf(" added\n");
	pdev(val);
}

int
config(void)
{
	cmd_t cmd;
	int i, st;

	/* Set up command table pointer */
	cmd.table = cmd_table;

	printf("Enter 'help' for information\n");

	/* Edit cycle */
	do {
again:
		printf("ukc> ");
		fflush(stdout);
		ask_cmd(&cmd);

		if (cmd.cmd[0] == '\0')
			goto again;
		for (i = 0; cmd_table[i].cmd != NULL; i++)
			if (strstr(cmd_table[i].cmd, cmd.cmd) ==
			    cmd_table[i].cmd)
				break;

		/* Quick hack to put in '?' == 'help' */
		if (!strcmp(cmd.cmd, "?"))
			i = 0;

		/* Check for valid command */
		if (cmd_table[i].cmd == NULL) {
			printf("Invalid command '%s'.  Try 'help'.\n", cmd.cmd);
			continue;
		} else
			strlcpy(cmd.cmd, cmd_table[i].cmd, sizeof cmd.cmd);

		/* Call function */
		st = cmd_table[i].fcn(&cmd);

		/* Update status */
		if (st == CMD_EXIT)
			break;
		if (st == CMD_SAVE)
			break;
	} while (1);

	return (st == CMD_SAVE);
}

void
process_history(int len, char *buf)
{
	char *c;
	int devno, newno;
	short unit, state;
	struct timezone *tz;

	if (len == 0) {
		printf("History is empty\n");
		return;
	}

	printf("Processing history...\n");

	buf[len] = 0;

	c = buf;

	while (*c != NULL) {
		switch (*c) {
		case 'a':
			c++;
			c++;
			devno = atoi(c);
			while (*c >= '0' && *c <= '9')
				c++;
			c++;
			unit = atoi(c);
			if (*c == '-') c++;
			while (*c >= '0' && *c <= '9')
				c++;
			c++;
			state = atoi(c);
			if (*c == '-')
				c++;
			while (*c >= '0' && *c <= '9')
				c++;
			c++;
			newno = atoi(c);
			while (*c >= '0' && *c <= '9')
				c++;
			add_history(devno, unit, state, newno);
			while (*c != '\n')
				c++;
			c++;
			break;
		case 'c':
			c++;
			c++;
			devno = atoi(c);
			while (*c >= '0' && *c <= '9')
				c++;
			if (*c == ' ')
				c++;
			if (*c != '\n')
				change_history(devno, c);
			while (*c != '\n')
				c++;
			c++;
			break;
		case 'd':
			c++;
			devno = atoi(c);
			disable(devno);
			while (*c != '\n')
				c++;
			c++;
			break;
		case 'e':
			c++;
			devno = atoi(c);
			enable(devno);
			while (*c != '\n')
				c++;
			c++;
			break;
		case 't':
			c++;
			c++;
			tz = (struct timezone *)adjust((caddr_t)nl[TZ_TZ].
			    n_value);
			tz->tz_minuteswest = atoi(c);
			while (*c != ' ')
				c++;
			c++;
			tz->tz_dsttime = atoi(c);
			while (*c != '\n')
				c++;
			c++;
			ukc_mod_kernel = 1;
			break;
		case 'q':
			while (*c != NULL)
				c++;
			break;
		default:
			printf("unknown command %c\n", *c);
			while (*c != NULL && *c != '\n')
				c++;
			break;
		}
	}
}