root/usr/src/lib/krb5/ss/listen.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Listener loop for subsystem library libss.a.
 *
 *      util/ss/listen.c
 *
 * Copyright 1987, 1988 by MIT Student Information Processing Board
 *
 * For copyright information, see copyright.h.
 */

#include "copyright.h"
#include "ss_internal.h"
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <termios.h>
#include <libintl.h>
#include <sys/param.h>
/* Solaris Kerberos */
#include <libtecla.h>

#define MAX_LINE_LEN BUFSIZ
#define MAX_HIST_LEN 8192

static ss_data *current_info;
static jmp_buf listen_jmpb;

static RETSIGTYPE print_prompt()
{
    struct termios termbuf;

    if (tcgetattr(STDIN_FILENO, &termbuf) == 0) {
        termbuf.c_lflag |= ICANON|ISIG|ECHO;
        tcsetattr(STDIN_FILENO, TCSANOW, &termbuf);
    }
    (void) fputs(current_info->prompt, stdout);
    (void) fflush(stdout);
}

static RETSIGTYPE listen_int_handler(signo)
    int signo;
{
    putc('\n', stdout);
    longjmp(listen_jmpb, 1);
}
/* Solaris Kerberos */
typedef struct _ss_commands {
        int sci_idx;
        const char **cmd;
        unsigned int count;
} ss_commands;

/*
 * Solaris Kerberos
 * get_commands fills out a ss_commands structure with pointers
 * to the top-level commands (char*) that a program supports.
 * count reflects the number of commands cmd holds. Memory must
 * be allocated by the caller.
 */
void get_commands(ss_commands *commands) {
        const char * const *cmd;
        ss_request_table **table;
        ss_request_entry *request;
        ss_data *info;

        commands->count = 0;

        info = ss_info(commands->sci_idx);
        for (table = info->rqt_tables; *table; table++) {
                for (request = (*table)->requests;
                    request->command_names != NULL; request++) {
                        for (cmd = request->command_names;
                            cmd != NULL && *cmd != NULL; cmd++) {
                                if (commands->cmd != NULL)
                                        commands->cmd[commands->count] = *cmd;
                                commands->count++;
                        }
                }
        }
}

/*
 * Solaris Kerberos
 * Match function used by libtecla for tab-completion.
 */
CPL_MATCH_FN(cmdmatch) {
        int argc, len, ws, i;
        char **argv, *l;
        ss_commands *commands = data;
        int ret = 0;

        /* Dup the line as ss_parse will modify the string */
        l = strdup(line);
        if (l == NULL)
                return (ret);

        /* Tab-completion may happen in the middle of a line */
        if (word_end != strlen(l))
                l[word_end] = '\0';

        if (ss_parse(commands->sci_idx, l, &argc, &argv, 1)) {
                free (l);
                return (ret);
        }

        /* Don't bother if the arg count is not 1 or 0 */
        if (argc < 2) {
                len = argc ? strlen(argv[0]) : 0;
                ws = word_end - len;

                for (i = 0; i < commands->count; i++) {
                        if (strncmp(commands->cmd[i], line + ws, len) == 0) {
                                ret = cpl_add_completion(cpl, line, ws,
                                    word_end, commands->cmd[i] + len, "", " ");
                                if (ret)
                                        break;
                        }
                }
        }

        free(argv);
        free(l);
        return (ret);
}

int ss_listen (sci_idx)
    int sci_idx;
{
    register char *cp;
    register ss_data *info;
    char buffer[BUFSIZ];
    char *volatile end = buffer;
    int code;

    /* Solaris Kerberos */
    char *input;
    GetLine *gl;
    GlReturnStatus ret;
    ss_commands commands;

    jmp_buf old_jmpb;
    ss_data *old_info = current_info;
#ifdef POSIX_SIGNALS
    struct sigaction isig, csig, nsig, osig;
    sigset_t nmask, omask;
#else
    register RETSIGTYPE (*sig_cont)();
    RETSIGTYPE (*sig_int)(), (*old_sig_cont)();
    int mask;
#endif

    current_info = info = ss_info(sci_idx);
    info->abort = 0;

    /* Solaris Kerberos */
    gl = new_GetLine(MAX_LINE_LEN, MAX_HIST_LEN);
    if (gl == NULL) {
        ss_error(sci_idx, 0, dgettext(TEXT_DOMAIN,
            "new_GetLine() failed.\n"));
        current_info = old_info;
        return (SS_ET_TECLA_ERR);
    }

    commands.sci_idx = sci_idx;
    commands.cmd = NULL;

    /* Find out how many commands there are */
    get_commands(&commands);

    /* Alloc space for them */
    commands.cmd = malloc(sizeof (char *) * commands.count);
    if (commands.cmd == NULL) {
        current_info = old_info;
        gl = del_GetLine(gl);
        return (ENOMEM);
    }

    /* Fill-in commands.cmd */
    get_commands(&commands);

    if (gl_customize_completion(gl, &commands, cmdmatch) != 0 ) {
        ss_error(sci_idx, 0, dgettext(TEXT_DOMAIN,
            "failed to register completion function.\n"));
        free(commands.cmd);
        current_info = old_info;
        gl = del_GetLine(gl);
        return (SS_ET_TECLA_ERR);
    }

#ifdef POSIX_SIGNALS
    csig.sa_handler = (RETSIGTYPE (*)())0;
    sigemptyset(&nmask);
    sigaddset(&nmask, SIGINT);
    sigprocmask(SIG_BLOCK, &nmask, &omask);
#else
    sig_cont = (RETSIGTYPE (*)())0;
    mask = sigblock(sigmask(SIGINT));
#endif

    memcpy(old_jmpb, listen_jmpb, sizeof(jmp_buf));

#ifdef POSIX_SIGNALS
    nsig.sa_handler = listen_int_handler;
    sigemptyset(&nsig.sa_mask);
    nsig.sa_flags = 0;
    sigaction(SIGINT, &nsig, &isig);
#else
    sig_int = signal(SIGINT, listen_int_handler);
#endif

    setjmp(listen_jmpb);

#ifdef POSIX_SIGNALS
    sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
#else
    (void) sigsetmask(mask);
#endif

    /*
     * Solaris Kerberos:
     * Let libtecla deal with SIGINT when it's doing its own processing
     * otherwise the input line won't be cleared on SIGINT.
     */
    if (gl_trap_signal(gl, SIGINT, GLS_DONT_FORWARD, GLS_ABORT, 0)) {
        ss_error(sci_idx, 0, dgettext(TEXT_DOMAIN,
            "Failed to trap SIGINT.\n"));
        code = SS_ET_TECLA_ERR;
        goto egress;
    }

    while(!info->abort) {
        print_prompt();
        *end = '\0';
#ifdef POSIX_SIGNALS
        nsig.sa_handler = listen_int_handler;   /* fgets is not signal-safe */
        osig = csig;
        sigaction(SIGCONT, &nsig, &csig);
        if ((RETSIGTYPE (*)())csig.sa_handler==(RETSIGTYPE (*)())listen_int_handler)
            csig = osig;
#else
        old_sig_cont = sig_cont;
        sig_cont = signal(SIGCONT, print_prompt);
        if (sig_cont == print_prompt)
            sig_cont = old_sig_cont;
#endif

        /* Solaris Kerberos */
        input = gl_get_line(gl, info->prompt, NULL, -1);
        ret = gl_return_status(gl);

        switch (ret) {
            case (GLR_SIGNAL):
                gl_abandon_line(gl);
                continue;
            case (GLR_EOF):
                info->abort = 1;
                continue;
            case (GLR_ERROR):
                ss_error(sci_idx, 0, dgettext(TEXT_DOMAIN,
                    "Failed to read line: %s\n"), gl_error_message(gl, NULL, 0));
                info->abort = 1;
                code = SS_ET_TECLA_ERR;
                goto egress;
        }
        cp = strchr(input, '\n');
        if (cp) {
            *cp = '\0';
            if (cp == input)
                continue;
        }
#ifdef POSIX_SIGNALS
        sigaction(SIGCONT, &csig, (struct sigaction *)0);
#else
        (void) signal(SIGCONT, sig_cont);
#endif
        for (end = input; *end; end++)
            ;

        code = ss_execute_line (sci_idx, input);
        if (code == SS_ET_COMMAND_NOT_FOUND) {
            register char *c = input;
            while (*c == ' ' || *c == '\t')
                c++;
            cp = strchr (c, ' ');
            if (cp)
                *cp = '\0';
            cp = strchr (c, '\t');
            if (cp)
                *cp = '\0';
            ss_error (sci_idx, 0, dgettext(TEXT_DOMAIN,
                    "Unknown request \"%s\".  Type \"?\" for a request list."),
                       c);
        }
    }
    code = 0;
egress:

    /* Solaris Kerberos */
    free(commands.cmd);
    gl = del_GetLine(gl);

#ifdef POSIX_SIGNALS
    sigaction(SIGINT, &isig, (struct sigaction *)0);
#else
    (void) signal(SIGINT, sig_int);
#endif
    memcpy(listen_jmpb, old_jmpb, sizeof(jmp_buf));
    current_info = old_info;
    return code;
}

void ss_abort_subsystem(sci_idx, code)
    int sci_idx;
    int code;
{
    ss_info(sci_idx)->abort = 1;
    ss_info(sci_idx)->exit_status = code;
}

void ss_quit(argc, argv, sci_idx, infop)
    int argc;
    char const * const *argv;
    int sci_idx;
    pointer infop;
{
    ss_abort_subsystem(sci_idx, 0);
}