
/*
 *  $Id: agFunOut.c,v 2.8 1999/07/07 19:30:51 bkorb Exp $
 *
 *  This module implements the output file manipulation function
 */

/*
 *  AutoGen copyright 1992-1999 Bruce Korb
 *
 *  AutoGen is free software.
 *  You may redistribute it and/or modify it under the terms of the
 *  GNU General Public License, as published by the Free Software
 *  Foundation; either version 2, or (at your option) any later version.
 *
 *  AutoGen is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with AutoGen.  See the file "COPYING".  If not,
 *  write to:  The Free Software Foundation, Inc.,
 *             59 Temple Place - Suite 330,
 *             Boston,  MA  02111-1307, USA.
 */

#include <time.h>
#include <utime.h>

#include "autogen.h"

#define OUTFUN_TABLE                            \
    _OF_( DELETE )                              \
    _OF_( MOVE )                                \
    _OF_( POP )                                 \
    _OF_( PUSH_ADD )                            \
    _OF_( PUSH_NEW )                            \
    _OF_( SWITCH )

#define _OF_(n) OFUNC_ ## n,
typedef enum { OUTFUN_TABLE FUNCTION_COUNT } teOutFunc;
#undef _OF_

#define _OF_(n) tSCC zFunc_ ##n[] = #n;
OUTFUN_TABLE
#undef _OF_

#define _OF_(n) zFunc_ ##n,
tSCC*  apzOutFuncName[] = { OUTFUN_TABLE };
#undef _OF_

#ifndef S_IAMB
/*
 *  Access Mask Bits (3 special plus RWX for User Group & Others (9))
 */
#  define S_IAMB      (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)
#endif

    void
removeWriteAccess( int fd )
{
    struct stat    sbuf;
    /*
     *  Set our usage mask to all all the access
     *  bits that do not provide for write access
     */
#   define USE_MASK ~(S_IWUSR|S_IWGRP|S_IWOTH)
    fstat( fd, &sbuf );

    /*
     *  Mask off the write permission bits, but ensure that
     *  the user read bit is set.
     */
    sbuf.st_mode = (sbuf.st_mode & USE_MASK) | S_IRUSR;
    fchmod( fd, sbuf.st_mode & S_IAMB );
}


    STATIC void
addWriteAccess( char* pzFileName )
{
    struct stat    sbuf;
    stat( pzFileName, &sbuf );
    /*
     *  Or in the user write bit
     */
    sbuf.st_mode = sbuf.st_mode | S_IWUSR;
    chmod( pzFileName, sbuf.st_mode & S_IAMB );
}


    STATIC teOutFunc
getOutputFunc( const char* pzName )
{
    tSCC zBad[]       = "ERROR:  output function `%s' is \n";
    tSCC zAmbiguous[] = "ambiguous";
    tSCC zUnknown[]   = "unknown";

    teOutFunc func = (teOutFunc)0;
    teOutFunc save = (teOutFunc)0;
    size_t    len  = strlen( pzName );
    int       mCt  = 0;

    /*
     *  Look through all the functions for a maximal match
     */
    do  {
        if (streqvcmp( pzName, apzOutFuncName[ func ]) == 0) {
            /*
             *  For an exact match, return now.
             */
            if (len == strlen( apzOutFuncName[ func ]))
                return func;

            /*
             *  Too many partial matches?
             *  Bail out on error
             */
            if (mCt++ > 0) {
                fprintf( stderr, zBad, pzName, zAmbiguous );
                return FUNCTION_COUNT;
            }

            /*
             *  remember for later
             */
            save = func;
        }

        /*
         *  Try the next function code
         */
        func = (teOutFunc)((int)func + 1);
    } while (func < FUNCTION_COUNT);

    /*
     *  IF we found exactly one partial match,
     *  THEN return the match.
     */
    if (mCt == 1)
        return save;

    fprintf( stderr, zBad, pzName, zUnknown );
    return FUNCTION_COUNT;
}


/*=macfunc OUTPUT
 *
 *  what:   Alter the output file
 *
 *  cindex: output file
 *
 *  desc:
 *
 *  Change the name of the output file.  First argument describes function
 *  and the second the name of the new file.
 *
 *  @table @samp
 *  @findex Delete
 *  @item Delete
 *  Remove the current output file.  Cease processing the template for
 *  the current suffix.  It is an error if there are @code{push}-ed
 *  output files.  Use the @code{[#_ERROR 0#]} function instead.
 *
 *  @findex Switch
 *  @item Switch
 *  Close current output, open new output file
 *
 *  @findex Move
 *  @item Move
 *  Rename current output file.
 *
 *  @findex Push-new
 *  @item Push-new
 *  Leave the current output file open, but purge and create
 *  a new file that will remain open until a @code{pop} @code{delete}
 *  or @code{switch} closes it.
 *
 *  @findex Push-add
 *  @item Push-add
 *  Identical to @code{push-new}, except the contents are @strong{not}
 *  purged, but appended to.
 *
 *  @findex Pop
 *  @item Pop
 *  If there has been a @code{push} on the output, then close that
 *  file and go back to the previously open file.  It is an error
 *  if there has not been a @code{push}.  The file name argument may be
 *  omitted and is ignored.
 *  @end table
=*/
    tMacro*
mFunc_Output( tMacro* pM, tDefEntry* pCurDef )
{
    tSCC zBadEval[] = "%s ERROR: Output function and file names invalid\n";

    ag_bool    freeFunc   = AG_FALSE;
    ag_bool    freeName   = AG_FALSE;
    char*      pzFunc;
    char*      pzName     = (char*)NULL;
    teOutFunc  func;

    switch (pM->tknCt) {
    case 0:
        return pM+1;

    case 2:
        pzName = pM->ppTkns[1];
        /* FALLTHROUGH */

    case 1:
        pzFunc = pM->ppTkns[0];
        break;

    default:
        /*
         *  IF we have some sort of expression to evaluate,
         *  THEN do that now.  Make sure the result is a string.
         */
        switch ( eval( pM, pCurDef )) {
        default:
        case VT_NONE:
        case VT_VALUE:
            fprintf( stderr, zBadEval, pzProg );
            evalError( pM );
            /* NOTREACHED */

        case VT_ALLOC_STR:
            freeFunc = AG_TRUE;
            /* FALLTHROUGH */

        case VT_STRING:
            pzFunc = (char*)pM->evalRes;
            pzName = (char*)NULL;
            break;

        case VT_STACK:
        {
            tValStack* pVS = (tValStack*)pM->evalRes;

            switch (pVS[0].valType) {
            default:
                fprintf( stderr, zBadEval, pzProg );
                evalError( pM );
            case VT_ALLOC_STR:
                freeFunc = AG_TRUE;
                /* FALLTHROUGH */
            case VT_STRING:
                pzFunc = pVS[0].val.pz;
            }

            switch (pVS[1].valType) {
            default:
                fprintf( stderr, zBadEval, pzProg );
                evalError( pM );

            case VT_NONE:
                pzFunc = (char*)NULL;
                break;

            case VT_ALLOC_STR:
                freeName = AG_TRUE;
                /* FALLTHROUGH */

            case VT_STRING:
                pzName = pVS[1].val.pz;

                if (pVS[2].valType != VT_NONE) {
                    fprintf( stderr, zBadEval, pzProg );
                    evalError( pM );
                }
            }

            AGFREE( (void*)pVS );
        }
        }
    }

    func = getOutputFunc( pzFunc );

    switch (func) {
    default:
        longjmp( fileAbort, FAILURE );
        /* NOTREACHED */

    case OFUNC_DELETE:
    {
        tSCC zSkipMsg[] = "NOTE:  skipping file '%s'\n";
        /*
         *  Delete the current output file
         */
        fprintf( stderr, zSkipMsg, pCurFp->pzName );
        longjmp( fileAbort, PROBLEM );
        /* NOTREACHED */
    }

    case OFUNC_MOVE:
        /*
         *  Move (rename) the current output file - do not switch file ptrs
         */
        if (pzName == (char*)NULL)
            break;

        rename( pCurFp->pzName, pzName );
        if (freeName) {
            pCurFp->pzName = pzName;
            freeName = AG_FALSE;
        } else AGDUPSTR( pCurFp->pzName, pzName );
        break;

    case OFUNC_SWITCH:
    {
        /*
         *  Switch output files - close current file and make the current
         *  file pointer refer to the new file
         */
        struct utimbuf tbuf;

        /*
         *  IF no change, THEN ignore this
         */
        if (  (pzName == (char*)NULL)
           || (strcmp( pCurFp->pzName, pzName ) == 0) )
            break;

        unlink( pzName );
        removeWriteAccess( fileno( pCurFp->pFile ));

        /*
         *  Make sure we get a new file pointer!!
         */
        if (   freopen( pzName, "w" FOPEN_TEXT_FLAG, pCurFp->pFile )
            != pCurFp->pFile) {
            tSCC zOpen[] = "%s ERROR %d (%s): cannot open %s\n";
            fprintf( stderr, zOpen, pzProg,
                     errno, strerror( errno ), pM->ppTkns );
            longjmp( fileAbort, FAILURE );
        }

        /*
         *  Set the mod time on the old file.
         */
        tbuf.actime  = time( (time_t*)NULL );
        tbuf.modtime = outTime;
        utime( pCurFp->pzName, &tbuf );
        strcpy( pCurFp->pzName, pzName );
        break;
    }

    case OFUNC_POP:
        if (pCurFp->pPrev == (tFpStack*)NULL) {
            fputs( "ERROR:  Cannot pop output with no output pushed\n",
                   stderr );
            longjmp( fileAbort, PROBLEM );
        }
        closeOutput( AG_FALSE );
        break;

    case OFUNC_PUSH_ADD:
    case OFUNC_PUSH_NEW:
    {
        tFpStack* p;
        if (pzName == (char*)NULL)
            break;

        p = (tFpStack*)AGALOC( sizeof( tFpStack ));
        if (p == (tFpStack*)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     sizeof( tFpStack ), "file ptr stack entry" );
            longjmp( fileAbort, FAILURE );
        }

        if (freeName)
             freeName = AG_FALSE;
        else AGDUPSTR( pzName, pzName );

        p->pPrev  = pCurFp;
        p->pzName = pzName;
        if (func == OFUNC_PUSH_ADD) {
            addWriteAccess( p->pzName );
            p->pFile  = fopen( pzName, "a" FOPEN_TEXT_FLAG );
        } else {
            unlink( p->pzName );
            p->pFile  = fopen( pzName, "w" FOPEN_TEXT_FLAG );
        }

        pCurFp = p;
    }
    }

    if (freeName)
        AGFREE( (void*)pzName );

    if (freeFunc)
        AGFREE( (void*)pzFunc );

    return pM+1;
}
/* end of agFunOut.c */
