root/sys/kern/subr_userconf.c
/*      $OpenBSD: subr_userconf.c,v 1.48 2022/08/14 01:58:28 jsg Exp $  */

/*
 * Copyright (c) 1996-2001 Mats O Jansson <moj@stacken.kth.se>
 * 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <dev/cons.h>

extern char *locnames[];
extern short locnamp[];
extern short cfroots[];
extern int cfroots_size;
extern int pv_size;
extern short pv[];
extern char *pdevnames[];
extern int pdevnames_size;
extern struct pdevinit pdevinit[];

int userconf_base = 16;                         /* Base for "large" numbers */
int userconf_maxdev = -1;                       /* # of used device slots   */
int userconf_totdev = -1;                       /* # of device slots        */
int userconf_maxlocnames = -1;                  /* # of locnames            */
int userconf_cnt = -1;                          /* Line counter for ...     */
int userconf_lines = 12;                        /* ... # of lines per page  */
int userconf_histlen = 0;
int userconf_histcur = 0;
char userconf_history[1024];
int userconf_histsz = sizeof(userconf_history);
char userconf_argbuf[40];                       /* Additional input         */
char userconf_cmdbuf[40];                       /* Command line             */
char userconf_histbuf[40];

void userconf_init(void);
int userconf_more(void);
void userconf_modify(char *, long *, long);
void userconf_hist_cmd(char);
void userconf_hist_int(long);
void userconf_hist_eoc(void);
void userconf_pnum(long);
void userconf_pdevnam(short);
void userconf_pdev(short);
int userconf_number(char *, long *, long);
int userconf_device(char *, long *, short *, short *);
int userconf_attr(char *, long *);
void userconf_change(int);
void userconf_disable(int);
void userconf_enable(int);
void userconf_help(void);
void userconf_list(void);
void userconf_show(void);
void userconf_common_attr_val(short, long *, char);
void userconf_show_attr(char *);
void userconf_common_dev(char *, int, short, short, char);
void userconf_common_attr(char *, int, char);
void userconf_add_read(char *, char, char *, int, long *);
void userconf_add(char *, int, short, short);
int userconf_parse(char *);

#define UC_CHANGE 'c'
#define UC_DISABLE 'd'
#define UC_ENABLE 'e'
#define UC_FIND 'f'
#define UC_SHOW 's'

char *userconf_cmds[] = {
        "add",          "a",
        "base",         "b",
        "change",       "c",
#if defined(DDB)
        "ddb",          "D",
#endif
        "disable",      "d",
        "enable",       "e",
        "exit",         "q",
        "find",         "f",
        "help",         "h",
        "list",         "l",
        "lines",        "L",
        "quit",         "q",
        "show",         "s",
        "verbose",      "v",
        "?",            "h",
        "",              "",
};

void
userconf_init(void)
{
        int i = 0;
        struct cfdata *cd;
        int   ln;

        while (cfdata[i].cf_attach != NULL) {
                userconf_maxdev = i;
                userconf_totdev = i;

                cd = &cfdata[i];
                ln = cd->cf_locnames;
                while (locnamp[ln] != -1) {
                        if (locnamp[ln] > userconf_maxlocnames)
                                userconf_maxlocnames = locnamp[ln];
                        ln++;
                }
                i++;
        }

        while (cfdata[i].cf_attach == NULL) {
                userconf_totdev = i;
                i++;
        }
        userconf_totdev = userconf_totdev - 1;
}

int
userconf_more(void)
{
        int quit = 0;
        char c = '\0';

        if (userconf_cnt != -1) {
                if (userconf_cnt == userconf_lines) {
                        printf("--- more ---");
                        c = cngetc();
                        userconf_cnt = 0;
                        printf("\r            \r");
                }
                userconf_cnt++;
                if (c == 'q' || c == 'Q')
                        quit = 1;
        }
        return (quit);
}

void
userconf_hist_cmd(char cmd)
{
        userconf_histcur = userconf_histlen;
        if (userconf_histcur < userconf_histsz) {
                userconf_history[userconf_histcur] = cmd;
                userconf_histcur++;
        }
}

void
userconf_hist_int(long val)
{
        snprintf(userconf_histbuf, sizeof userconf_histbuf, " %ld", val);
        if (userconf_histcur + strlen(userconf_histbuf) < userconf_histsz) {
                bcopy(userconf_histbuf,
                    &userconf_history[userconf_histcur],
                    strlen(userconf_histbuf));
                userconf_histcur = userconf_histcur + strlen(userconf_histbuf);
        }
}

void
userconf_hist_eoc(void)
{
        if (userconf_histcur < userconf_histsz) {
                userconf_history[userconf_histcur] = '\n';
                userconf_histcur++;
                userconf_histlen = userconf_histcur;
        }
}

void
userconf_pnum(long val)
{
        if (val > -2 && val < 16) {
                printf("%ld",val);
                return;
        }

        switch (userconf_base) {
        case 8:
                printf("0%lo",val);
                break;
        case 10:
                printf("%ld",val);
                break;
        case 16:
        default:
                printf("0x%lx",val);
                break;
        }
}

void
userconf_pdevnam(short dev)
{
        struct cfdata *cd;

        cd = &cfdata[dev];
        printf("%s", cd->cf_driver->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
userconf_pdev(short devno)
{
        struct cfdata *cd;
        short *p;
        long  *l;
        int   ln;
        char c;

        if (devno > userconf_maxdev && devno <= userconf_totdev) {
                printf("%3d free slot (for add)\n", devno);
                return;
        }

        if (devno > userconf_totdev &&
            devno <= userconf_totdev+pdevnames_size) {
                printf("%3d %s count %d", devno,
                    pdevnames[devno-userconf_totdev-1],
                    abs(pdevinit[devno-userconf_totdev-1].pdev_count));
                if (pdevinit[devno-userconf_totdev-1].pdev_count < 1)
                        printf(" disable");
                printf(" (pseudo device)\n");
                return;
        }

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

        cd = &cfdata[devno];

        printf("%3d ", devno);
        userconf_pdevnam(devno);
        printf(" at");
        c = ' ';
        p = cd->cf_parents;
        if (*p == -1)
                printf(" root");
        while (*p != -1) {
                printf("%c", c);
                userconf_pdevnam(*p++);
                c = '|';
        }
        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;
        }
        l = cd->cf_loc;
        ln = cd->cf_locnames;
        while (locnamp[ln] != -1) {
                printf(" %s ", locnames[locnamp[ln]]);
                ln++;
                userconf_pnum(*l++);
        }
        printf(" flags 0x%x\n", cd->cf_flags);
}

int
userconf_number(char *c, long *val, long limit)
{
        u_long num = 0;
        int neg = 0;
        int base = 10;

        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 > limit) /* overflow */
                return (1);
        *val = neg ? - num : num;
        return (0);
}

int
userconf_device(char *cmd, long *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
userconf_attr(char *cmd, long *val)
{
        char *c;
        short attr = -1, i = 0, l = 0;

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

        while (i <= userconf_maxlocnames) {
                if (strlen(locnames[i]) == l) {
                        if (strncasecmp(cmd, locnames[i], l) == 0)
                                attr = i;
                }
                i++;
        }

        if (attr == -1) {
                return (-1);
        }

        *val = attr;

        return(0);
}

void
userconf_modify(char *item, long *val, long limit)
{
        int ok = 0;
        long a;
        char *c;
        int i;

        while (!ok) {
                printf("%s [", item);
                userconf_pnum(*val);
                printf("] ? ");

                i = getsn(userconf_argbuf, sizeof(userconf_argbuf));

                c = userconf_argbuf;
                while (*c == ' ' || *c == '\t' || *c == '\n') c++;

                if (*c != '\0') {
                        if (userconf_number(c, &a, limit) == 0) {
                                *val = a;
                                ok = 1;
                        } else {
                                printf("Unknown argument\n");
                        }
                } else {
                        ok = 1;
                }
        }
}

void
userconf_change(int devno)
{
        struct cfdata *cd;
        char c = '\0';
        long  *l, tmp;
        int   ln;

        if (devno <=  userconf_maxdev) {
                userconf_pdev(devno);

                while (c != 'y' && c != 'Y' && c != 'n' && c != 'N') {
                        printf("change (y/n) ?");
                        c = cngetc();
                        printf("\n");
                }

                if (c == 'y' || c == 'Y') {
                        int share = 0, i, lklen;
                        long *lk;

                        /* XXX add cmd 'c' <devno> */
                        userconf_hist_cmd('c');
                        userconf_hist_int(devno);

                        cd = &cfdata[devno];
                        l = cd->cf_loc;
                        ln = cd->cf_locnames;

                        /*
                         * Search for some other driver sharing this
                         * locator table. if one does, we may need to
                         * replace the locators with a malloc'd copy.
                         */
                        for (i = 0; cfdata[i].cf_driver; i++)
                                if (i != devno && cfdata[i].cf_loc == l)
                                        share = 1;
                        if (share) {
                                for (i = 0; locnamp[ln+i] != -1 ; i++)
                                        ;
                                lk = l = mallocarray(i, sizeof(long),
                                    M_TEMP, M_NOWAIT);
                                if (lk == NULL) {
                                        printf("out of memory.\n");
                                        return;
                                }
                                lklen = i * sizeof(long);
                                bcopy(cd->cf_loc, l, lklen);
                        }

                        while (locnamp[ln] != -1) {
                                userconf_modify(locnames[locnamp[ln]], l,
                                    LONG_MAX);

                                /* XXX add *l */
                                userconf_hist_int(*l);

                                ln++;
                                l++;
                        }
                        tmp = cd->cf_flags;
                        userconf_modify("flags", &tmp, INT_MAX);
                        userconf_hist_int(tmp);
                        cd->cf_flags = tmp;

                        if (share) {
                                if (memcmp(cd->cf_loc, lk, lklen))
                                        cd->cf_loc = lk;
                                else
                                        free(lk, M_TEMP, lklen);
                        }

                        printf("%3d ", devno);
                        userconf_pdevnam(devno);
                        printf(" changed\n");
                        userconf_pdev(devno);
                }
                return;
        }

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

        if (devno > userconf_totdev &&
            devno <= userconf_totdev+pdevnames_size) {
                userconf_pdev(devno);
                while (c != 'y' && c != 'Y' && c != 'n' && c != 'N') {
                        printf("change (y/n) ?");
                        c = cngetc();
                        printf("\n");
                }

                if (c == 'y' || c == 'Y') {
                        /* XXX add cmd 'c' <devno> */
                        userconf_hist_cmd('c');
                        userconf_hist_int(devno);

                        tmp = pdevinit[devno-userconf_totdev-1].pdev_count;
                        userconf_modify("count", &tmp, INT_MAX);
                        userconf_hist_int(tmp);
                        pdevinit[devno-userconf_totdev-1].pdev_count = tmp;

                        printf("%3d %s changed\n", devno,
                            pdevnames[devno-userconf_totdev-1]);
                        userconf_pdev(devno);

                        /* XXX add eoc */
                        userconf_hist_eoc();
                }
                return;
        }

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

void
userconf_disable(int devno)
{
        int done = 0;

        if (devno <= userconf_maxdev) {
                switch (cfdata[devno].cf_fstate) {
                case FSTATE_NOTFOUND:
                        cfdata[devno].cf_fstate = FSTATE_DNOTFOUND;
                        break;
                case FSTATE_STAR:
                        cfdata[devno].cf_fstate = FSTATE_DSTAR;
                        break;
                case FSTATE_DNOTFOUND:
                case FSTATE_DSTAR:
                        done = 1;
                        break;
                default:
                        printf("Error unknown state\n");
                        break;
                }

                printf("%3d ", devno);
                userconf_pdevnam(devno);
                if (done) {
                        printf(" already");
                } else {
                        /* XXX add cmd 'd' <devno> eoc */
                        userconf_hist_cmd('d');
                        userconf_hist_int(devno);
                        userconf_hist_eoc();
                }
                printf(" disabled\n");

                return;
        }

        if (devno > userconf_maxdev && devno <= userconf_totdev) {
                printf("%3d can't disable free slot\n", devno);
                return;
        }

        if (devno > userconf_totdev &&
            devno <= userconf_totdev+pdevnames_size) {
                printf("%3d %s", devno, pdevnames[devno-userconf_totdev-1]);
                if (pdevinit[devno-userconf_totdev-1].pdev_count < 1) {
                        printf(" already ");
                } else {
                        pdevinit[devno-userconf_totdev-1].pdev_count *= -1;
                        /* XXX add cmd 'd' <devno> eoc */
                        userconf_hist_cmd('d');
                        userconf_hist_int(devno);
                        userconf_hist_eoc();
                }
                printf(" disabled\n");
                return;
        }

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

void
userconf_enable(int devno)
{
        int done = 0;

        if (devno <= userconf_maxdev) {
                switch (cfdata[devno].cf_fstate) {
                case FSTATE_DNOTFOUND:
                        cfdata[devno].cf_fstate = FSTATE_NOTFOUND;
                        break;
                case FSTATE_DSTAR:
                        cfdata[devno].cf_fstate = FSTATE_STAR;
                        break;
                case FSTATE_NOTFOUND:
                case FSTATE_STAR:
                        done = 1;
                        break;
                default:
                        printf("Error unknown state\n");
                        break;
                }

                printf("%3d ", devno);
                userconf_pdevnam(devno);
                if (done) {
                        printf(" already");
                } else {
                        /* XXX add cmd 'e' <devno> eoc */
                        userconf_hist_cmd('e');
                        userconf_hist_int(devno);
                        userconf_hist_eoc();
                }
                printf(" enabled\n");
                return;
        }

        if (devno > userconf_maxdev && devno <= userconf_totdev) {
                printf("%3d can't enable free slot\n", devno);
                return;
        }

        if (devno > userconf_totdev &&
            devno <= userconf_totdev+pdevnames_size) {
                printf("%3d %s", devno, pdevnames[devno-userconf_totdev-1]);
                if (pdevinit[devno-userconf_totdev-1].pdev_count > 0) {
                        printf(" already");
                } else {
                        pdevinit[devno-userconf_totdev-1].pdev_count *= -1;
                        /* XXX add cmd 'e' <devno> eoc */
                        userconf_hist_cmd('e');
                        userconf_hist_int(devno);
                        userconf_hist_eoc();
                }
                printf(" enabled\n");
                return;
        }

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

void
userconf_help(void)
{
        int j = 0, k;

        printf("command   args                description\n");
        while (*userconf_cmds[j] != '\0') {
                printf("%s", userconf_cmds[j]);
                k = strlen(userconf_cmds[j]);
                while (k < 10) {
                        printf(" ");
                        k++;
                }
                switch (*userconf_cmds[j+1]) {
                case 'L':
                        printf("[count]             number of lines before more");
                        break;
                case 'a':
                        printf("dev                 add a device");
                        break;
                case 'b':
                        printf("8|10|16             base on large numbers");
                        break;
                case 'c':
                        printf("devno|dev           change devices");
                        break;
#if defined(DDB)
                case 'D':
                        printf("                    enter ddb");
                        break;
#endif
                case 'd':
                        printf("attr val|devno|dev  disable devices");
                        break;
                case 'e':
                        printf("attr val|devno|dev  enable devices");
                        break;
                case 'f':
                        printf("devno|dev           find devices");
                        break;
                case 'h':
                        printf("                    this message");
                        break;
                case 'l':
                        printf("                    list configuration");
                        break;
                case 'q':
                        printf("                    leave UKC");
                        break;
                case 's':
                        printf("[attr [val]]        "
                           "show attributes (or devices with an attribute)");
                        break;
                case 'v':
                        printf("                    toggle verbose booting");
                        break;
                default:
                        printf("                    don't know");
                        break;
                }
                printf("\n");
                j += 2;
        }
}

void
userconf_list(void)
{
        int i = 0;

        userconf_cnt = 0;

        while (i <= (userconf_totdev+pdevnames_size)) {
                if (userconf_more())
                        break;
                userconf_pdev(i++);
        }

        userconf_cnt = -1;
}

void
userconf_show(void)
{
        int i = 0;

        userconf_cnt = 0;

        while (i <= userconf_maxlocnames) {
                if (userconf_more())
                        break;
                printf("%s\n", locnames[i++]);
        }

        userconf_cnt = -1;
}

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

        userconf_cnt = 0;

        while (i <= userconf_maxdev) {
                cd = &cfdata[i];
                l = cd->cf_loc;
                ln = cd->cf_locnames;
                while (locnamp[ln] != -1) {
                        if (locnamp[ln] == attr) {
                                if (val == NULL) {
                                        quit = userconf_more();
                                        userconf_pdev(i);
                                } else {
                                        if (*val == *l) {
                                                quit = userconf_more();
                                                switch (routine) {
                                                case UC_ENABLE:
                                                        userconf_enable(i);
                                                        break;
                                                case UC_DISABLE:
                                                        userconf_disable(i);
                                                        break;
                                                case UC_SHOW:
                                                        userconf_pdev(i);
                                                        break;
                                                default:
                                                        printf("Unknown routine /%c/\n",
                                                            routine);
                                                        break;
                                                }
                                        }
                                }
                        }
                        if (quit)
                                break;
                        ln++;
                        l++;
                }
                if (quit)
                        break;
                i++;
        }

        userconf_cnt = -1;
}

void
userconf_show_attr(char *cmd)
{
        char *c;
        short attr = -1, i = 0, l = 0;
        long a;

        c = cmd;
        while (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\0') {
                c++;
                l++;
        }
        while (*c == ' ' || *c == '\t' || *c == '\n') {
                c++;
        }
        while (i <= userconf_maxlocnames) {
                if (strlen(locnames[i]) == l) {
                        if (strncasecmp(cmd, locnames[i], l) == 0) {
                                attr = i;
                        }
                }
                i++;
        }

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

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

void
userconf_common_dev(char *dev, int len, short unit, short state, char routine)
{
        int i = 0;

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

        while (cfdata[i].cf_attach != NULL) {
                if (strlen(cfdata[i].cf_driver->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, cfdata[i].cf_driver->cd_name,
                                        len) == 0 &&
                            (state == FSTATE_FOUND ||
                             (state == FSTATE_STAR &&
                              (cfdata[i].cf_fstate == FSTATE_STAR ||
                               cfdata[i].cf_fstate == FSTATE_DSTAR)) ||
                             (state == FSTATE_NOTFOUND &&
                              cfdata[i].cf_unit == unit &&
                              (cfdata[i].cf_fstate == FSTATE_NOTFOUND ||
                               cfdata[i].cf_fstate == FSTATE_DNOTFOUND)))) {
                                if (userconf_more())
                                        break;
                                switch (routine) {
                                case UC_CHANGE:
                                        userconf_change(i);
                                        break;
                                case UC_ENABLE:
                                        userconf_enable(i);
                                        break;
                                case UC_DISABLE:
                                        userconf_disable(i);
                                        break;
                                case UC_FIND:
                                        userconf_pdev(i);
                                        break;
                                default:
                                        printf("Unknown routine /%c/\n",
                                            routine);
                                        break;
                                }
                        }
                }
                i++;
        }

        for (i = 0; i < pdevnames_size; i++) {
                if (strncasecmp(dev, pdevnames[i], len) == 0 &&
                    state == FSTATE_FOUND) {
                        switch(routine) {
                        case UC_CHANGE:
                                userconf_change(userconf_totdev+1+i);
                                break;
                        case UC_ENABLE:
                                userconf_enable(userconf_totdev+1+i);
                                break;
                        case UC_DISABLE:
                                userconf_disable(userconf_totdev+1+i);
                                break;
                        case UC_FIND:
                                userconf_pdev(userconf_totdev+1+i);
                                break;
                        default:
                                printf("Unknown pseudo routine /%c/\n",routine);
                                break;
                        }
                }
        }

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

void
userconf_common_attr(char *cmd, int attr, char routine)
{
        char *c;
        short l = 0;
        long 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 (userconf_number(c, &a, INT_MAX) == 0) {
                userconf_common_attr_val(attr, &a, routine);
        } else {
                printf("Unknown argument\n");
        }
}

void
userconf_add_read(char *prompt, char field, char *dev, int len, long *val)
{
        int ok = 0;
        long a;
        char *c;
        int i;

        *val = -1;

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

                i = getsn(userconf_argbuf, sizeof(userconf_argbuf));

                c = userconf_argbuf;
                while (*c == ' ' || *c == '\t' || *c == '\n')
                        c++;

                if (*c != '\0') {
                        if (userconf_number(c, &a, INT_MAX) == 0) {
                                if (a > userconf_maxdev) {
                                        printf("Unknown devno (max is %d)\n",
                                            userconf_maxdev);
                                } else if (strncasecmp(dev,
                                    cfdata[a].cf_driver->cd_name, len) != 0 &&
                                    field == 'a') {
                                        printf("Not same device type\n");
                                } else {
                                        *val = a;
                                        ok = 1;
                                }
                        } else if (*c == '?') {
                                userconf_common_dev(dev, len, 0,
                                    FSTATE_FOUND, UC_FIND);
                        } else if (*c == 'q' || *c == 'Q') {
                                ok = 1;
                        } else {
                                printf("Unknown argument\n");
                        }
                } else {
                        ok = 1;
                }
        }
}

void
userconf_add(char *dev, int len, short unit, short state)
{
        int found = 0;
        struct cfdata new;
        int max_unit, star_unit;
        long i = 0, val, orig;

        memset(&new, 0, sizeof(struct cfdata));

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

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

        for (i = 0; cfdata[i].cf_driver; i++)
                if (strlen(cfdata[i].cf_driver->cd_name) == len &&
                    strncasecmp(dev, cfdata[i].cf_driver->cd_name, len) == 0)
                        found = 1;

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

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

        if (val != -1) {
                orig = val;
                new = cfdata[val];
                new.cf_unit = unit;
                new.cf_fstate = state;
                userconf_add_read("Insert before Device (DevNo, 'q' or '?')",
                    'i', dev, len, &val);
        }

        if (val != -1) {
                /* XXX add cmd 'a' <orig> <val> eoc */
                userconf_hist_cmd('a');
                userconf_hist_int(orig);
                userconf_hist_int(unit);
                userconf_hist_int(state);
                userconf_hist_int(val);
                userconf_hist_eoc();

                /* Insert the new record */
                for (i = userconf_maxdev; val <= i; i--)
                        cfdata[i+1] = cfdata[i];
                cfdata[val] = new;

                /* Fix indexs in pv */
                for (i = 0; i < pv_size; i++) {
                        if (pv[i] != -1 && pv[i] >= val)
                                pv[i]++;
                }

                /* Fix indexs in cfroots */
                for (i = 0; i < cfroots_size; i++) {
                        if (cfroots[i] != -1 && cfroots[i] >= val)
                                cfroots[i]++;
                }

                userconf_maxdev++;

                max_unit = -1;

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

                i = 0;
                while (cfdata[i].cf_attach != NULL) {
                        if (strlen(cfdata[i].cf_driver->cd_name) == len &&
                            strncasecmp(dev, cfdata[i].cf_driver->cd_name,
                            len) == 0) {
                                switch (cfdata[i].cf_fstate) {
                                case FSTATE_NOTFOUND:
                                case FSTATE_DNOTFOUND:
                                        if (cfdata[i].cf_unit > max_unit)
                                                max_unit = cfdata[i].cf_unit;
                                        break;
                                default:
                                        break;
                                }
                        }
                        i++;
                }

                /*
                 * For all * entries set unit number to max+1, and update
                 * cf_starunit1 if necessary.
                 */
                max_unit++;
                star_unit = -1;

                i = 0;
                while (cfdata[i].cf_attach != NULL) {
                        if (strlen(cfdata[i].cf_driver->cd_name) == len &&
                            strncasecmp(dev, cfdata[i].cf_driver->cd_name,
                            len) == 0) {
                                switch (cfdata[i].cf_fstate) {
                                case FSTATE_NOTFOUND:
                                case FSTATE_DNOTFOUND:
                                        if (cfdata[i].cf_unit > star_unit)
                                                star_unit = cfdata[i].cf_unit;
                                        break;
                                default:
                                        break;
                                }
                        }
                        i++;
                }
                star_unit++;

                i = 0;
                while (cfdata[i].cf_attach != NULL) {
                        if (strlen(cfdata[i].cf_driver->cd_name) == len &&
                            strncasecmp(dev, cfdata[i].cf_driver->cd_name,
                            len) == 0) {
                                switch (cfdata[i].cf_fstate) {
                                case FSTATE_STAR:
                                case FSTATE_DSTAR:
                                        cfdata[i].cf_unit = max_unit;
                                        if (cfdata[i].cf_starunit1 < star_unit)
                                                cfdata[i].cf_starunit1 =
                                                    star_unit;
                                        break;
                                default:
                                        break;
                                }
                        }
                        i++;
                }
                userconf_pdev(val);
        }

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

int
userconf_parse(char *cmd)
{
        char *c, *v;
        int i = 0, j = 0, k;
        long a;
        short unit, state;

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

        k = -1;
        while (*userconf_cmds[j] != '\0') {
                if (strlen(userconf_cmds[j]) == i) {
                        if (strncasecmp(v, userconf_cmds[j], i) == 0)
                                k = j;
                }
                j += 2;
        }

        while (*c == ' ' || *c == '\t' || *c == '\n')
                c++;

        if (k == -1) {
                if (*v != '\n')
                        printf("Unknown command, try help\n");
        } else {
                switch (*userconf_cmds[k+1]) {
                case 'L':
                        if (*c == '\0')
                                printf("Argument expected\n");
                        else if (userconf_number(c, &a, INT_MAX) == 0)
                                userconf_lines = a;
                        else
                                printf("Unknown argument\n");
                        break;
                case 'a':
                        if (*c == '\0')
                                printf("Dev expected\n");
                        else if (userconf_device(c, &a, &unit, &state) == 0)
                                userconf_add(c, a, unit, state);
                        else
                                printf("Unknown argument\n");
                        break;
                case 'b':
                        if (*c == '\0')
                                printf("8|10|16 expected\n");
                        else if (userconf_number(c, &a, INT_MAX) == 0) {
                                if (a == 8 || a == 10 || a == 16) {
                                        userconf_base = a;
                                } else {
                                        printf("8|10|16 expected\n");
                                }
                        } else
                                printf("Unknown argument\n");
                        break;
                case 'c':
                        if (*c == '\0')
                                printf("DevNo or Dev expected\n");
                        else if (userconf_number(c, &a, INT_MAX) == 0)
                                userconf_change(a);
                        else if (userconf_device(c, &a, &unit, &state) == 0)
                                userconf_common_dev(c, a, unit, state, UC_CHANGE);
                        else
                                printf("Unknown argument\n");
                        break;
#if defined(DDB)
                case 'D':
                        db_enter();
                        break;
#endif
                case 'd':
                        if (*c == '\0')
                                printf("Attr, DevNo or Dev expected\n");
                        else if (userconf_attr(c, &a) == 0)
                                userconf_common_attr(c, a, UC_DISABLE);
                        else if (userconf_number(c, &a, INT_MAX) == 0)
                                userconf_disable(a);
                        else if (userconf_device(c, &a, &unit, &state) == 0)
                                userconf_common_dev(c, a, unit, state, UC_DISABLE);
                        else
                                printf("Unknown argument\n");
                        break;
                case 'e':
                        if (*c == '\0')
                                printf("Attr, DevNo or Dev expected\n");
                        else if (userconf_attr(c, &a) == 0)
                                userconf_common_attr(c, a, UC_ENABLE);
                        else if (userconf_number(c, &a, INT_MAX) == 0)
                                userconf_enable(a);
                        else if (userconf_device(c, &a, &unit, &state) == 0)
                                userconf_common_dev(c, a, unit, state, UC_ENABLE);
                        else
                                printf("Unknown argument\n");
                        break;
                case 'f':
                        if (*c == '\0')
                                printf("DevNo or Dev expected\n");
                        else if (userconf_number(c, &a, INT_MAX) == 0)
                                userconf_pdev(a);
                        else if (userconf_device(c, &a, &unit, &state) == 0)
                                userconf_common_dev(c, a, unit, state, UC_FIND);
                        else
                                printf("Unknown argument\n");
                        break;
                case 'h':
                        userconf_help();
                        break;
                case 'l':
                        if (*c == '\0')
                                userconf_list();
                        else
                                printf("Unknown argument\n");
                        break;
                case 'q':
                        /* XXX add cmd 'q' eoc */
                        userconf_hist_cmd('q');
                        userconf_hist_eoc();
                        return(-1);
                        break;
                case 's':
                        if (*c == '\0')
                                userconf_show();
                        else
                                userconf_show_attr(c);
                        break;
                case 'v':
                        autoconf_verbose = !autoconf_verbose;
                        printf("autoconf verbose %sabled\n",
                            autoconf_verbose ? "en" : "dis");
                        break;
                default:
                        printf("Unknown command\n");
                        break;
                }
        }
        return(0);
}

void
user_config(void)
{
        userconf_init();
        printf("User Kernel Config\n");

        cnpollc(1);
        while (1) {
                printf("UKC> ");
                if (getsn(userconf_cmdbuf, sizeof(userconf_cmdbuf)) > 0 &&
                    userconf_parse(userconf_cmdbuf))
                        break;
        }
        cnpollc(0);

        printf("Continuing...\n");
}