/*
 * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
 * Use is subject to license terms.
 *
 *      Copyright (c) 1984 AT&T
 *        All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "apr.h"
#include "apr_lib.h"
#include "libsed.h"
#include "sed.h"
#include "apr_strings.h"
#include "regexp.h"

static const char *const trans[040]  = {
    "\\01",
    "\\02",
    "\\03",
    "\\04",
    "\\05",
    "\\06",
    "\\07",
    "\\10",
    "\\11",
    "\n",
    "\\13",
    "\\14",
    "\\15",
    "\\16",
    "\\17",
    "\\20",
    "\\21",
    "\\22",
    "\\23",
    "\\24",
    "\\25",
    "\\26",
    "\\27",
    "\\30",
    "\\31",
    "\\32",
    "\\33",
    "\\34",
    "\\35",
    "\\36",
    "\\37"
};
static const char rub[] = {"\\177"};

extern int sed_step(char *p1, char *p2, int circf, step_vars_storage *vars);
static int substitute(sed_eval_t *eval, sed_reptr_t *ipc,
                      step_vars_storage *step_vars);
static apr_status_t execute(sed_eval_t *eval);
static int match(sed_eval_t *eval, char *expbuf, int gf,
                 step_vars_storage *step_vars);
static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
                          step_vars_storage *step_vars);
static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2);
static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
                            step_vars_storage *step_vars);
static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz);
static apr_status_t arout(sed_eval_t *eval);

static void eval_errf(sed_eval_t *eval, const char *fmt, ...)
{
    if (eval->errfn && eval->pool) {
        va_list args;
        const char* error;
        va_start(args, fmt);
        error = apr_pvsprintf(eval->pool, fmt, args);
        eval->errfn(eval->data, error);
        va_end(args);
    }
}

#define INIT_BUF_SIZE 1024
#define MAX_BUF_SIZE 1024*8192

/*
 * grow_buffer
 */
static apr_status_t grow_buffer(apr_pool_t *pool, char **buffer,
                        char **spend, apr_size_t *cursize,
                        apr_size_t newsize)
{
    char* newbuffer = NULL;
    apr_size_t spendsize = 0;
    if (*cursize >= newsize) {
        return APR_SUCCESS;
    }
    /* Avoid number of times realloc is called. It could cause huge memory
     * requirement if line size is huge e.g 2 MB */
    if (newsize < *cursize * 2) {
        newsize = *cursize * 2;
    }

    /* Align it to 4 KB boundary */
    newsize = (newsize  + ((1 << 12) - 1)) & ~((1 << 12) - 1);
    if (newsize > MAX_BUF_SIZE) { 
        return APR_ENOMEM;
    }
    newbuffer = apr_pcalloc(pool, newsize);
    if (*spend && *buffer && (*cursize > 0)) {
        spendsize = *spend - *buffer;
    }
    if ((*cursize > 0) && *buffer) {
        memcpy(newbuffer, *buffer, *cursize);
    }
    *buffer = newbuffer;
    *cursize = newsize;
    if (spend != buffer) {
        *spend = *buffer + spendsize;
    }
    return APR_SUCCESS;
}

/*
 * grow_line_buffer
 */
static apr_status_t grow_line_buffer(sed_eval_t *eval, apr_size_t newsize)
{
    return grow_buffer(eval->pool, &eval->linebuf, &eval->lspend,
                &eval->lsize, newsize);
}

/*
 * grow_hold_buffer
 */
static apr_status_t grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize)
{
    return grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend,
                &eval->hsize, newsize);
}

/*
 * grow_gen_buffer
 */
static apr_status_t grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize,
                            char **gspend)
{
    apr_status_t rc = 0;
    if (gspend == NULL) {
        gspend = &eval->genbuf;
    }
    rc = grow_buffer(eval->pool, &eval->genbuf, gspend,
                     &eval->gsize, newsize);
    if (rc == APR_SUCCESS) { 
        eval->lcomend = &eval->genbuf[71];
    }
    return rc;
}

/*
 * appendmem_to_linebuf
 */
static apr_status_t appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len)
{
    apr_status_t rc = 0;
    apr_size_t reqsize = (eval->lspend - eval->linebuf) + len;
    if (eval->lsize < reqsize) {
        rc = grow_line_buffer(eval, reqsize);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
    }
    memcpy(eval->lspend, sz, len);
    eval->lspend += len;
    return APR_SUCCESS;
}

/*
 * append_to_linebuf
 */
static apr_status_t append_to_linebuf(sed_eval_t *eval, const char* sz,
                              step_vars_storage *step_vars)
{
    apr_size_t len = strlen(sz);
    char *old_linebuf = eval->linebuf;
    apr_status_t rc = 0;
    /* Copy string including null character */
    rc = appendmem_to_linebuf(eval, sz, len + 1);
    if (rc != APR_SUCCESS) { 
        return rc;
    }
    --eval->lspend; /* lspend will now point to NULL character */
    /* Sync step_vars after a possible linebuf expansion */
    if (step_vars && old_linebuf != eval->linebuf) {
        if (step_vars->loc1) {
            step_vars->loc1 = step_vars->loc1 - old_linebuf + eval->linebuf;
        }
        if (step_vars->loc2) {
            step_vars->loc2 = step_vars->loc2 - old_linebuf + eval->linebuf;
        }
        if (step_vars->locs) {
            step_vars->locs = step_vars->locs - old_linebuf + eval->linebuf;
        }
    }
    return APR_SUCCESS;
}

/*
 * copy_to_linebuf
 */
static apr_status_t copy_to_linebuf(sed_eval_t *eval, const char* sz,
                            step_vars_storage *step_vars)
{
    eval->lspend = eval->linebuf;
    return append_to_linebuf(eval, sz, step_vars);
}

/*
 * append_to_holdbuf
 */
static apr_status_t append_to_holdbuf(sed_eval_t *eval, const char* sz)
{
    apr_size_t len = strlen(sz);
    apr_size_t reqsize = (eval->hspend - eval->holdbuf) + len + 1;
    apr_status_t rc = 0;
    if (eval->hsize <= reqsize) {
        rc = grow_hold_buffer(eval, reqsize);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
    }
    memcpy(eval->hspend, sz, len + 1);
    /* hspend will now point to NULL character */
    eval->hspend += len;
    return APR_SUCCESS;
}

/*
 * copy_to_holdbuf
 */
static apr_status_t copy_to_holdbuf(sed_eval_t *eval, const char* sz)
{
    eval->hspend = eval->holdbuf;
    return append_to_holdbuf(eval, sz);
}

/*
 * append_to_genbuf
 */
static apr_status_t append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend)
{
    apr_size_t len = strlen(sz);
    apr_size_t reqsize = (*gspend - eval->genbuf) + len + 1;
    apr_status_t rc = 0;
    if (eval->gsize < reqsize) {
        rc = grow_gen_buffer(eval, reqsize, gspend);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
    }
    memcpy(*gspend, sz, len + 1);
    /* *gspend will now point to NULL character */
    *gspend += len;
    return APR_SUCCESS;
}

/*
 * copy_to_genbuf
 */
static apr_status_t copy_to_genbuf(sed_eval_t *eval, const char* sz)
{
    apr_size_t len = strlen(sz);
    apr_size_t reqsize = len + 1;
    apr_status_t rc = APR_SUCCESS;;
    if (eval->gsize < reqsize) {
        rc = grow_gen_buffer(eval, reqsize, NULL);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
    }
    memcpy(eval->genbuf, sz, len + 1);
    return rc;
}

/*
 * sed_init_eval
 */
apr_status_t sed_init_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data, sed_write_fn_t *writefn, apr_pool_t* p)
{
    memset(eval, 0, sizeof(*eval));
    eval->pool = p;
    eval->writefn = writefn;
    return sed_reset_eval(eval, commands, errfn, data);
}

/*
 * sed_reset_eval
 */
apr_status_t sed_reset_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data)
{
    int i;

    eval->errfn = errfn;
    eval->data = data;

    eval->commands = commands;

    eval->lnum = 0;
    eval->fout = NULL;

    if (eval->linebuf == NULL) {
        eval->lsize = INIT_BUF_SIZE;
        eval->linebuf = apr_pcalloc(eval->pool, eval->lsize);
    }
    if (eval->holdbuf == NULL) {
        eval->hsize = INIT_BUF_SIZE;
        eval->holdbuf = apr_pcalloc(eval->pool, eval->hsize);
    }
    if (eval->genbuf == NULL) {
        eval->gsize = INIT_BUF_SIZE;
        eval->genbuf = apr_pcalloc(eval->pool, eval->gsize);
    }
    eval->lspend = eval->linebuf;
    eval->hspend = eval->holdbuf;
    eval->lcomend = &eval->genbuf[71];

    for (i = 0; i < sizeof(eval->abuf) / sizeof(eval->abuf[0]); i++)
        eval->abuf[i] = NULL;
    eval->aptr = eval->abuf;
    eval->pending = NULL;
    eval->inar = apr_pcalloc(eval->pool, commands->nrep * sizeof(unsigned char));
    eval->nrep = commands->nrep;

    eval->dolflag = 0;
    eval->sflag = 0;
    eval->jflag = 0;
    eval->delflag = 0;
    eval->lreadyflag = 0;
    eval->quitflag = 0;
    eval->finalflag = 1; /* assume we're evaluating only one file/stream */
    eval->numpass = 0;
    eval->nullmatch = 0;
    eval->col = 0;

    for (i = 0; i < commands->nfiles; i++) {
        const char* filename = commands->fname[i];
        if (apr_file_open(&eval->fcode[i], filename,
                          APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
                          eval->pool) != APR_SUCCESS) {
            eval_errf(eval, SEDERR_COMES, filename);
            return APR_EGENERAL;
        }
    }

    return APR_SUCCESS;
}

/*
 * sed_destroy_eval
 */
void sed_destroy_eval(sed_eval_t *eval)
{
    int i;
    /* eval->linebuf, eval->holdbuf, eval->genbuf and eval->inar are allocated
     * on pool. It will be freed when pool will be freed */
    for (i = 0; i < eval->commands->nfiles; i++) {
        if (eval->fcode[i] != NULL) {
            apr_file_close(eval->fcode[i]);
            eval->fcode[i] = NULL;
        }
    }
}

/*
 * sed_eval_file
 */
apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout)
{
    for (;;) {
        char buf[1024];
        apr_size_t read_bytes = 0;

        read_bytes = sizeof(buf);
        if (apr_file_read(fin, buf, &read_bytes) != APR_SUCCESS)
            break;

        if (sed_eval_buffer(eval, buf, read_bytes, fout) != APR_SUCCESS)
            return APR_EGENERAL;

        if (eval->quitflag)
            return APR_SUCCESS;
    }

    return sed_finalize_eval(eval, fout);
}

/*
 * sed_eval_buffer
 */
apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout)
{
    apr_status_t rv;

    if (eval->quitflag)
        return APR_SUCCESS;

    if (!sed_canbe_finalized(eval->commands)) {
        /* Commands were not finalized properly. */
        const char* error = sed_get_finalize_error(eval->commands, eval->pool);
        if (error) {
            eval_errf(eval, error);
            return APR_EGENERAL;
        }
    }

    eval->fout = fout;

    /* Process leftovers */
    if (bufsz && eval->lreadyflag) {
        eval->lreadyflag = 0;
        eval->lspend--;
        *eval->lspend = '\0';
        rv = execute(eval);
        if (rv != APR_SUCCESS)
            return rv;
    }

    while (bufsz) {
        apr_status_t rc = 0;
        char *n;
        apr_size_t llen;

        n = memchr(buf, '\n', bufsz);
        if (n == NULL)
            break;

        llen = n - buf;
        if (llen == bufsz - 1) {
            /* This might be the last line; delay its processing */
            eval->lreadyflag = 1;
            break;
        }

        rc = appendmem_to_linebuf(eval, buf, llen + 1);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
        --eval->lspend;
        /* replace new line character with NULL */
        *eval->lspend = '\0';
        buf += (llen + 1);
        bufsz -= (llen + 1);
        rv = execute(eval);
        if (rv != APR_SUCCESS)
            return rv;
        if (eval->quitflag)
            break;
    }

    /* Save the leftovers for later */
    if (bufsz) {
        apr_status_t rc = appendmem_to_linebuf(eval, buf, bufsz);
        if (rc != APR_SUCCESS) { 
            return rc;
        }
    }

    return APR_SUCCESS;
}

/*
 * sed_finalize_eval
 */
apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout)
{
    if (eval->quitflag)
        return APR_SUCCESS;

    if (eval->finalflag)
        eval->dolflag = 1;

    eval->fout = fout;

    /* Process leftovers */
    if (eval->lspend > eval->linebuf) {
        apr_status_t rv;
        apr_status_t rc = 0;

        if (eval->lreadyflag) {
            eval->lreadyflag = 0;
            eval->lspend--;
        } else {
            /* Code can probably reach here when last character in output
             * buffer is not a newline.
             */
            /* Assure space for NULL */
            rc = append_to_linebuf(eval, "", NULL);
            if (rc != APR_SUCCESS) { 
                return rc;
            }
        }

        *eval->lspend = '\0';
        rv = execute(eval);
        if (rv != APR_SUCCESS)
            return rv;
    }

    eval->quitflag = 1;

    return APR_SUCCESS;
}

/*
 * execute
 */
static apr_status_t execute(sed_eval_t *eval)
{
    sed_reptr_t *ipc = eval->commands->ptrspace;
    step_vars_storage step_vars;
    apr_status_t rv = APR_SUCCESS;

    eval->lnum++;

    eval->sflag = 0;

    if (eval->pending) {
        ipc = eval->pending;
        eval->pending = NULL;
    }

    memset(&step_vars, 0, sizeof(step_vars));

    while (ipc->command) {
        char *p1;
        char *p2;
        int c;

        p1 = ipc->ad1;
        p2 = ipc->ad2;

        if (p1) {

            if (eval->inar[ipc->nrep]) {
                if (*p2 == CEND) {
                    p1 = 0;
                } else if (*p2 == CLNUM) {
                    c = (unsigned char)p2[1];
                    if (eval->lnum > eval->commands->tlno[c]) {
                        eval->inar[ipc->nrep] = 0;
                        if (ipc->negfl)
                            goto yes;
                        ipc = ipc->next;
                        continue;
                    }
                    if (eval->lnum == eval->commands->tlno[c]) {
                        eval->inar[ipc->nrep] = 0;
                    }
                } else if (match(eval, p2, 0, &step_vars)) {
                    eval->inar[ipc->nrep] = 0;
                }
            } else if (*p1 == CEND) {
                if (!eval->dolflag) {
                    if (ipc->negfl)
                        goto yes;
                    ipc = ipc->next;
                    continue;
                }
            } else if (*p1 == CLNUM) {
                c = (unsigned char)p1[1];
                if (eval->lnum != eval->commands->tlno[c]) {
                    if (ipc->negfl)
                        goto yes;
                    ipc = ipc->next;
                    continue;
                }
                if (p2)
                    eval->inar[ipc->nrep] = 1;
            } else if (match(eval, p1, 0, &step_vars)) {
                if (p2)
                    eval->inar[ipc->nrep] = 1;
            } else {
                if (ipc->negfl)
                    goto yes;
                ipc = ipc->next;
                continue;
            }
        }

        if (ipc->negfl) {
            ipc = ipc->next;
            continue;
        }

yes:
        rv = command(eval, ipc, &step_vars);
        if (rv != APR_SUCCESS)
            return rv;

        if (eval->quitflag)
            return APR_SUCCESS;

        if (eval->pending)
            return APR_SUCCESS;

        if (eval->delflag)
            break;

        if (eval->jflag) {
            eval->jflag = 0;
            if ((ipc = ipc->lb1) == 0) {
                ipc = eval->commands->ptrspace;
                break;
            }
        } else
            ipc = ipc->next;
    }

    if (!eval->commands->nflag && !eval->delflag) {
        rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
        if (rv != APR_SUCCESS)
            return rv;
    }

    if (eval->aptr > eval->abuf)
        rv = arout(eval);

    eval->delflag = 0;

    eval->lspend = eval->linebuf;

    return rv;
}

/*
 * match
 */
static int match(sed_eval_t *eval, char *expbuf, int gf,
                 step_vars_storage *step_vars)
{
    char   *p1;
    int circf;

    if (gf) {
        if (*expbuf)    return(0);
        step_vars->locs = p1 = step_vars->loc2;
    } else {
        p1 = eval->linebuf;
        step_vars->locs = 0;
    }

    circf = *expbuf++;
    return(sed_step(p1, expbuf, circf, step_vars));
}

/*
 * substitute
 */
static int substitute(sed_eval_t *eval, sed_reptr_t *ipc,
                      step_vars_storage *step_vars)
{
    if (match(eval, ipc->re1, 0, step_vars) == 0)    return(0);

    eval->numpass = 0;
    eval->sflag = 0;        /* Flags if any substitution was made */
    if (dosub(eval, ipc->rhs, ipc->gfl, step_vars) != APR_SUCCESS)
        return -1;

    if (ipc->gfl) {
        while (*step_vars->loc2) {
            if (match(eval, ipc->re1, 1, step_vars) == 0) break;
            if (dosub(eval, ipc->rhs, ipc->gfl, step_vars) != APR_SUCCESS)
                return -1;
        }
    }
    return(eval->sflag);
}

/*
 * dosub
 */
static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
                          step_vars_storage *step_vars)
{
    char *lp, *sp, *rp;
    int c;
    apr_status_t rv = APR_SUCCESS;

    if (n > 0 && n < 999) {
        eval->numpass++;
        if (n != eval->numpass) return APR_SUCCESS;
    }
    eval->sflag = 1;
    lp = eval->linebuf;
    sp = eval->genbuf;
    rp = rhsbuf;
    sp = place(eval, sp, lp, step_vars->loc1);
    if (sp == NULL) { 
        return APR_EGENERAL;
    }
    while ((c = *rp++) != 0) {
        if (c == '&') {
            sp = place(eval, sp, step_vars->loc1, step_vars->loc2);
            if (sp == NULL) {
                return APR_EGENERAL;
            }
        }
        else if (c == '\\') {
            c = *rp++;
            if (c >= '1' && c < NBRA+'1') {
                sp = place(eval, sp, step_vars->braslist[c-'1'],
                           step_vars->braelist[c-'1']);
                if (sp == NULL)
                    return APR_EGENERAL;
            }
            else
                *sp++ = c;
          } else
            *sp++ = c;
        if (sp >= eval->genbuf + eval->gsize) {
            /* expand genbuf and set the sp appropriately */
            rv = grow_gen_buffer(eval, eval->gsize + 1024, &sp);
            if (rv != APR_SUCCESS) { 
                return rv;
            }
        }
    }
    lp = step_vars->loc2;
    step_vars->loc2 = sp - eval->genbuf + eval->linebuf;
    rv = append_to_genbuf(eval, lp, &sp);
    if (rv != APR_SUCCESS) { 
        return rv;
    }
    rv = copy_to_linebuf(eval, eval->genbuf, step_vars);
    return rv;
}

/*
 * place
 */
static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2)
{
    char *sp = asp;
    apr_size_t n = al2 - al1;
    apr_size_t reqsize = (sp - eval->genbuf) + n + 1;

    if (eval->gsize < reqsize) {
        apr_status_t rc = grow_gen_buffer(eval, reqsize, &sp);
        if (rc != APR_SUCCESS) { 
            return NULL;
        }
    }
    memcpy(sp, al1, n);
    return sp + n;
}

/*
 * command
 */
static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
                            step_vars_storage *step_vars)
{
    int    i;
    char   *p1, *p2;
    const char *p3;
    int length;
    char sz[32]; /* 32 bytes enough to store 64 bit integer in decimal */
    apr_status_t rv = APR_SUCCESS;


    switch(ipc->command) {

        case ACOM:
            if (eval->aptr >= &eval->abuf[SED_ABUFSIZE]) {
                eval_errf(eval, SEDERR_TMAMES, eval->lnum);
            } else {
                *eval->aptr++ = ipc;
                *eval->aptr = NULL;
            }
            break;

        case CCOM:
            eval->delflag = 1;
            if (!eval->inar[ipc->nrep] || eval->dolflag) {
                for (p1 = ipc->re1; *p1; p1++)
                    ;
                rv = wline(eval, ipc->re1, p1 - ipc->re1);
            }
            break;

        case DCOM:
            eval->delflag++;
            break;

        case CDCOM:
            p1 = eval->linebuf;

            while (*p1 != '\n') {
                if (*p1++ == 0) {
                    eval->delflag++;
                    return APR_SUCCESS;
                }
            }

            p1++;
            rv = copy_to_linebuf(eval, p1, step_vars);
            if (rv != APR_SUCCESS) return rv;
            eval->jflag++;
            break;

        case EQCOM:
            length = apr_snprintf(sz, sizeof(sz), "%d", (int) eval->lnum);
            rv = wline(eval, sz, length);
            break;

        case GCOM:
            rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
            if (rv != APR_SUCCESS) return rv;
            break;

        case CGCOM:
            rv = append_to_linebuf(eval, "\n", step_vars);
            if (rv != APR_SUCCESS) return rv;
            rv = append_to_linebuf(eval, eval->holdbuf, step_vars);
            if (rv != APR_SUCCESS) return rv;
            break;

        case HCOM:
            rv = copy_to_holdbuf(eval, eval->linebuf);
            if (rv != APR_SUCCESS) return rv;
            break;

        case CHCOM:
            rv = append_to_holdbuf(eval, "\n");
            if (rv != APR_SUCCESS) return rv;
            rv = append_to_holdbuf(eval, eval->linebuf);
            if (rv != APR_SUCCESS) return rv;
            break;

        case ICOM:
            for (p1 = ipc->re1; *p1; p1++);
            rv = wline(eval, ipc->re1, p1 - ipc->re1);
            break;

        case BCOM:
            eval->jflag = 1;
            break;

        case LCOM:
            p1 = eval->linebuf;
            p2 = eval->genbuf;
            eval->genbuf[72] = 0;
            while (*p1) {
                if ((unsigned char)*p1 >= 040) {
                    if (*p1 == 0177) {
                        p3 = rub;
                        while ((*p2++ = *p3++) != 0)
                            if (p2 >= eval->lcomend) {
                                *p2 = '\\';
                                rv = wline(eval, eval->genbuf,
                                           strlen(eval->genbuf));
                                if (rv != APR_SUCCESS)
                                    return rv;
                                p2 = eval->genbuf;
                            }
                        p2--;
                        p1++;
                        continue;
                    }
                    if (!isprint(*p1 & 0377)) {
                        *p2++ = '\\';
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                        *p2++ = (*p1 >> 6) + '0';
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                        *p2++ = ((*p1 >> 3) & 07) + '0';
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                        *p2++ = (*p1++ & 07) + '0';
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                    } else {
                        *p2++ = *p1++;
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                    }
                } else {
                    p3 = trans[(unsigned char)*p1-1];
                    while ((*p2++ = *p3++) != 0)
                        if (p2 >= eval->lcomend) {
                            *p2 = '\\';
                            rv = wline(eval, eval->genbuf,
                                       strlen(eval->genbuf));
                            if (rv != APR_SUCCESS)
                                return rv;
                            p2 = eval->genbuf;
                        }
                    p2--;
                    p1++;
                }
            }
            *p2 = 0;
            rv = wline(eval, eval->genbuf, strlen(eval->genbuf));
            break;

        case NCOM:
            if (!eval->commands->nflag) {
                rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
                if (rv != APR_SUCCESS)
                    return rv;
            }

            if (eval->aptr > eval->abuf) {
                rv = arout(eval);
                if (rv != APR_SUCCESS)
                    return rv;
            }
            eval->lspend = eval->linebuf;
            eval->pending = ipc->next;
            break;

        case CNCOM:
            if (eval->aptr > eval->abuf) {
                rv = arout(eval);
                if (rv != APR_SUCCESS)
                    return rv;
            }
            rv = append_to_linebuf(eval, "\n", step_vars);
            if (rv != APR_SUCCESS) return rv;
            eval->pending = ipc->next;
            break;

        case PCOM:
            rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
            break;

        case CPCOM:
            for (p1 = eval->linebuf; *p1 != '\n' && *p1 != '\0'; p1++);
            rv = wline(eval, eval->linebuf, p1 - eval->linebuf);
            break;

        case QCOM:
            if (!eval->commands->nflag) {
                rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
                if (rv != APR_SUCCESS)
                    break;
            }

            if (eval->aptr > eval->abuf) {
                rv = arout(eval);
                if (rv != APR_SUCCESS)
                    return rv;
            }

            eval->quitflag = 1;
            break;

        case RCOM:
            if (eval->aptr >= &eval->abuf[SED_ABUFSIZE]) {
                eval_errf(eval, SEDERR_TMRMES, eval->lnum);
            } else {
                *eval->aptr++ = ipc;
                *eval->aptr = NULL;
            }
            break;

        case SCOM:
            i = substitute(eval, ipc, step_vars);
            if (i == -1) {
                return APR_EGENERAL;
            }
            if (ipc->pfl && eval->commands->nflag && i) {
                if (ipc->pfl == 1) {
                    rv = wline(eval, eval->linebuf, eval->lspend -
                               eval->linebuf);
                    if (rv != APR_SUCCESS)
                        return rv;
                } else {
                    for (p1 = eval->linebuf; *p1 != '\n' && *p1 != '\0'; p1++);
                    rv = wline(eval, eval->linebuf, p1 - eval->linebuf);
                    if (rv != APR_SUCCESS)
                        return rv;
                }
            }
            if (i && (ipc->findex >= 0) && eval->fcode[ipc->findex])
                apr_file_printf(eval->fcode[ipc->findex], "%s\n",
                                eval->linebuf);
            break;

        case TCOM:
            if (eval->sflag == 0)  break;
            eval->sflag = 0;
            eval->jflag = 1;
            break;

        case WCOM:
            if (ipc->findex >= 0)
                apr_file_printf(eval->fcode[ipc->findex], "%s\n",
                                eval->linebuf);
            break;

        case XCOM:
            rv = copy_to_genbuf(eval, eval->linebuf);
            if (rv != APR_SUCCESS) return rv;
            rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
            if (rv != APR_SUCCESS) return rv;
            rv = copy_to_holdbuf(eval, eval->genbuf);
            if (rv != APR_SUCCESS) return rv;
            break;

        case YCOM:
            p1 = eval->linebuf;
            p2 = ipc->re1;
            while ((*p1 = p2[(unsigned char)*p1]) != 0)    p1++;
            break;
    }
    return rv;
}

/*
 * arout
 */
static apr_status_t arout(sed_eval_t *eval)
{
    apr_status_t rv = APR_SUCCESS;
    eval->aptr = eval->abuf - 1;
    while (*++eval->aptr) {
        if ((*eval->aptr)->command == ACOM) {
            char *p1;

            for (p1 = (*eval->aptr)->re1; *p1; p1++);
            rv = wline(eval, (*eval->aptr)->re1, p1 - (*eval->aptr)->re1);
            if (rv != APR_SUCCESS)
                return rv;
        } else {
            apr_file_t *fi = NULL;
            char buf[512];
            apr_size_t n = sizeof(buf);

            if (apr_file_open(&fi, (*eval->aptr)->re1, APR_READ, 0, eval->pool)
                              != APR_SUCCESS)
                continue;
            while ((apr_file_read(fi, buf, &n)) == APR_SUCCESS) {
                if (n == 0)
                    break;
                rv = eval->writefn(eval->fout, buf, n);
                if (rv != APR_SUCCESS) {
                    apr_file_close(fi);
                    return rv;
                }
                n = sizeof(buf);
            }
            apr_file_close(fi);
        }
    }
    eval->aptr = eval->abuf;
    *eval->aptr = NULL;
    return rv;
}

/*
 * wline
 */
static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz)
{
    apr_status_t rv = APR_SUCCESS;
    rv = eval->writefn(eval->fout, buf, sz);
    if (rv != APR_SUCCESS)
        return rv;
    rv = eval->writefn(eval->fout, "\n", 1);
    return rv;
}

