
/*
 * arglist.c -- Routines for handling argument lists for execvp() and friends
 *
 * This code is Copyright (c) 2013, by the authors of nmh.  See the
 * COPYRIGHT file in the root directory of the nmh distribution for
 * complete copyright information.
 */

#include <h/mh.h>
#include <h/utils.h>

/*
 * Split up a command into an appropriate array to pass to execvp()
 * or similar function.  Returns an allocated argv[] array.
 *
 * Function arguments:
 *
 * command - String to split up
 * file    - the first argument to "command", suitable for the first argument
 *           to execvp().  Returns allocated memory that must be free()d.
 * argp    - Index to last element (NULL) of returned argv[] array.
 *
 * Our basic algorithm is this:
 *
 * - If there are no spaces or shell metacharacters in "command", then
 *   take it as-is.
 * - If there are spaces in command, space-split command string.
 * - If we have shell metacharacters, run the command using
 *   /bin/sh -c 'command "$@"'.  In this case, any additional arguments
 *   appended to the arglist will be expanded by "$@".
 *
 * In all cases additional arguments can be added to the argv[] array.
 */

/* Shell metacharacters we use to trigger a call to the shell */

#define METACHARS	"$&*(){}[]'\";\\|?<>~`\n"

char **
argsplit(char *command, char **file, int *argp)
{
    char **argvarray, *p;
    int space = 0, metachar = 0, i;

    for (p = command; *p; p++) {
    	if (*p == ' ' || *p == '\t') {
		space = 1;
	} else if (strchr(METACHARS, *p)) {
		metachar = 1;
		break;
	}
    }

    argvarray = (char **) mh_xmalloc((sizeof(char **) * (MAXARGS + 5)));

    /*
     * The simple case - no spaces or shell metacharacters
     */

    if (!space && !metachar) {
    	argvarray[0] = getcpy(r1bindex(command, '/'));
	argvarray[1] = NULL;
	*file = getcpy(command);
	if (argp)
	    *argp = 1;
	return argvarray;
    }

    /*
     * Spaces, but no shell metacharacters; space-split into seperate
     * arguments
     */

    if (space && !metachar) {
    	char **split;
	p = getcpy(command);
	split = brkstring(p, " \t", NULL);
	if (split[0] == NULL) {
	    adios(NULL, "Invalid blank command found");
	}
	argvarray[0] = getcpy(r1bindex(split[0], '/'));
	for (i = 1; split[i] != NULL; i++) {
	    if (i > MAXARGS) {
		adios(NULL, "Command exceeded argument limit");
	    }
	    argvarray[i] = getcpy(split[i]);
	}
	argvarray[i] = NULL;
	*file = getcpy(split[0]);
	if (argp)
	    *argp = i;
	free(p);
	return argvarray;
    }

    /*
     * Remaining option - pass to the shell.
     *
     * Some notes here:
     *
     * - The command passed to "sh -c" is actually:
     *      command "$@"
     *
     *   If there are additional arguments they will be expanded by the
     *   shell, otherwise "$@" expands to nothing.
     *
     * - Every argument after the -c string gets put into positional
     *   parameters starting at $0, but $@ starts expanding with $1.
     *   So we put in a dummy argument (we just use /bin/sh)
     */

    *file = getcpy("/bin/sh");
    argvarray[0] = getcpy("sh");
    argvarray[1] = getcpy("-c");
    argvarray[2] = getcpy(command);
    argvarray[2] = add(" \"$@\"", argvarray[2]);
    argvarray[3] = getcpy("/bin/sh");
    argvarray[4] = NULL;

    if (argp)
    	*argp = 4;

    return argvarray;
}

/*
 * Free our argument array
 */

void
arglist_free(char *command, char **argvarray)
{
    int i;

    if (command != NULL)
    	free(command);

    if (argvarray != NULL) {
    	for (i = 0; argvarray[i] != NULL; i++)
	    free(argvarray[i]);
	free(argvarray);
    }
}

/*
 * Similar in functionality to argsplit, but is designed to deal with
 * a msgs_array.
 */

void
argsplit_msgarg(struct msgs_array *msgs, char *command, char **program)
{
    int argp, i;
    char **vec;

    vec = argsplit(command, program, &argp);

    /*
     * As usual, there is lousy memory management in nmh.  Nothing ever
     * free's the msgs_array, and a lot of the arguments are allocated
     * from static memory.  I could have app_msgarg() allocate new
     * memory for each pointer, but for now I decided to stick with
     * the existing interface; maybe that will be revisited later.
     * So we'll just copy over our pointers and free the pointer list
     * (not the actual pointers themselves).  Note that we don't
     * include a trailing NULL, since we are expecting the application
     * to take care of that.
     */

    for (i = 0; i < argp; i++) {
    	app_msgarg(msgs, vec[i]);
    }

    free(vec);
}

/*
 * Insert a arglist vector into the beginning of an struct msgs array
 *
 * Uses by some programs (e.g., show) who want to decide which proc
 * to use after the argument vector has been constructed
 */

#ifndef MAXMSGS
#define MAXMSGS 256
#endif

void
argsplit_insert(struct msgs_array *msgs, char *command, char **program)
{
    int argp, i;
    char **vec;

    vec = argsplit(command, program, &argp);

    /*
     * Okay, we want to shuffle all of our arguments down so we have room
     * for argp number of arguments.  This means we need to know about
     * msgs_array internals.  If that changes, we need to change this
     * code here.
     */

    if (msgs->size + argp >= msgs->max) {
    	msgs->max += MAXMSGS > argp ? MAXMSGS : argp;
	msgs->msgs = mh_xrealloc(msgs->msgs, msgs->max * sizeof(*msgs->msgs));
    }

    for (i = msgs->size - 1; i >= 0; i--)
    	msgs->msgs[i + argp] = msgs->msgs[i];

    msgs->size += argp;

    /*
     * Now fill in the arguments at the beginning of the vector.
     */

    for (i = 0; i < argp; i++)
    	msgs->msgs[i] = vec[i];

    free(vec);
}
