Subversion Repositories DevTools

Rev

Rev 313 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/* -*- mode: c; tabs: 4; -*- ************************************************
* Module name   : cmdfile.c
* Module type   : CMDFILE source file
* Environment(s): n/a
*
* Description:
*
    CMDFILE --- command line file builder
*
* Version   Who     Date        Description
            APY     12/99       Created
            APY     14/03/00    When '-o' switch, use temp output and
                                    rename on success ...
                                Additional command line help
                                v1.1
            APY     16/04/02    realpath macro
            APY     08/12/03    - MRI kludges (k2 and r?) (v1.2)
                                - CMDFILE env variable support
                    26/04/04    - shortpath
                                - macro argument single and double quoted
                                    strings processing.
                                - realpath2
                                - url2path
                                - path2url
                                - 1.21
                    05/05/04    - 'w', 'd' and 'm' switches
                                - 1.22
                    10/05/04    - handle leading white space within paths.
                                - 1.23
                    17/05/04    - vlibgcc
                                - 1.24
                    23/06/04    - Whitespace handling -W switch
                                - 1.25
                    23/06/04    - Fixed macro buffer overflow
            DP      23/09/04    - vpath() generates more readable error
                                    messages when a library file cannot be
                                    found in vpath.
                                - 1.26
            APY     05/10/04    vpath3, vlib3 and vglob3
                                Path arguments support embedded spaces.
                                Extended usage
                                @vhost and @vsep
                                @vlint
                                - 1.30
*
* $Source: /cvsroot/device/DEVL/UTILS/CMDFILE/cmdfile.c,v $
* $Revision: 1.11 $ $Date: 2004/11/05 07:51:39 $ $State: Exp $
* $Author: ayoung $ $Locker:  $
*.........................................................................*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include "cmdfile.h"
#include "longopt.h"

#define NEWLINE     -2
#define SEPERATOR   -3

const char *        version_string      = "1.31";
char *              program_name        = "";

char                tmp_buf[15]         = TMPNAME;
char *              tmp_name            = tmp_buf;
int                 tmp_fd              = -1;
FILE *              output              = NULL;

char                mflg                = '@';
char                dflg[2]             = { '(', ')' };

int                 cflg                = 0;
int                 eflg                = 1;
int                 kflg                = 0;
int                 k2flg               = 0;
int                 Mflg                = 0;
int                 nflg                = 1;
int                 nlflg               = 0;
const char *        oflg                = NULL;
int                 rflg                = 0;
char                rtags[10]           = { 0 };
int                 tflg                = 0;
int                 vflg                = 0;
int                 wflg                = 0;
int                 whitespace          = WS_UNKNOWN;
int                 nl = FALSE;

static int          linelen = 0;
static char         linebuffer[ 32*1024 ];      /* 32k line buffer */


static void         PUTC(int c);
static void         PUTS(const char *s);
static void         FLUSH(void);

static int          inline_realpath(char *line, int *length);
static void         do_padkludge(void);
static void         usage(int status);
static void         cleanup(void);


/* Print the words in LIST to standard output.  If the first word
    *   is '-n', then don't print a trailing newline.  We also
    *   support the echo syntax from Version 9 unix systems.
    */
int
#if defined(WIN32)
__cdecl
#endif
main(int argc, char **argv)
{
    output = stdout;

    if ((program_name = strrchr(argv[0], '/')) == NULL &&
            (program_name = strrchr(argv[0], '\\')) == NULL)
        program_name = argv[0];
    else program_name++;

#if defined(WIN32) && (0)
    rflg = -1;                                  /* MRI kludge (v2)*/
    strcpy(rtags, "IJ");
#endif

/* Handle command file */
    if (argc == 2 && argv[1][0] == '@')
    {
        struct  stat ast;
        int     afd, len;
        char    *args;
        char    **nargv;
        int     nargc;
        char    quotechar = '\0';
        char    *p;

        nargc = 1;
#if defined(MSDOS) || defined(WIN32)
        if ((afd = open(argv[1]+1, O_RDONLY|O_BINARY)) < 0)
#else
        if ((afd = open(argv[1]+1, O_RDONLY)) < 0)
#endif
            fatalerr("Cannot open \"%s\"", argv[1]+1);
        fstat(afd, &ast);
        if ((args = (char *)malloc(ast.st_size + 1)) == NULL)
            fatalerr("Cannot allocate read buffer");
        args[ ast.st_size ] = '\0';
        do {
            if ((len = read(afd, args, ast.st_size)) < 0)
                fatalerr("Failed to read %s", argv[1]+1);
        } while ((ast.st_size -= len) > 0);

        close(afd);
        for (p = args; *p; p++)
        {
            if (quotechar)
            {
                if (quotechar == '\\' ||
                            (*p == quotechar && p[-1] != '\\'))
                        quotechar = '\0';
                    continue;
            }
            switch (*p) {
            case '\\':
            case '"':
            case '\'':
                quotechar = *p;
                break;
            case ' ':
            case '\n':
               *p = '\0';
               if (p > args && p[-1])
                  nargc++;
               break;
            case '\r':
               *p = '\0';
               if (p > args && p[-1])
                  nargc++;
               if (p[1] == '\n')
                  *++p = '\0';
               break;
            }
        }
        if (p[-1])
            nargc++;
        nargv = (char **)malloc(nargc * sizeof(char *));
        nargv[0] = argv[0];
        argc = 1;
        for (p = args; argc < nargc; p += strlen(p) + 1)
            if (*p)
                nargv[argc++] = p;
        argv = nargv;
    }

/* Long options (specials),
    *   --help
    *   --version
    */
    parse_long_options( argc, argv, "cmdfile", version_string, usage );
    argc--, argv++;

/* Import CMDFILE environment variable */
    if (getenv("CMDFILE"))
    {
printf( "PARSE ENV OPTS\n" );
        parse_short_argument( getenv("CMDFILE" ) );
    }

/* Standard options (only allowed as first command) */
    if (argc > 0 && *argv[0] == '-')
        if (parse_short_argument( argv[0]+1 ) > 0)
        {
            if (oflg)
            {
                if ((tmp_fd = mkstemp( tmp_name )) == -1)
                    fatalerr( "Cannot build temp file name \"%s\" : %d (%s)",
                        tmp_name, errno, strerror(errno));

                atexit( cleanup );              /* temp name cleanup */

                if ((output = fdopen( tmp_fd, "wb+" )) == NULL)
                    fatalerr( "Cannot fdopen \"%s\" : %d (%s)",
                        tmp_name, errno, strerror(errno));
            }

            argc--, argv++;                     /* remove argument */
        }

    if (vflg > 2) 
    {
        verbose( "Options:" );
        if (cflg)   verbose( "  Redirecting errors to stdout." );
        if (nflg)   verbose( "  No trailing newline" );
        if (nlflg)  verbose( "  Newline seperator" );
        if (wflg)   verbose( "  Consuming leading whitespace" );
        if (!Mflg)  verbose( "  Procesing macros" );
        if (!eflg)  verbose( "  Not processing escape sequences" );
        if (eflg)   verbose( "  Level %d escape sequence processing", eflg );
    }

/* Process remaining */
    if (argc > 0)
    {
        int argi;
        nl = FALSE;

        for (argi = 0; argc > 0; argi++)
        {
            if (vflg > 2)
                verbose( "ARGV[%d]%s", argi, argv[0] );

            process_string ( argv[0] );

            argc--, argv++;                     /* next argument */

            if (argc > 0 && nl == FALSE)
            {                                   /* seperator */
                PUTC(SEPERATOR);
                if (nlflg) nl = TRUE;
            }
        }
    }

    if (nflg)
        PUTC(NEWLINE);

    FLUSH();

/* Rename 'temp' */
    if (oflg)
    {
        if (k2flg)
            do_padkludge();

        fclose(output);
        tmp_fd = -1;

        (void) remove(oflg);
        verbose( " renaming(%s to %s)", tmp_name, oflg );
        if (rename(tmp_name, oflg) == -1)
            fatalerr("Cannot rename temp file \"%s\" to \"%s\" : %d (%s)",
                tmp_name, oflg, errno, strerror(errno));
        tmp_name[0] = '\0';                     /* stop cleanup of tmp_name */
    }
    return (0);
}

/*----------------------------------------------------------------------------
** FUNCTION           : process_string
**
** DESCRIPTION        : Process a string
**                      It may be from an argument, it may be from a macro
**
**
** INPUTS             : s               - String to process
**
** RETURNS            : Nothing
**
----------------------------------------------------------------------------*/

void process_string (register char *s )
{
    register int c = '\0';

    while ((c = *s++) != '\0')
    {
        /*
         *  Escape processing ...
         */
        if (eflg && c == '\\' && *s)
        {
            switch (c = *s++)
            {
            case '\n':                  /* concat line */
                nl = TRUE, c = '\0';
                break;
            case 'n':                   /* \n */
                c = NEWLINE;
                break;
            case '\\':                  /* backslash */
                break;
            default:
                if (eflg >= 2) {        /* extended */
                    switch (c)
                    {
                    case 'a': c = '\007'; break;
                    case 'b': c = '\b'; break;
                    case 'c': nflg = 0; break;
                    case 'f': c = '\f'; break;
                    case 'r': c = '\r'; break;
                    case 't': c = '\t'; break;
                    case 'v': c = (int) 0x0B; break;
                    case '0': case '1': case '2': case '3':
                    case '4': case '5': case '6': case '7':
                        c -= '0';
                        if (*s >= '0' && *s <= '7')
                            c = c * 8 + (*s++ - '0');
                        if (*s >= '0' && *s <= '7')
                            c = c * 8 + (*s++ - '0');
                        break;
                    default:
                        PUTC('\\');     /* unknown echo */
                        break;
                    }
                } else {
                    PUTC('\\');         /* unknown echo */
                }
                break;
            }
        }

        /*
         *  Macros ...
         */
        else if (Mflg == 0 && c == MACROCHAR && *s)
        {
            const char *str;
            String_t     mstr;

            StringInit( &mstr, NULL, MACRO_MAX );       /* macro resource */
            s += macro(&mstr, s);
            if ((str = StringData(&mstr)) != NULL && str[0]) {
                PUTS(str);
                nl = FALSE;
            }
            free( StringData(&mstr) );
            c = '\0';
        }

        /*
         *  Default output ...
         */
        if (c != '\0')
        {
            if (nl && (c == ' ' || c == '\t') && wflg)
                continue;               /* eat leading white space */

            if (c == '\n' || c == NEWLINE)
                nl = TRUE;
            else nl = FALSE;

            PUTC(c);                    /* output character */
        }
    }
    
}

static void
__PUTC(int c)
{
    int     flushit = 0;

    if (c == NEWLINE || c == '\r' || c == '\n') /* flush on newline */
        flushit = 1;

    if (flushit && linelen > 2)
        if (rflg && linebuffer[0] == '-')       /* inline realpath */
        {
            inline_realpath( linebuffer, &linelen );
        }

    if (c == NEWLINE) {
        if (kflg)
            linebuffer[ linelen++ ] = '\r';
        linebuffer[ linelen++ ] = '\n';
    } else {
        linebuffer[ linelen++ ] = c;            /* append character */
    }

    if (flushit)
        FLUSH();
}


static void
PUTC(int c)
{
    if (vflg > 2) {
        if (c == NEWLINE)
            verbose( "PUTC[NEWLINE]" );
        else if (c == SEPERATOR)
            verbose( "PUTC[SEPERATOR]" );
        else {
            verbose( "PUTC[]%c", c );
        }
    }
    if (c == SEPERATOR)
        c = (nlflg ? NEWLINE : ' ');
    __PUTC(c);
}


static void
PUTS(const char *s)
{
    if (vflg > 2)
        verbose( "PUTS[]%s", s );
    while (*s)
        __PUTC(*s++);
}


static void
FLUSH(void)
{
    if (linelen)
    {
        register const char *p = linebuffer;

        linebuffer[ linelen ] = '\0';
        while (*p)                              /* flush line buffer */
            putc( *p++, output );
        linelen = 0;
    }
}


static int
inline_realpath(char *line, int *length)
{
    const   char *s = line;
    char    buf[ PATH_MAX ], *pbuf;
    int     space = 0;
    char    tag, sep;
    int     i;

    sep = *s++;
    tag = *s++;
    if (strchr(rtags, tag) == 0)                /* known tag? */
        return (0);

    while (s[0] == ' ' || s[0] == '\t' )
        space++, s++;

    line[ *length ] = '\0';                     /* terminate line */

    if (s[0] == '\0' ||
            Realpath(s, buf) == NULL)           /* expand */
        return (-1);

    verbose( " inline realpath(%s) = '-%c%s'", s, tag, buf );

    i = 0;
    line[ i++ ] = sep;
    line[ i++ ] = tag;
    if (space)
        line[ i++ ] = ' ';
    for (pbuf = buf; (line[ i++ ] = *pbuf) != '\0'; pbuf++)
        /*continue*/;

    *length = i-1;

    return (i);
}


static void
do_padkludge(void)
{
    long    flen, pad = 0;

    fflush(output);
    flen = ftell(output);
    if (flen > 440 && flen < 500)
        pad = 540 - flen;
    else if (flen > 740 && flen < 800)
        pad = 840 - flen;

    if (pad)
    {
        char    *image;                         /* current file image */

        verbose(" padding output (%d --> %d)", flen, flen+pad );

        if ((image = (void *)calloc(flen, 1)) == NULL)
            fatalerr("Memory allocation error");
        if (fseek(output, 0L, SEEK_SET) == -1 ||
                (int)fread(image, sizeof(char), flen, output) != flen)
            fatalerr("reading : %d (%s)", errno, strerror(errno));

        fseek(output, 0L, SEEK_SET);
        fputs("-DPADKLUDGE", output);
        if ((pad -= (12 + (kflg ? 2 : 1))) > 0)
        {
            fputs("=", output);
            while (pad-- > 0)
                fputc( "1234567890"[pad%10], output);
        }
        if (kflg)
            fputc('\r', output);
        fputc('\n', output);

        if ((int)fwrite(image, sizeof(char), flen, output) != flen)
            fatalerr("rewriting : %d (%s)", errno, strerror(errno));
        free(image);
    }
}


static void
usage(int status)
{
    if (status != 0) {
        fprintf(stderr, "Try `%s --help' for more information.\n",
            program_name);
        exit(status);
    }

printf("Usage: %s [-OPTION[OPTION]]... [STRING]...\n", program_name);
printf("Usage: %s @cmdfile\n", program_name);
printf("\n\
Options, all must be encoded as the first argument.\n\
\n\
  -n                   Do not output the trailing newline.\n\
  -N                   Use newline as argument seperator (default space).\n\
  -m?                  Alternative macro identifier (eg -m$, default '@').\n\
  -d?                  Alternative macro delimitor set (eg -m[, default '(').\n\
                        '[' implies the delimitor set of \"[]\", '(' = \"()\",\n\
                        '{' = \"{}\" and '<' implies \"<>\".\n\
  -k|-k1               DOS lf/cr kludge.\n\
  -k2                  MRI file padding Kludge.\n\
  -w                   Trim leading white after newlines.\n\
  -W{method}           Method of handling embedded whitespace within paths.\n\
    0,-                 - Disable warnings.\n\
    1,e                 - Escape (eg /Embedded\\ Space/x.h).\n\
    2,q                 - Quote the entire path (eg \"/Embedded Space/x.h\").\n\
  -r?                  Realpath expansion kludge (eg -rI, expands -Ipath)\n\
  -v                   Verbose output.\n\
  -c                   Redirect errors to stdout.\n\
  -E                   Disable interpolation of some sequences in STRINGs.\n\
  -e                   Enable extended interpolation.\n\
  -M                   Disable macros.\n\
  -t                   Dont remove temporary working files.\n\
  -o<file>             Output file (must be the last option).\n");
printf("\n\
\n\
The follow options must be specified alone;\n\
\n\
  --help               Display this help and exit\n\
  --version            Output version information and exit\n");
printf("\n\
Without -E, the following sequences are recognized and interpolated:\n\
\n\
  \\n                   New line.\n\
  \\\\                   Backslash.\n");
printf("\n\
If the -e option is given, the following additional sequences are\n\
recognized and interpolated:\n\
\n\
  \\a                   Alert (bell).\n\
  \\b                   Backspace.\n\
  \\c                   Suppress trailing newline (same as -n).\n\
  \\f                   Form feed.\n\
  \\r                   Carriage return.\n\
  \\t                   Horizontal tab.\n\
  \\v                   Vertical tab.\n\
  \\NNN                 The character whose ASCII code is NNN (octal).\n");
printf("\n\
Without -M, the following macros are recognized and executed:\n\
\n\
  @(env,var)           Get text from named EnvVar\n\
  @(envmacro,var)      Process text from named EnvVar\n\
  @(dosify,path)       Convert the specify path to DOS conventions (8.3)\n\
  @(realpath,path)     Determine the absolute path, removing . and ..\n\
                       and resolving relative references\n\
  @(shortpath,path)    Convert the path into a short path (WIN32 specific).\n\
  @(vhost,ident)       Override the default host (Unix or WIN32).\n\
  @(vsep,ident)        Override the default path sep(colon or semicolon).\n\
  @(sep,c,text)        Use path element sep characacter and clean text.\n\
  @(vlibgcc,path)      Invoke the specific gcc version to determine the\n\
                       default library search directories.\n\
  @(vpath[23],n,path,[sep])  Resolve 'name' using search 'path'.\n\
  @(vlib[23],n,path,[sep])   Resolve library 'name' using the search 'path',\n\
  @(vlint[23],n,path,[sep])  Resolve lint resource 'n' using the search 'path'.\n\
  @(vglob[23],p,path,[sep])  Match the pattern 'p' using the search 'path'.\n\
\n\
The vpath, vlib, vglob and vlint macros have 3 forms that determine the\n\
handling of a \"not found\" condition.\n\
\n\
  1 (default)          Search name/pattern is quoted as specified.\n\
  2                    Fatal exit, stating target name/pattern not found.\n\
  3                    Outputs nothing.\n\
\n\
The vpath, vlib, vglob and vlint macros take an optional 3rd argument\n\
This is a single character that will be used to seperate the elements\n\
in the output path. Normally determined from the content 'path' argument.\n\
\n");

#if defined(DEFUNC)
printf("\n\
  @(url2path,url)      Convert the URL encoded path to a native path.\n\
  @(path2url,url)      Convert the path using URL encoding rules.\n\
\n");
#endif

    exit (status);
}

void
message(const char *msg, ...)
{
    va_list args;
    FILE    *out = cflg ? stderr : stdout;

    va_start(args, msg);
    vfprintf(out, msg, args);
    va_end(args);
}


void
fatalerr(const char *msg, ...)
{
    va_list args;
    FILE    *out = cflg ? stderr : stdout;

    fprintf(out, "%s: *** Error: ", program_name);
    va_start(args, msg);
    vfprintf(out, msg, args);
    fprintf(out, ". Stop\n");
    va_end(args);
    exit(1);
}


void
warning(const char *msg, ...)
{
    va_list args;
    FILE    *out = cflg ? stderr : stdout;

    fprintf(out, "%s: *** Warning: ", program_name);
    va_start(args, msg);
    vfprintf(out, msg, args);
    fprintf(out, ".\n");
    va_end(args);
}


void
verbose(const char *msg, ...)
{
    va_list args;

    if (!vflg)
        return;

    va_start(args, msg);
    vprintf(msg, args);
    va_end(args);

    printf(".\n");
    if (vflg >= 2)
        fflush(stdout);
}


static void
cleanup(void)
{
    if (oflg && tmp_name[0])
    {
        verbose( " removing(%s)", tmp_name );
        if (tmp_fd != -1)
            close(tmp_fd);
        if (!tflg)
        remove(tmp_name);
    }
}