root/usr/src/cmd/idmap/idmap/idmap_engine.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <locale.h>
#include <ctype.h>
#ifdef WITH_LIBTECLA
#include <libtecla.h>
#endif
#include "idmap_engine.h"

/* The maximal line length. Longer lines may not be parsed OK. */
#define MAX_CMD_LINE_SZ 1023

#ifdef WITH_LIBTECLA
#define MAX_HISTORY_LINES 1023
static GetLine * gl_h;
/* LINTED E_STATIC_UNUSED */
#endif

/* Array for arguments of the actuall command */
static char ** my_argv;
/* Allocated size for my_argv */
static int my_argv_size = 16;
/* Actuall length of my_argv */
static int my_argc;

/* Array for subcommands */
static cmd_ops_t *my_comv;
/* my_comc length */
static int my_comc;

/* Input filename specified by the -f flag */
static char *my_filename;

/*
 * Batch mode means reading file, stdin or libtecla input. Shell input is
 * a non-batch mode.
 */
static int my_batch_mode;

/* Array of all possible flags */
static flag_t flags[FLAG_ALPHABET_SIZE];

/* getopt variables */
extern char *optarg;
extern int optind, optopt, opterr;

/* Fill the flags array: */
static int
options_parse(int argc, char *argv[], const char *options)
{
        int c;

        optind = 1;

        while ((c = getopt(argc, argv, options)) != EOF) {
                switch (c) {
                case '?':
                        return (-1);
                case ':':
        /* This is relevant only if options starts with ':': */
                        (void) fprintf(stderr,
                            gettext("Option %s: missing parameter\n"),
                            argv[optind - 1]);
                        return (-1);
                default:
                        if (optarg == NULL)
                                flags[c] = FLAG_SET;
                        else
                                flags[c] = optarg;

                }
        }
        return (optind);
}

/* Unset all flags */
static void
options_clean()
{
        (void) memset(flags, 0, FLAG_ALPHABET_SIZE * sizeof (flag_t));
}

/* determine which subcommand is argv[0] and execute its handler */
static int
run_command(int argc, char **argv, cmd_pos_t *pos)
{
        int i;

        if (argc == 0) {
                if (my_batch_mode)
                        return (0);
                return (-1);
        }
        for (i = 0; i < my_comc; i++) {
                int optind;
                int rc;

                if (strcmp(my_comv[i].cmd, argv[0]) != 0)
                        continue;

                /* We found it. Now execute the handler. */
                options_clean();
                optind = options_parse(argc, argv, my_comv[i].options);
                if (optind < 0) {
                        return (-1);
                }

                rc = my_comv[i].p_do_func(flags,
                    argc - optind,
                    argv + optind,
                    pos);

                return (rc);
        }

        (void) fprintf(stderr, gettext("Unknown command %s\n"),
            argv[0]);

        return (-1);

}

/*
 * Read another parameter from "from", up to a space char (unless it
 * is quoted). Duplicate it to "to". Remove quotation, if any.
 */
static int
get_param(char **to, const char *from)
{
        int to_i, from_i;
        char c;
        int last_slash = 0;     /* Preceded by a slash? */
        int in_string = 0;      /* Inside quites? */
        int is_param = 0;
        size_t buf_size = 20;   /* initial length of the buffer. */
        char *buf = (char *)malloc(buf_size * sizeof (char));

        from_i = 0;
        while (isspace(from[from_i]))
                from_i++;

        for (to_i = 0; '\0' != from[from_i]; from_i++) {
                c = from[from_i];

                if (to_i >= buf_size - 1) {
                        buf_size *= 2;
                        buf = (char *)realloc(buf, buf_size * sizeof (char));
                }

                if (c == '"' && !last_slash) {
                        in_string = !in_string;
                        is_param = 1;
                        continue;

                } else if (c == '\\' && !last_slash) {
                        last_slash = 1;
                        continue;

                } else if (!last_slash && !in_string && isspace(c)) {
                        break;
                }

                buf[to_i++] = from[from_i];
                last_slash = 0;

        }

        if (to_i == 0 && !is_param) {
                free(buf);
                *to = NULL;
                return (0);
        }

        buf[to_i] = '\0';
        *to = buf;

        if (in_string)
                return (-1);

        return (from_i);
}

/*
 * Split a string to a parameter array and append it to the specified position
 * of the array
 */
static int
line2array(const char *line)
{
        const char *cur;
        char *param;
        int len;

        for (cur = line; len = get_param(&param, cur); cur += len) {
                if (my_argc >= my_argv_size) {
                        my_argv_size *= 2;
                        my_argv = (char **)realloc(my_argv,
                            my_argv_size * sizeof (char *));
                }

                my_argv[my_argc] = param;
                ++my_argc;

                /* quotation not closed */
                if (len < 0)
                        return (-1);

        }
        return (0);

}

/* Clean all aruments from my_argv. Don't deallocate my_argv itself. */
static void
my_argv_clean()
{
        int i;
        for (i = 0; i < my_argc; i++) {
                free(my_argv[i]);
                my_argv[i] = NULL;
        }
        my_argc = 0;
}


#ifdef WITH_LIBTECLA
/* This is libtecla tab completion. */
static
CPL_MATCH_FN(command_complete)
{
        /*
         * WordCompletion *cpl; const char *line; int word_end are
         * passed from the CPL_MATCH_FN macro.
         */
        int i;
        char *prefix;
        int prefix_l;

        /* We go on even if quotation is not closed */
        (void) line2array(line);


        /* Beginning of the line: */
        if (my_argc == 0) {
                for (i = 0; i < my_comc; i++)
                        (void) cpl_add_completion(cpl, line, word_end,
                            word_end, my_comv[i].cmd, "", " ");
                goto cleanup;
        }

        /* Is there something to complete? */
        if (isspace(line[word_end - 1]))
                goto cleanup;

        prefix = my_argv[my_argc - 1];
        prefix_l = strlen(prefix);

        /* Subcommand name: */
        if (my_argc == 1) {
                for (i = 0; i < my_comc; i++)
                        if (strncmp(prefix, my_comv[i].cmd, prefix_l) == 0)
                                (void) cpl_add_completion(cpl, line,
                                    word_end - prefix_l,
                                    word_end, my_comv[i].cmd + prefix_l,
                                    "", " ");
                goto cleanup;
        }

        /* Long options: */
        if (prefix[0] == '-' && prefix [1] == '-') {
                char *options2 = NULL;
                char *paren;
                char *thesis;
                int i;

                for (i = 0; i < my_comc; i++)
                        if (0 == strcmp(my_comv[i].cmd, my_argv[0])) {
                                options2 = strdup(my_comv[i].options);
                                break;
                        }

                /* No such subcommand, or not enough memory: */
                if (options2 == NULL)
                        goto cleanup;

                for (paren = strchr(options2, '(');
                    paren && ((thesis = strchr(paren + 1, ')')) != NULL);
                    paren = strchr(thesis + 1, '(')) {
                /* Short option or thesis must precede, so this is safe: */
                        *(paren - 1) = '-';
                        *paren = '-';
                        *thesis = '\0';
                        if (strncmp(paren - 1, prefix, prefix_l) == 0) {
                                (void) cpl_add_completion(cpl, line,
                                    word_end - prefix_l,
                                    word_end, paren - 1 + prefix_l, "", " ");
                        }
                }
                free(options2);

                /* "--" is a valid completion */
                if (prefix_l == 2) {
                        (void) cpl_add_completion(cpl, line,
                            word_end - 2,
                            word_end, "", "", " ");
                }

        }

cleanup:
        my_argv_clean();
        return (0);
}

/* libtecla subshell: */
static int
interactive_interp()
{
        int rc = 0;
        char *prompt;
        const char *line;

        (void) sigset(SIGINT, SIG_IGN);

        gl_h = new_GetLine(MAX_CMD_LINE_SZ, MAX_HISTORY_LINES);

        if (gl_h == NULL) {
                (void) fprintf(stderr,
                    gettext("Error reading terminal: %s.\n"),
                    gl_error_message(gl_h, NULL, 0));
                return (-1);
        }

        (void) gl_customize_completion(gl_h, NULL, command_complete);

        for (;;) {
new_line:
                my_argv_clean();
                prompt = "> ";
continue_line:
                line = gl_get_line(gl_h, prompt, NULL, -1);

                if (line == NULL) {
                        switch (gl_return_status(gl_h)) {
                        case GLR_SIGNAL:
                                gl_abandon_line(gl_h);
                                goto new_line;

                        case GLR_EOF:
                                (void) line2array("exit");
                                break;

                        case GLR_ERROR:
                                (void) fprintf(stderr,
                                    gettext("Error reading terminal: %s.\n"),
                                    gl_error_message(gl_h, NULL, 0));
                                rc = -1;
                                goto end_of_input;
                        default:
                                (void) fprintf(stderr, "Internal error.\n");
                                exit(1);
                        }
                } else {
                        if (line2array(line) < 0) {
                                (void) fprintf(stderr,
                                    gettext("Quotation not closed\n"));
                                goto new_line;
                        }
                        if (my_argc == 0) {
                                goto new_line;
                        }
                        if (strcmp(my_argv[my_argc-1], "\n") == 0) {
                                my_argc--;
                                free(my_argv[my_argc]);
                                (void) strcpy(prompt, "> ");
                                goto continue_line;
                        }
                }

                rc = run_command(my_argc, my_argv, NULL);

                if (strcmp(my_argv[0], "exit") == 0 && rc == 0) {
                        break;
                }

        }

end_of_input:
        gl_h = del_GetLine(gl_h);
        my_argv_clean();
        return (rc);
}
#endif

/* Interpretation of a source file given by "name" */
static int
source_interp(const char *name)
{
        FILE *f;
        int is_stdin;
        int rc = -1;
        char line[MAX_CMD_LINE_SZ];
        cmd_pos_t pos;

        if (name == NULL || strcmp("-", name) == 0) {
                f = stdin;
                is_stdin = 1;
        } else {
                is_stdin = 0;
                f = fopen(name, "r");
                if (f == NULL) {
                        perror(name);
                        return (-1);
                }
        }

        pos.linenum = 0;
        pos.line = line;

        while (fgets(line, MAX_CMD_LINE_SZ, f)) {
                pos.linenum ++;

                if (line2array(line) < 0) {
                        (void) fprintf(stderr,
                            gettext("Quotation not closed\n"));
                        my_argv_clean();
                        continue;
                }

                /* We do not wan't "\n" as the last parameter */
                if (my_argc != 0 && strcmp(my_argv[my_argc-1], "\n") == 0) {
                        my_argc--;
                        free(my_argv[my_argc]);
                        continue;
                }

                if (my_argc != 0 && strcmp(my_argv[0], "exit") == 0) {
                        rc = 0;
                        my_argv_clean();
                        break;
                }

                rc = run_command(my_argc, my_argv, &pos);
                my_argv_clean();
        }

        if (my_argc > 0) {
                (void) fprintf(stderr, gettext("Line continuation missing\n"));
                rc = 1;
                my_argv_clean();
        }

        if (!is_stdin)
                (void) fclose(f);

        return (rc);
}

/*
 * Initialize the engine.
 * comc, comv is the array of subcommands and its length,
 * argc, argv are arguments to main to be scanned for -f filename and
 *    the length og the array,
 * is_batch_mode passes to the caller the information if the
 *    batch mode is on.
 *
 * Return values:
 * 0: ... OK
 * IDMAP_ENG_ERROR: error and message printed already
 * IDMAP_ENG_ERROR_SILENT: error and message needs to be printed
 *
 */

int
engine_init(int comc, cmd_ops_t *comv, int argc, char **argv,
    int *is_batch_mode)
{
        int c;

        my_comc = comc;
        my_comv = comv;

        my_argc = 0;
        my_argv = (char **)calloc(my_argv_size, sizeof (char *));

        if (argc < 1) {
                my_filename = NULL;
                if (isatty(fileno(stdin))) {
#ifdef WITH_LIBTECLA
                        my_batch_mode = 1;
#else
                        my_batch_mode = 0;
                        return (IDMAP_ENG_ERROR_SILENT);
#endif
                } else
                        my_batch_mode = 1;

                goto the_end;
        }

        my_batch_mode = 0;

        optind = 0;
        while ((c = getopt(argc, argv,
            "f:(command-file)")) != EOF) {
                switch (c) {
                case '?':
                        return (IDMAP_ENG_ERROR);
                case 'f':
                        my_batch_mode = 1;
                        my_filename = optarg;
                        break;
                default:
                        (void) fprintf(stderr, "Internal error.\n");
                        exit(1);
                }
        }

the_end:

        if (is_batch_mode != NULL)
                *is_batch_mode = my_batch_mode;
        return (0);
}

/* finitialize the engine */
int
engine_fini()
{
        my_argv_clean();
        free(my_argv);
        return (0);
}

/*
 * Interpret the subcommands defined by the arguments, unless
 * my_batch_mode was set on in egnine_init.
 */
int
run_engine(int argc, char **argv)
{
        int rc = -1;

        if (my_batch_mode) {
#ifdef WITH_LIBTECLA
                if (isatty(fileno(stdin)))
                        rc = interactive_interp();
                else
#endif
                        rc = source_interp(my_filename);
                goto cleanup;
        }

        rc = run_command(argc, argv, NULL);

cleanup:
        return (rc);
}