/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#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)
{
char 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);
}