root/usr/src/lib/libadm/common/ckitem.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
 */

/*LINTLIBRARY*/

#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include "valtools.h"
#include <sys/types.h>
#include <stdlib.h>
#include <strings.h>
#include "libadm.h"

static int      insert(struct _choice_ *, CKMENU *);
static char     *strtoki(char *, char *);
static char     **match(CKMENU *, char *, int);
static int      getstr(char *, char *, char *, char *, char *);
static int      getnum(char *, int, int *, int *);
static struct _choice_ *next(struct _choice_ *);

static char     *deferr;
static char     *errmsg;
static char     *defhlp;

#define PROMPT  "Enter selection"
#define MESG0   "Entry does not match available menu selection. "
#define MESG1   "the number of the menu item you wish to select, or "
#define MESG2   "the token which is associated with the menu item,\
                or a partial string which uniquely identifies the \
                token for the menu item. Enter ?? to reprint the menu."

#define TOOMANY "Too many items selected from menu"
#define NOTUNIQ "The entered text does not uniquely identify a menu choice."
#define BADNUM  "Bad numeric choice specification"

static char *
setmsg(CKMENU *menup, short flag)
{
        int     n;
        char    *msg;

        n = (int)(6 + sizeof (MESG2));
        if (flag)
                n += (int)(sizeof (MESG0));

        if (menup->attr & CKUNNUM) {
                msg = calloc((size_t)n, sizeof (char));
                if (flag)
                        (void) strcpy(msg, MESG0);
                else
                        msg[0] = '\0';
                (void) strcat(msg, "Enter ");
                (void) strcat(msg, MESG2);
        } else {
                msg = calloc(n+sizeof (MESG1), sizeof (char));
                if (flag)
                        (void) strcpy(msg, MESG0);
                else
                        msg[0] = '\0';
                (void) strcat(msg, "Enter ");
                (void) strcat(msg, MESG1);
                (void) strcat(msg, MESG2);
        }
        return (msg);
}

CKMENU *
allocmenu(char *label, int attr)
{
        CKMENU *pt;

        if (pt = calloc(1, sizeof (CKMENU))) {
                pt->attr = attr;
                pt->label = label;
        }
        return (pt);
}

void
ckitem_err(CKMENU *menup, char *error)
{
        deferr = setmsg(menup, 1);
        puterror(stdout, deferr, error);
        free(deferr);
}

void
ckitem_hlp(CKMENU *menup, char *help)
{
        defhlp = setmsg(menup, 0);
        puthelp(stdout, defhlp, help);
        free(defhlp);
}

int
ckitem(CKMENU *menup, char *item[], short max, char *defstr, char *error,
        char *help, char *prompt)
{
        int     n, i;
        char    strval[MAX_INPUT];
        char    **list;

        if ((menup->nchoices <= 0) && !menup->invis)
                return (4); /* nothing to choose from */

        if (menup->attr & CKONEFLAG) {
                if (((n = menup->nchoices) <= 1) && menup->invis) {
                        for (i = 0; menup->invis[i]; ++i)
                                n++;
                }
                if (n <= 1) {
                        if (menup->choice)
                                item[0] = menup->choice->token;
                        else if (menup->invis)
                                item[0] = menup->invis[0];
                        item[1] = NULL;
                        return (0);
                }
        }

        if (max < 1)
                max = menup->nchoices;

        if (!prompt)
                prompt = PROMPT;
        defhlp = setmsg(menup, 0);
        deferr = setmsg(menup, 1);

reprint:
        printmenu(menup);

start:
        if (n = getstr(strval, defstr, error, help, prompt)) {
                free(defhlp);
                free(deferr);
                return (n);
        }
        if (strcmp(strval, "??") == 0) {
                goto reprint;
        }
        if ((defstr) && (strcmp(strval, defstr) == 0)) {
                item[0] = defstr;
                item[1] = NULL;
        } else {
                list = match(menup, strval, (int)max);
                if (!list) {
                        puterror(stderr, deferr, (errmsg ? errmsg : error));
                        goto start;
                }
                for (i = 0; (i < max); i++)
                        item[i] = list[i];
                free(list);
        }
        free(defhlp);
        free(deferr);
        return (0);
}

static int
getnum(char *strval, int max, int *begin, int *end)
{
        int n;
        char *pt;

        *begin = *end = 0;
        pt = strval;
        for (;;) {
                if (*pt == '$') {
                        n = max;
                        pt++;
                } else {
                        n = (int)strtol(pt, &pt, 10);
                        if ((n <= 0) || (n > max))
                                return (1);
                }
                while (isspace((unsigned char)*pt))
                        pt++;

                if (!*begin && (*pt == '-')) {
                        *begin = n;
                        pt++;
                        while (isspace((unsigned char)*pt))
                                pt++;
                        continue;
                } else if (*pt) {
                        return (1); /* wasn't a number, or an invalid one */
                } else if (*begin) {
                        *end = n;
                        break;
                } else {
                        *begin = n;
                        break;
                }
        }
        if (!*end)
                *end = *begin;
        return ((*begin <= *end) ? 0 : 1);
}

static char **
match(CKMENU *menup, char *strval, int max)
{
        struct _choice_ *chp;
        char **choice;
        int begin, end;
        char *pt, *found;
        int i, len, nchoice;

        nchoice = 0;
        choice = calloc((size_t)max, sizeof (char *));

        do {
                if (pt = strpbrk(strval, " \t,")) {
                        do {
                                *pt++ = '\0';
                        } while (strchr(" \t,", *pt));
                }

                if (nchoice >= max) {
                        errmsg = TOOMANY;
                        return (NULL);
                }
                if (!(menup->attr & CKUNNUM) &&
                        isdigit((unsigned char)*strval)) {
                        if (getnum(strval, (int)menup->nchoices, &begin,
                            &end)) {
                                errmsg = BADNUM;
                                return (NULL);
                        }
                        chp = menup->choice;
                        for (i = 1; chp; i++) {
                                if ((i >= begin) && (i <= end)) {
                                        if (nchoice >= max) {
                                                errmsg = TOOMANY;
                                                return (NULL);
                                        }
                                        choice[nchoice++] = chp->token;
                                }
                                chp = chp->next;
                        }
                        continue;
                }

                found = NULL;
                chp = menup->choice;
                for (i = 0; chp; i++) {
                        len = (int)strlen(strval);
                        if (strncmp(chp->token, strval, (size_t)len) == 0) {
                                if (chp->token[len] == '\0') {
                                        found = chp->token;
                                        break;
                                } else if (found) {
                                        errmsg = NOTUNIQ;
                                        return (NULL); /* not unique */
                                }
                                found = chp->token;
                        }
                        chp = chp->next;
                }

                if (menup->invis) {
                        for (i = 0; menup->invis[i]; ++i) {
                                len = (int)strlen(strval);
                                if (strncmp(menup->invis[i], strval,
                                    (size_t)len) == 0) {
#if _3b2
                                        if (chp->token[len] == '\0') {
#else
                                        if (menup->invis[i][len] == '\0') {
#endif
                                                found = menup->invis[i];
                                                break;
                                        } else if (found) {
                                                errmsg = NOTUNIQ;
                                                return (NULL);
                                        }
                                        found = menup->invis[i];
                                }
                        }
                }
                if (found) {
                        choice[nchoice++] = found;
                        continue;
                }
                errmsg = NULL;
                return (NULL);
        } while (((strval = pt) != NULL) && *pt);
        return (choice);
}

int
setitem(CKMENU *menup, char *choice)
{
        struct _choice_ *chp;
        int n;
        char *pt;

        if (choice == NULL) {
                /* request to clear memory usage */
                chp = menup->choice;
                while (chp) {
                        struct _choice_ *_chp = chp;

                        chp = chp->next;
                        menup->longest = menup->nchoices = 0;

                        (void) free(_chp->token); /* free token and text */
                        (void) free(_chp);
                }
                return (1);
        }

        if ((chp = calloc(1, sizeof (struct _choice_))) == NULL)
                return (1);

        if ((pt = strdup(choice)) == NULL) {
                free(chp);
                return (1);
        }
        if (!*pt || isspace((unsigned char)*pt)) {
                free(chp);
                return (2);
        }

        chp->token = strtoki(pt, " \t\n");
        chp->text = strtoki(NULL, "");

        if (chp->text) {
            while (isspace((unsigned char)*chp->text))
                chp->text++;
        }
        n = (int)strlen(chp->token);
        if (n > menup->longest)
                menup->longest = (short)n;

        if (insert(chp, menup))
                menup->nchoices++;
        else
                free(chp); /* duplicate entry */
        return (0);
}

int
setinvis(CKMENU *menup, char *choice)
{
        int     index;

        index = 0;
        if (choice == NULL) {
                if (menup->invis == NULL)
                        return (0);
                while (menup->invis[index])
                        free(menup->invis[index]);
                free(menup->invis);
                return (0);
        }

        if (menup->invis == NULL)
                menup->invis = calloc(2, sizeof (char *));
        else {
                while (menup->invis[index])
                        index++; /* count invisible choices */
                menup->invis = realloc(menup->invis,
                        (index+2)* sizeof (char *));
                menup->invis[index+1] = NULL;
        }
        if (!menup->invis)
                return (-1);
        menup->invis[index] = strdup(choice);
        return (0);
}

static int
insert(struct _choice_ *chp, CKMENU *menup)
{
        struct _choice_ *last, *base;
        int n;

        base = menup->choice;
        last = NULL;

        if (!(menup->attr & CKALPHA)) {
                while (base) {
                        if (strcmp(base->token, chp->token) == 0)
                                return (0);
                        last = base;
                        base = base->next;
                }
                if (last)
                        last->next = chp;
                else
                        menup->choice = chp;
                return (1);
        }

        while (base) {
                if ((n = strcmp(base->token, chp->token)) == 0)
                        return (0);
                if (n > 0) {
                        /* should come before this one */
                        break;
                }
                last = base;
                base = base->next;
        }
        if (last) {
                chp->next = last->next;
                last->next = chp;
        } else {
                chp->next = menup->choice;
                menup->choice = chp;
        }
        return (1);
}

void
printmenu(CKMENU *menup)
{
        int i;
        struct _choice_ *chp;
        char *pt;
        char format[16];
        int c;

        (void) fputc('\n', stderr);
        if (menup->label) {
                (void) puttext(stderr, menup->label, 0, 0);
                (void) fputc('\n', stderr);
        }
        (void) sprintf(format, "%%-%ds", menup->longest+5);

        (void) next(NULL);
        chp = ((menup->attr & CKALPHA) ? next(menup->choice) : menup->choice);
        for (i = 1; chp; ++i) {
                if (!(menup->attr & CKUNNUM))
                        (void) fprintf(stderr, "%3d  ", i);
                /* LINTED E_SEC_PRINTF_VAR_FMT */
                (void) fprintf(stderr, format, chp->token);
                if (chp->text) {
                        /* there is text associated with the token */
                        pt = chp->text;
                        while (*pt) {
                                (void) fputc(*pt, stderr);
                                if (*pt++ == '\n') {
                                        if (!(menup->attr & CKUNNUM))
                                                (void) fprintf(stderr,
                                                    "%5s", "");
                                        /* LINTED E_SEC_PRINTF_VAR_FMT */
                                        (void) fprintf(stderr, format, "");
                                        while (isspace((unsigned char)*pt))
                                                ++pt;
                                }
                        }
                }
                (void) fputc('\n', stderr);
                chp = ((menup->attr & CKALPHA) ?
                        next(menup->choice) : chp->next);
                if (chp && ((i % 10) == 0)) {
                        /* page the choices */
                        (void) fprintf(stderr,
                            "\n... %d more menu choices to follow;",
                            menup->nchoices - i);
                        (void) fprintf(stderr,
                            /* CSTYLED */
                            "\n<RETURN> for more choices, <CTRL-D> to stop \
display:");
                        /* ignore other chars */
                        while (((c = getc(stdin)) != EOF) && (c != '\n'))
                                ;
                        (void) fputc('\n', stderr);
                        if (c == EOF)
                                break; /* stop printing menu */
                }
        }
}

static int
getstr(char *strval, char *defstr, char *error, char *help, char *prompt)
{
        char input[MAX_INPUT];
        char end[MAX_INPUT];

        *end = '\0';
        if (defstr) {
                (void) snprintf(end, MAX_INPUT, "(default: %s) ", defstr);
        }
        if (ckquit) {
                (void) strlcat(end, "[?,??,q]", MAX_INPUT);
        } else {
                (void) strlcat(end, "[?,??]", MAX_INPUT);
        }

start:
        (void) fputc('\n', stderr);
        (void) puttext(stderr, prompt, 0, 0);
        (void) fprintf(stderr, " %s: ", end);

        if (getinput(input))
                return (1);

        if (strlen(input) == 0) {
                if (defstr) {
                        (void) strcpy(strval, defstr);
                        return (0);
                }
                puterror(stderr, deferr, (errmsg ? errmsg : error));
                goto start;
        } else if (strcmp(input, "?") == 0) {
                puthelp(stderr, defhlp, help);
                goto start;
        } else if (ckquit && (strcmp(input, "q") == 0)) {
                /* (void) strcpy(strval, input); */
                return (3);
        }
        (void) strcpy(strval, input);
        return (0);
}

static struct _choice_ *
next(struct _choice_ *chp)
{
        static char *last;
        static char *first;
        struct _choice_ *found;

        if (!chp) {
                last = NULL;
                return (NULL);
        }

        found = NULL;
        for (first = NULL; chp; chp = chp->next) {
                if (last && strcmp(last, chp->token) >= 0)
                        continue; /* lower than the last one we found */

                if (!first || strcmp(first, chp->token) > 0) {
                        first = chp->token;
                        found = chp;
                }
        }
        last = first;
        return (found);
}

static char *
strtoki(char *string, char *sepset)
{
        char    *p, *q, *r;
        static char     *savept;

        /* first or subsequent call */
        p = (string == NULL)? savept: string;

        if (p == NULL)          /* return if no tokens remaining */
                return (NULL);

        q = p + strspn(p, sepset);      /* skip leading separators */

        if (*q == '\0')         /* return if no tokens remaining */
                return (NULL);

        if ((r = strpbrk(q, sepset)) == NULL)   /* move past token */
                savept = 0;     /* indicate this is last token */
        else {
                *r = '\0';
                savept = ++r;
        }
        return (q);
}