/* ====================================================================
 * Copyright (c) 1996-1999 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

#include "httpd.h"
#include "http_main.h"
#include "http_log.h"

#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifndef NO_WRITEV
#include <sys/types.h>
#include <sys/uio.h>
#endif

#ifdef HAVE_BSTRING_H
#include <bstring.h>		/* for IRIX, FD_SET calls bzero() */
#endif

#ifndef DEFAULT_BUFSIZE
#define DEFAULT_BUFSIZE (4096)
#endif
/* This must be enough to represent (DEFAULT_BUFSIZE - 3) in hex,
 * plus two extra characters.
 */
#ifndef CHUNK_HEADER_SIZE
#define CHUNK_HEADER_SIZE (5)
#endif


/* bwrite()s of greater than this size can result in a large_write() call,
 * which can result in a writev().  It's a little more work to set up the
 * writev() rather than copy bytes into the buffer, so we don't do it for small
 * writes.  This is especially important when chunking (which is a very likely
 * source of small writes if it's a module using ap_bputc/ap_bputs)...because we
 * have the expense of actually building two chunks for each writev().
 */
#ifndef LARGE_WRITE_THRESHOLD
#define LARGE_WRITE_THRESHOLD 31
#endif


/*
 * Buffered I/O routines.
 * These are a replacement for the stdio routines.
 * Advantages:
 *  Known semantics for handling of file-descriptors (on close etc.)
 *  No problems reading and writing simultanously to the same descriptor
 *  No limits on the number of open file handles.
 *  Only uses memory resources; no need to ensure the close routine
 *  is called.
 *  Extra code could be inserted between the buffered and un-buffered routines.
 *  Timeouts could be handled by using select or poll before read or write.
 *  Extra error handling could be introduced; e.g.
 *   keep an address to which we should longjump(), or
 *   keep a stack of routines to call on error.
 */

/* Notes:
 *  On reading EOF, EOF will set in the flags and no further Input will
 * be done.
 *
 * On an error except for EAGAIN, ERROR will be set in the flags and no
 * futher I/O will be done
 */

#ifdef WIN32

/*
  select() sometimes returns 1 even though the write will block. We must work around this.
*/

#ifdef EAPI
API_EXPORT(int) sendwithtimeout(int sock, const char *buf, int len, int flags)
#else /* EAPI */
int sendwithtimeout(int sock, const char *buf, int len, int flags)
#endif /* EAPI */
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;
    int retry;

    if (!(tv.tv_sec = ap_check_alarm()))
	return (send(sock, buf, len, flags));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    if (rv) {
	err = WSAGetLastError();
	ap_assert(0);
    }
    rv = send(sock, buf, len, flags);
    if (rv == SOCKET_ERROR) {
	err = WSAGetLastError();
	if (err == WSAEWOULDBLOCK)
	    do {
		retry=0;

		FD_ZERO(&fdset);
		FD_SET(sock, &fdset);
		tv.tv_usec = 0;
		rv = select(FD_SETSIZE, NULL, &fdset, NULL, &tv);
		if (rv == SOCKET_ERROR)
		    err = WSAGetLastError();
		else if (rv == 0) {
 		    ioctlsocket(sock, FIONBIO, &iostate);
		    if(ap_check_alarm() < 0) {
			WSASetLastError(EINTR);	/* Simulate an alarm() */
			return (SOCKET_ERROR);
		    }
		}
		else {
		    rv = send(sock, buf, len, flags);
		    if (rv == SOCKET_ERROR) {
		        err = WSAGetLastError();
			if(err == WSAEWOULDBLOCK) {
			    ap_log_error(APLOG_MARK,APLOG_DEBUG,NULL,
				"select claimed we could write, but in fact we couldn't. This is a bug in Windows.");
			    retry=1;
			    Sleep(100);
			}
		    }
		}
	    } while(retry);
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
	WSASetLastError(err);
    return (rv);
}

#ifdef EAPI
API_EXPORT(int) recvwithtimeout(int sock, char *buf, int len, int flags)
#else /* EAPI */
int recvwithtimeout(int sock, char *buf, int len, int flags)
#endif /* EAPI */
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;

    if (!(tv.tv_sec = ap_check_alarm()))
	return (recv(sock, buf, len, flags));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    ap_assert(!rv);
    rv = recv(sock, buf, len, flags);
    if (rv == SOCKET_ERROR) {
	err = WSAGetLastError();
	if (err == WSAEWOULDBLOCK) {
	    FD_ZERO(&fdset);
	    FD_SET(sock, &fdset);
	    tv.tv_usec = 0;
	    rv = select(FD_SETSIZE, &fdset, NULL, NULL, &tv);
	    if (rv == SOCKET_ERROR)
		err = WSAGetLastError();
	    else if (rv == 0) {
		ioctlsocket(sock, FIONBIO, &iostate);
		ap_check_alarm();
		WSASetLastError(WSAEWOULDBLOCK);
		return (SOCKET_ERROR);
	    }
	    else {
		rv = recv(sock, buf, len, flags);
		if (rv == SOCKET_ERROR)
		    err = WSAGetLastError();
	    }
	}
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
	WSASetLastError(err);
    return (rv);
}

#endif /* WIN32 */


/* the lowest level reading primitive */
static int ap_read(BUFF *fb, void *buf, int nbyte)
{
    int rv;
    
#ifdef WIN32
    if (fb->hFH != INVALID_HANDLE_VALUE) {
        if (!ReadFile(fb->hFH,buf,nbyte,&rv,NULL))
            rv = -1;
    }
    else
#endif
#ifdef EAPI
	if (!ap_hook_call("ap::buff::read", &rv, fb, buf, nbyte))
#endif /* EAPI */
	rv = read(fb->fd_in, buf, nbyte);
    
    return rv;
}

static ap_inline int buff_read(BUFF *fb, void *buf, int nbyte)
{
    int rv;

#if defined (WIN32)
    if (fb->flags & B_SOCKET) {
#ifdef EAPI
	if (!ap_hook_call("ap::buff::recvwithtimeout", &rv, fb, buf, nbyte))
#endif /* EAPI */
	rv = recvwithtimeout(fb->fd_in, buf, nbyte, 0);
	if (rv == SOCKET_ERROR)
	    errno = WSAGetLastError();
    }
    else
	rv = ap_read(fb, buf, nbyte);
#elif defined (BEOS)
    if (fb->flags & B_SOCKET) {
    rv = recv(fb->fd_in, buf, nbyte, 0);
    } else
    rv = ap_read(fb,buf,nbyte);
#elif defined(TPF)
    fd_set fds;
    struct timeval tv;

    ap_check_signals();
    if (fb->flags & B_SOCKET) {
        alarm(rv = alarm(0));
        FD_ZERO(&fds);
        FD_SET(fb->fd_in, &fds);
        tv.tv_sec = rv+1;
        tv.tv_usec = 0;
        rv = ap_select(fb->fd_in + 1, &fds, NULL, NULL, &tv);
        if (rv > 0)
            rv = ap_read(fb, buf, nbyte);
    }
    else
    rv = ap_read(fb, buf, nbyte);
#else
    rv = ap_read(fb, buf, nbyte);
#endif /* WIN32 */
    return rv;
}

/* the lowest level writing primitive */
static int ap_write(BUFF *fb, const void *buf, int nbyte)
{
    int rv;
    
#ifdef WIN32
    if (fb->hFH != INVALID_HANDLE_VALUE) {
        if (!WriteFile(fb->hFH,buf,nbyte,&rv,NULL))
          rv = -1;
    }
    else
#endif
#ifdef EAPI
	if (!ap_hook_call("ap::buff::write", &rv, fb, buf, nbyte))
#endif /* EAPI */
#if defined (B_SFIO)
	rv = sfwrite(fb->sf_out, buf, nbyte);
#else
	rv = write(fb->fd, buf, nbyte);
#endif
    
    return rv;
}

static ap_inline int buff_write(BUFF *fb, const void *buf, int nbyte)
{
    int rv;

#if defined(WIN32)
    if (fb->flags & B_SOCKET) {
#ifdef EAPI
	if (!ap_hook_call("ap::buff::sendwithtimeout", &rv, fb, buf, nbyte))
#endif /* EAPI */
	rv = sendwithtimeout(fb->fd, buf, nbyte, 0);
	if (rv == SOCKET_ERROR)
	    errno = WSAGetLastError();
    }
    else
	rv = ap_write(fb, buf, nbyte);
#elif defined(BEOS)
    if(fb->flags & B_SOCKET) {
    rv = send(fb->fd, buf, nbyte, 0);
    } else 
    rv = ap_write(fb, buf,nbyte);
#else
    rv = ap_write(fb, buf, nbyte);
#endif /* WIN32 */
    return rv;
}

static void doerror(BUFF *fb, int direction)
{
    int errsave = errno;	/* Save errno to prevent overwriting it below */

    fb->flags |= (direction == B_RD ? B_RDERR : B_WRERR);
    if (fb->error != NULL)
	(*fb->error) (fb, direction, fb->error_data);

    errno = errsave;
}

/* Buffering routines */
/*
 * Create a new buffered stream
 */
API_EXPORT(BUFF *) ap_bcreate(pool *p, int flags)
{
    BUFF *fb;

    fb = ap_palloc(p, sizeof(BUFF));
    fb->pool = p;
    fb->bufsiz = DEFAULT_BUFSIZE;
    fb->flags = flags & (B_RDWR | B_SOCKET);

    if (flags & B_RD)
	fb->inbase = ap_palloc(p, fb->bufsiz);
    else
	fb->inbase = NULL;

    /* overallocate so that we can put a chunk trailer of CRLF into this
     * buffer */
    if (flags & B_WR)
	fb->outbase = ap_palloc(p, fb->bufsiz + 2);
    else
	fb->outbase = NULL;

#ifdef CHARSET_EBCDIC
    fb->flags |= (flags & B_SOCKET) ? (B_EBCDIC2ASCII | B_ASCII2EBCDIC) : 0;
#endif /*CHARSET_EBCDIC*/

    fb->inptr = fb->inbase;

    fb->incnt = 0;
    fb->outcnt = 0;
    fb->outchunk = -1;
    fb->error = NULL;
    fb->bytes_sent = 0L;

    fb->fd = -1;
    fb->fd_in = -1;
#ifdef WIN32
    fb->hFH = INVALID_HANDLE_VALUE;
#endif

#ifdef B_SFIO
    fb->sf_in = NULL;
    fb->sf_out = NULL;
    fb->sf_in = sfnew(fb->sf_in, NIL(Void_t *),
		      (size_t) SF_UNBOUND, 0, SF_READ);
    fb->sf_out = sfnew(fb->sf_out, NIL(Void_t *),
		       (size_t) SF_UNBOUND, 1, SF_WRITE);
#endif

#ifdef EAPI
    fb->ctx = ap_ctx_new(p);
#endif /* EAPI */

    return fb;
}

/*
 * Push some I/O file descriptors onto the stream
 */
API_EXPORT(void) ap_bpushfd(BUFF *fb, int fd_in, int fd_out)
{
    fb->fd = fd_out;
    fb->fd_in = fd_in;
}

#ifdef WIN32
/*
 * Push some Win32 handles onto the stream.
 */
API_EXPORT(void) ap_bpushh(BUFF *fb, HANDLE hFH)
{
    fb->hFH = hFH;
}
#endif

API_EXPORT(int) ap_bsetopt(BUFF *fb, int optname, const void *optval)
{
    if (optname == BO_BYTECT) {
	fb->bytes_sent = *(const long int *) optval - (long int) fb->outcnt;;
	return 0;
    }
    else {
	errno = EINVAL;
	return -1;
    }
}

API_EXPORT(int) ap_bgetopt(BUFF *fb, int optname, void *optval)
{
    if (optname == BO_BYTECT) {
	long int bs = fb->bytes_sent + fb->outcnt;
	if (bs < 0L)
	    bs = 0L;
	*(long int *) optval = bs;
	return 0;
    }
    else {
	errno = EINVAL;
	return -1;
    }
}

static int bflush_core(BUFF *fb);

/*
 * Start chunked encoding.
 *
 * Note that in order for ap_bputc() to be an efficient macro we have to guarantee
 * that start_chunk() has always been called on the buffer before we leave any
 * routine in this file.  Said another way, if a routine here uses end_chunk()
 * and writes something on the wire, then it has to call start_chunk() or set
 * an error condition before returning.
 */
static void start_chunk(BUFF *fb)
{
    if (fb->outchunk != -1) {
	/* already chunking */
	return;
    }
    if ((fb->flags & (B_WRERR | B_EOUT | B_WR)) != B_WR) {
	/* unbuffered writes */
	return;
    }

    /* we need at least the header_len + at least 1 data byte
     * remember that we've overallocated fb->outbase so that we can always
     * fit the two byte CRLF trailer
     */
    if (fb->bufsiz - fb->outcnt < CHUNK_HEADER_SIZE + 1) {
	bflush_core(fb);
    }
    fb->outchunk = fb->outcnt;
    fb->outcnt += CHUNK_HEADER_SIZE;
}


/*
 * end a chunk -- tweak the chunk_header from start_chunk, and add a trailer
 */
static void end_chunk(BUFF *fb)
{
    int i;
    unsigned char *strp;

    if (fb->outchunk == -1) {
	/* not chunking */
	return;
    }

    if (fb->outchunk + CHUNK_HEADER_SIZE == fb->outcnt) {
	/* nothing was written into this chunk, and we can't write a 0 size
	 * chunk because that signifies EOF, so just erase it
	 */
	fb->outcnt = fb->outchunk;
	fb->outchunk = -1;
	return;
    }

    /* we know this will fit because of how we wrote it in start_chunk() */
    i = ap_snprintf((char *) &fb->outbase[fb->outchunk], CHUNK_HEADER_SIZE,
		"%x", fb->outcnt - fb->outchunk - CHUNK_HEADER_SIZE);

    /* we may have to tack some trailing spaces onto the number we just wrote
     * in case it was smaller than our estimated size.  We've also written
     * a \0 into the buffer with ap_snprintf so we might have to put a
     * \r back in.
     */
    strp = &fb->outbase[fb->outchunk + i];
    while (i < CHUNK_HEADER_SIZE - 2) {
	*strp++ = ' ';
	++i;
    }
    *strp++ = '\015';
    *strp = '\012';
#ifdef CHARSET_EBCDIC
    /* Chunks are an HTTP/1.1 Protocol feature. They must ALWAYS be in ASCII */
    ebcdic2ascii(&fb->outbase[fb->outchunk], &fb->outbase[fb->outchunk], CHUNK_HEADER_SIZE);
#endif /*CHARSET_EBCDIC*/

    /* tack on the trailing CRLF, we've reserved room for this */
    fb->outbase[fb->outcnt++] = '\015';
    fb->outbase[fb->outcnt++] = '\012';

    fb->outchunk = -1;
}


/*
 * Set a flag on (1) or off (0).
 */
API_EXPORT(int) ap_bsetflag(BUFF *fb, int flag, int value)
{
    if (value) {
	fb->flags |= flag;
	if (flag & B_CHUNK) {
	    start_chunk(fb);
	}
    }
    else {
	fb->flags &= ~flag;
	if (flag & B_CHUNK) {
	    end_chunk(fb);
	}
    }
    return value;
}


API_EXPORT(int) ap_bnonblock(BUFF *fb, int direction)
{
    int fd;

    fd = (direction == B_RD) ? fb->fd_in : fb->fd;
#if defined(O_NONBLOCK)
    return fcntl(fd, F_SETFL, O_NONBLOCK);
#elif defined(O_NDELAY)
    return fcntl(fd, F_SETFL, O_NDELAY);
#elif defined(FNDELAY)
    return fcntl(fd, F_SETFL, FNDELAY);
#else
    /* XXXX: this breaks things, but an alternative isn't obvious...*/
    return 0;
#endif
}

API_EXPORT(int) ap_bfileno(BUFF *fb, int direction)
{
    return (direction == B_RD) ? fb->fd_in : fb->fd;
}

/*
 * This is called instead of read() everywhere in here.  It implements
 * the B_SAFEREAD functionality -- which is to force a flush() if a read()
 * would block.  It also deals with the EINTR errno result from read().
 * return code is like read() except EINTR is eliminated.
 */


#if !defined (B_SFIO) || defined (WIN32)
#define saferead saferead_guts
#else
static int saferead(BUFF *fb, char *buf, int nbyte)
{
    return sfread(fb->sf_in, buf, nbyte);
}
#endif


/* Test the descriptor and flush the output buffer if it looks like
 * we will block on the next read.
 *
 * Note we assume the caller has ensured that fb->fd_in <= FD_SETSIZE
 */
API_EXPORT(void) ap_bhalfduplex(BUFF *fb)
{
    int rv;
    fd_set fds;
    struct timeval tv;

    /* We don't need to do anything if the connection has been closed
     * or there is something readable in the incoming buffer
     * or there is nothing flushable in the output buffer.
     */
    if (fb == NULL || fb->fd_in < 0 || fb->incnt > 0 || fb->outcnt == 0) {
	return;
    }
    /* test for a block */
    do {
	FD_ZERO(&fds);
	FD_SET(fb->fd_in, &fds);
	tv.tv_sec = 0;
	tv.tv_usec = 0;
	rv = ap_select(fb->fd_in + 1, &fds, NULL, NULL, &tv);
    } while (rv < 0 && errno == EINTR && !(fb->flags & B_EOUT));

    /* treat any error as if it would block as well */
    if (rv != 1) {
	ap_bflush(fb);
    }
}

static ap_inline int saferead_guts(BUFF *fb, void *buf, int nbyte)
{
    int rv;

    if (fb->flags & B_SAFEREAD) {
	ap_bhalfduplex(fb);
    }
    do {
	rv = buff_read(fb, buf, nbyte);
    } while (rv == -1 && errno == EINTR && !(fb->flags & B_EOUT));
    return (rv);
}

#ifdef B_SFIO
int bsfio_read(Sfio_t * f, char *buf, int nbyte, apache_sfio *disc)
{
    int rv;
    BUFF *fb = disc->buff;

    rv = saferead_guts(fb, buf, nbyte);

    buf[rv] = '\0';
    f->next = 0;

    return (rv);
}

int bsfio_write(Sfio_t * f, char *buf, int nbyte, apache_sfio *disc)
{
    return ap_write(disc->buff, buf, nbyte);
}

Sfdisc_t *bsfio_new(pool *p, BUFF *b)
{
    apache_sfio *disc;

    if (!(disc = (apache_sfio *) ap_palloc(p, sizeof(apache_sfio))))
	            return (Sfdisc_t *) disc;

    disc->disc.readf = (Sfread_f) bsfio_read;
    disc->disc.writef = (Sfwrite_f) bsfio_write;
    disc->disc.seekf = (Sfseek_f) NULL;
    disc->disc.exceptf = (Sfexcept_f) NULL;
    disc->buff = b;

    return (Sfdisc_t *) disc;
}
#endif


/* A wrapper around saferead which does error checking and EOF checking
 * yeah, it's confusing, this calls saferead, which calls buff_read...
 * and then there's the SFIO case.  Note that saferead takes care
 * of EINTR.
 */
static int read_with_errors(BUFF *fb, void *buf, int nbyte)
{
    int rv;

    rv = saferead(fb, buf, nbyte);
    if (rv == 0) {
	fb->flags |= B_EOF;
    }
    else if (rv == -1 && errno != EAGAIN) {
	doerror(fb, B_RD);
    }
    return rv;
}


/*
 * Read up to nbyte bytes into buf.
 * If fewer than byte bytes are currently available, then return those.
 * Returns 0 for EOF, -1 for error.
 * NOTE EBCDIC: The readahead buffer _always_ contains *unconverted* data.
 * Only when the caller retrieves data from the buffer (calls bread)
 * is a conversion done, if the conversion flag is set at that time.
 */
API_EXPORT(int) ap_bread(BUFF *fb, void *buf, int nbyte)
{
    int i, nrd;

    if (fb->flags & B_RDERR)
	return -1;
    if (nbyte == 0)
	return 0;

    if (!(fb->flags & B_RD)) {
	/* Unbuffered reading.  First check if there was something in the
	 * buffer from before we went unbuffered. */
	if (fb->incnt) {
	    i = (fb->incnt > nbyte) ? nbyte : fb->incnt;
#ifdef CHARSET_EBCDIC
	    if (fb->flags & B_ASCII2EBCDIC)
		ascii2ebcdic(buf, fb->inptr, i);
	    else
#endif /*CHARSET_EBCDIC*/
	    memcpy(buf, fb->inptr, i);
	    fb->incnt -= i;
	    fb->inptr += i;
	    return i;
	}
	i = read_with_errors(fb, buf, nbyte);
#ifdef CHARSET_EBCDIC
	if (i > 0 && ap_bgetflag(fb, B_ASCII2EBCDIC))
	    ascii2ebcdic(buf, buf, i);
#endif /*CHARSET_EBCDIC*/
	return i;
    }

    nrd = fb->incnt;
/* can we fill the buffer */
    if (nrd >= nbyte) {
#ifdef CHARSET_EBCDIC
	if (fb->flags & B_ASCII2EBCDIC)
	    ascii2ebcdic(buf, fb->inptr, nbyte);
	else
#endif /*CHARSET_EBCDIC*/
	memcpy(buf, fb->inptr, nbyte);
	fb->incnt = nrd - nbyte;
	fb->inptr += nbyte;
	return nbyte;
    }

    if (nrd > 0) {
#ifdef CHARSET_EBCDIC
	if (fb->flags & B_ASCII2EBCDIC)
	    ascii2ebcdic(buf, fb->inptr, nrd);
	else
#endif /*CHARSET_EBCDIC*/
	memcpy(buf, fb->inptr, nrd);
	nbyte -= nrd;
	buf = nrd + (char *) buf;
	fb->incnt = 0;
    }
    if (fb->flags & B_EOF)
	return nrd;

/* do a single read */
    if (nbyte >= fb->bufsiz) {
/* read directly into caller's buffer */
	i = read_with_errors(fb, buf, nbyte);
#ifdef CHARSET_EBCDIC
	if (i > 0 && ap_bgetflag(fb, B_ASCII2EBCDIC))
	    ascii2ebcdic(buf, buf, i);
#endif /*CHARSET_EBCDIC*/
	if (i == -1) {
	    return nrd ? nrd : -1;
	}
    }
    else {
/* read into hold buffer, then memcpy */
	fb->inptr = fb->inbase;
	i = read_with_errors(fb, fb->inptr, fb->bufsiz);
	if (i == -1) {
	    return nrd ? nrd : -1;
	}
	fb->incnt = i;
	if (i > nbyte)
	    i = nbyte;
#ifdef CHARSET_EBCDIC
	if (fb->flags & B_ASCII2EBCDIC)
	    ascii2ebcdic(buf, fb->inptr, i);
	else
#endif /*CHARSET_EBCDIC*/
	memcpy(buf, fb->inptr, i);
	fb->incnt -= i;
	fb->inptr += i;
    }
    return nrd + i;
}


/*
 * Reads from the stream into the array pointed to by buff, until
 * a (CR)LF sequence is read, or end-of-file condition is encountered
 * or until n-1 bytes have been stored in buff. If a CRLF sequence is
 * read, it is replaced by a newline character.  The string is then
 * terminated with a null character.
 *
 * Returns the number of bytes stored in buff, or zero on end of
 * transmission, or -1 on an error.
 *
 * Notes:
 *  If null characters are expected in the data stream, then
 * buff should not be treated as a null terminated C string; instead
 * the returned count should be used to determine the length of the
 * string.
 *  CR characters in the byte stream not immediately followed by a LF
 * will be preserved.
 */
API_EXPORT(int) ap_bgets(char *buff, int n, BUFF *fb)
{
    int i, ch, ct;

/* Can't do bgets on an unbuffered stream */
    if (!(fb->flags & B_RD)) {
	errno = EINVAL;
	return -1;
    }
    if (fb->flags & B_RDERR)
	return -1;

    ct = 0;
    i = 0;
    for (;;) {
	if (i == fb->incnt) {
/* no characters left */
	    fb->inptr = fb->inbase;
	    fb->incnt = 0;
	    if (fb->flags & B_EOF)
		break;
	    i = read_with_errors(fb, fb->inptr, fb->bufsiz);
	    if (i == -1) {
		buff[ct] = '\0';
		return ct ? ct : -1;
	    }
	    fb->incnt = i;
	    if (i == 0)
		break;		/* EOF */
	    i = 0;
	    continue;		/* restart with the new data */
	}

	ch = fb->inptr[i++];
#ifndef CHARSET_EBCDIC
	if (ch == '\012') {	/* got LF */
	    if (ct == 0)
		buff[ct++] = '\n';
/* if just preceeded by CR, replace CR with LF */
	    else if (buff[ct - 1] == '\015')
		buff[ct - 1] = '\n';
	    else if (ct < n - 1)
		buff[ct++] = '\n';
	    else
		i--;		/* no room for LF */
	    break;
	}
#else /* an EBCDIC machine: do the same, but convert to EBCDIC on the fly: */
	if (fb->flags & B_ASCII2EBCDIC)
	    ch = os_toebcdic[(unsigned char)ch];
	if (ch == os_toebcdic['\012']) {  /* got LF */
	    if (ct == 0)
		buff[ct++] = '\n';
/* if just preceeded by CR, replace CR with LF */
	    else if (buff[ct - 1] == os_toebcdic['\015'])
		buff[ct - 1] = '\n';
	    else if (ct < n - 1)
		buff[ct++] = '\n';
	    else
		i--;		/* no room for LF */
	    break;
	}
#endif
	if (ct == n - 1) {
	    i--;		/* push back ch */
	    break;
	}

	buff[ct++] = ch;
    }
    fb->incnt -= i;
    fb->inptr += i;

    buff[ct] = '\0';
    return ct;
}

/*
 * Looks at the stream fb and places the first character into buff
 * without removing it from the stream buffer.
 *
 * Returns 1 on success, zero on end of transmission, or -1 on an error.
 *
 */
API_EXPORT(int) ap_blookc(char *buff, BUFF *fb)
{
    int i;

    *buff = '\0';

    if (!(fb->flags & B_RD)) {	/* Can't do blookc on an unbuffered stream */
	errno = EINVAL;
	return -1;
    }
    if (fb->flags & B_RDERR)
	return -1;

    if (fb->incnt == 0) {	/* no characters left in stream buffer */
	fb->inptr = fb->inbase;
	if (fb->flags & B_EOF)
	    return 0;

	i = read_with_errors(fb, fb->inptr, fb->bufsiz);
	if (i <= 0) {
	    return i;
	}
	fb->incnt = i;
    }

#ifndef CHARSET_EBCDIC
    *buff = fb->inptr[0];
#else /*CHARSET_EBCDIC*/
    *buff = (fb->flags & B_ASCII2EBCDIC)
	     ? os_toebcdic[(unsigned char)fb->inptr[0]]
	     : fb->inptr[0];
#endif /*CHARSET_EBCDIC*/
    return 1;
}

/*
 * Skip data until a linefeed character is read
 * Returns 1 on success, 0 if no LF found, or -1 on error
 */
API_EXPORT(int) ap_bskiplf(BUFF *fb)
{
    unsigned char *x;
    int i;

/* Can't do bskiplf on an unbuffered stream */
    if (!(fb->flags & B_RD)) {
	errno = EINVAL;
	return -1;
    }
    if (fb->flags & B_RDERR)
	return -1;

    for (;;) {
	x = (unsigned char *) memchr(fb->inptr, '\012', fb->incnt);
	if (x != NULL) {
	    x++;
	    fb->incnt -= x - fb->inptr;
	    fb->inptr = x;
	    return 1;
	}

	fb->inptr = fb->inbase;
	fb->incnt = 0;
	if (fb->flags & B_EOF)
	    return 0;
	i = read_with_errors(fb, fb->inptr, fb->bufsiz);
	if (i <= 0)
	    return i;
	fb->incnt = i;
    }
}

/*
 * output a single character.  Used by ap_bputs when the buffer
 * is full... and so it'll cause the buffer to be flushed first.
 */
API_EXPORT(int) ap_bflsbuf(int c, BUFF *fb)
{
    char ss[1];

    ss[0] = c;
    return ap_bwrite(fb, ss, 1);
}

/*
 * Fill the buffer and read a character from it
 */
API_EXPORT(int) ap_bfilbuf(BUFF *fb)
{
    int i;
    char buf[1];

    i = ap_bread(fb, buf, 1);
    if (i == 0)
	errno = 0;		/* no error; EOF */
    if (i != 1)
	return EOF;
    else
	return buf[0];
}


/*
 * When doing chunked encodings we really have to write everything in the
 * chunk before proceeding onto anything else.  This routine either writes
 * nbytes and returns 0 or returns -1 indicating a failure.
 *
 * This is *seriously broken* if used on a non-blocking fd.  It will poll.
 *
 * Deals with calling doerror and setting bytes_sent.
 */
static int write_it_all(BUFF *fb, const void *buf, int nbyte)
{
    int i;

    if (fb->flags & (B_WRERR | B_EOUT))
	return -1;

    while (nbyte > 0) {
	i = buff_write(fb, buf, nbyte);
	if (i < 0) {
	    if (errno != EAGAIN && errno != EINTR) {
		doerror(fb, B_WR);
		return -1;
	    }
	}
	else {
	    nbyte -= i;
	    buf = i + (const char *) buf;
	    fb->bytes_sent += i;
	}
	if (fb->flags & B_EOUT)
	    return -1;
    }
    return 0;
}


#ifndef NO_WRITEV
/* Similar to previous, but uses writev.  Note that it modifies vec.
 * return 0 if successful, -1 otherwise.
 *
 * Deals with doerror() and bytes_sent.
 */
static int writev_it_all(BUFF *fb, struct iovec *vec, int nvec)
{
    int i, rv;

    /* while it's nice an easy to build the vector and crud, it's painful
     * to deal with a partial writev()
     */
    i = 0;
    while (i < nvec) {
	do
#ifdef EAPI
	    if (!ap_hook_call("ap::buff::writev", &rv, fb, &vec[i], nvec -i))
#endif /* EAPI */
	    rv = writev(fb->fd, &vec[i], nvec - i);
	while (rv == -1 && (errno == EINTR || errno == EAGAIN)
	       && !(fb->flags & B_EOUT));
	if (rv == -1) {
	    if (errno != EINTR && errno != EAGAIN) {
		doerror(fb, B_WR);
	    }
	    return -1;
	}
	fb->bytes_sent += rv;
	/* recalculate vec to deal with partial writes */
	while (rv > 0) {
	    if (rv < vec[i].iov_len) {
		vec[i].iov_base = (char *) vec[i].iov_base + rv;
		vec[i].iov_len -= rv;
		rv = 0;
	    }
	    else {
		rv -= vec[i].iov_len;
		++i;
	    }
	}
	if (fb->flags & B_EOUT)
	    return -1;
    }
    /* if we got here, we wrote it all */
    return 0;
}
#endif

/* A wrapper for buff_write which deals with error conditions and
 * bytes_sent.  Also handles non-blocking writes.
 */
static int write_with_errors(BUFF *fb, const void *buf, int nbyte)
{
    int rv;

    do
	rv = buff_write(fb, buf, nbyte);
    while (rv == -1 && errno == EINTR && !(fb->flags & B_EOUT));
    if (rv == -1) {
	if (errno != EAGAIN) {
	    doerror(fb, B_WR);
	}
	return -1;
    }
    else if (rv == 0) {
	errno = EAGAIN;
	return -1;
    }
    fb->bytes_sent += rv;
    return rv;
}


/*
 * A hook to write() that deals with chunking. This is really a protocol-
 * level issue, but we deal with it here because it's simpler; this is
 * an interim solution pending a complete rewrite of all this stuff in
 * 2.0, using something like sfio stacked disciplines or BSD's funopen().
 *
 * Can be used on non-blocking descriptors, but only if they're not chunked.
 * Deals with doerror() and bytes_sent.
 */
static int bcwrite(BUFF *fb, const void *buf, int nbyte)
{
    char chunksize[16];		/* Big enough for practically anything */
#ifndef NO_WRITEV
    struct iovec vec[3];
#endif

    if (fb->flags & (B_WRERR | B_EOUT))
	return -1;

    if (!(fb->flags & B_CHUNK)) {
	return write_with_errors(fb, buf, nbyte);
    }

#ifdef NO_WRITEV
    /* without writev() this has poor performance, too bad */

    ap_snprintf(chunksize, sizeof(chunksize), "%x\015\012", nbyte);
#ifdef CHARSET_EBCDIC
    /* Chunks are an HTTP/1.1 Protocol feature. They must ALWAYS be in ASCII */
    ebcdic2ascii(chunksize, chunksize, strlen(chunksize));
#endif /*CHARSET_EBCDIC*/
    if (write_it_all(fb, chunksize, strlen(chunksize)) == -1)
	return -1;
    if (write_it_all(fb, buf, nbyte) == -1)
	return -1;
    if (write_it_all(fb, "\015\012", 2) == -1)
	return -1;
    return nbyte;
#else
    vec[0].iov_base = chunksize;
    vec[0].iov_len = ap_snprintf(chunksize, sizeof(chunksize), "%x\015\012",
				 nbyte);
#ifdef CHARSET_EBCDIC
    /* Chunks are an HTTP/1.1 Protocol feature. They must ALWAYS be in ASCII */
    ebcdic2ascii(chunksize, chunksize, strlen(chunksize));
#endif /*CHARSET_EBCDIC*/
    vec[1].iov_base = (void *) buf;	/* cast is to avoid const warning */
    vec[1].iov_len = nbyte;
    vec[2].iov_base = "\015\012";
    vec[2].iov_len = 2;

    return writev_it_all(fb, vec, (sizeof(vec) / sizeof(vec[0]))) ? -1 : nbyte;
#endif
}


#ifndef NO_WRITEV
/*
 * Used to combine the contents of the fb buffer, and a large buffer
 * passed in.
 */
static int large_write(BUFF *fb, const void *buf, int nbyte)
{
    struct iovec vec[4];
    int nvec;
    char chunksize[16];

    /* it's easiest to end the current chunk */
    if (fb->flags & B_CHUNK) {
	end_chunk(fb);
    }
    nvec = 0;
    if (fb->outcnt > 0) {
	vec[nvec].iov_base = (void *) fb->outbase;
	vec[nvec].iov_len = fb->outcnt;
	++nvec;
    }
    if (fb->flags & B_CHUNK) {
	vec[nvec].iov_base = chunksize;
	vec[nvec].iov_len = ap_snprintf(chunksize, sizeof(chunksize),
					"%x\015\012", nbyte);
#ifdef CHARSET_EBCDIC
    /* Chunks are an HTTP/1.1 Protocol feature. They must ALWAYS be in ASCII */
	ebcdic2ascii(chunksize, chunksize, strlen(chunksize));
#endif /*CHARSET_EBCDIC*/
	++nvec;
	vec[nvec].iov_base = (void *) buf;
	vec[nvec].iov_len = nbyte;
	++nvec;
	vec[nvec].iov_base = "\015\012";
	vec[nvec].iov_len = 2;
	++nvec;
    }
    else {
	vec[nvec].iov_base = (void *) buf;
	vec[nvec].iov_len = nbyte;
	++nvec;
    }

    fb->outcnt = 0;
    if (writev_it_all(fb, vec, nvec)) {
	return -1;
    }
    else if (fb->flags & B_CHUNK) {
	start_chunk(fb);
    }
    return nbyte;
}
#endif


/*
 * Write nbyte bytes.
 * Only returns fewer than nbyte if an error ocurred.
 * Returns -1 if no bytes were written before the error ocurred.
 * It is worth noting that if an error occurs, the buffer is in an unknown
 * state.
 */
API_EXPORT(int) ap_bwrite(BUFF *fb, const void *buf, int nbyte)
{
    int i, nwr, useable_bufsiz;
#ifdef CHARSET_EBCDIC
    static char *cbuf = NULL;
    static int csize = 0;
#endif /*CHARSET_EBCDIC*/

    if (fb->flags & (B_WRERR | B_EOUT))
	return -1;
    if (nbyte == 0)
	return 0;

#ifdef CHARSET_EBCDIC
    if (ap_bgetflag(fb, B_EBCDIC2ASCII)) {
        if (nbyte > csize) {
            if (cbuf != NULL)
                free(cbuf);
            cbuf = malloc(csize = nbyte+HUGE_STRING_LEN);
            if (cbuf == NULL) {
                fprintf(stderr, "Ouch!  Out of memory in ap_bwrite()!\n");
                csize = 0;
            }
        }
        ebcdic2ascii((cbuf) ? cbuf : (void*)buf, buf, nbyte);
        buf = (cbuf) ? cbuf : buf;
    }
#endif /*CHARSET_EBCDIC*/

    if (!(fb->flags & B_WR)) {
/* unbuffered write -- have to use bcwrite since we aren't taking care
 * of chunking any other way */
	return bcwrite(fb, buf, nbyte);
    }

#ifndef NO_WRITEV
/*
 * Detect case where we're asked to write a large buffer, and combine our
 * current buffer with it in a single writev().  Note we don't consider
 * the case nbyte == 1 because modules which use rputc() loops will cause
 * us to use writev() too frequently.  In those cases we really should just
 * start a new buffer.
 */
    if (fb->outcnt > 0 && nbyte > LARGE_WRITE_THRESHOLD
	&& nbyte + fb->outcnt >= fb->bufsiz) {
	return large_write(fb, buf, nbyte);
    }
#endif

/*
 * Whilst there is data in the buffer, keep on adding to it and writing it
 * out
 */
    nwr = 0;
    while (fb->outcnt > 0) {
/* can we accept some data? */
	i = fb->bufsiz - fb->outcnt;
	if (i > 0) {
	    if (i > nbyte)
		i = nbyte;
	    memcpy(fb->outbase + fb->outcnt, buf, i);
	    fb->outcnt += i;
	    nbyte -= i;
	    buf = i + (const char *) buf;
	    nwr += i;
	    if (nbyte == 0)
		return nwr;	/* return if none left */
	}

/* the buffer must be full */
	if (fb->flags & B_CHUNK) {
	    end_chunk(fb);
	    /* it is just too painful to try to re-cram the buffer while
	     * chunking
	     */
	    if (write_it_all(fb, fb->outbase, fb->outcnt) == -1) {
		/* we cannot continue after a chunked error */
		return -1;
	    }
	    fb->outcnt = 0;
	    break;
	}
	i = write_with_errors(fb, fb->outbase, fb->outcnt);
	if (i <= 0) {
	    return nwr ? nwr : -1;
	}

	/* deal with a partial write */
	if (i < fb->outcnt) {
	    int j, n = fb->outcnt;
	    unsigned char *x = fb->outbase;
	    for (j = i; j < n; j++)
		x[j - i] = x[j];
	    fb->outcnt -= i;
	}
	else
	    fb->outcnt = 0;

	if (fb->flags & B_EOUT)
	    return -1;
    }
/* we have emptied the file buffer. Now try to write the data from the
 * original buffer until there is less than bufsiz left.  Note that we
 * use bcwrite() to do this for us, it will do the chunking so that
 * we don't have to dink around building a chunk in our own buffer.
 *
 * Note also that bcwrite never does a partial write if we're chunking,
 * so we're guaranteed to either end in an error state, or make it
 * out of this loop and call start_chunk() below.
 *
 * Remember we may not be able to use the entire buffer if we're
 * chunking.
 */
    useable_bufsiz = fb->bufsiz;
    if (fb->flags & B_CHUNK) useable_bufsiz -= CHUNK_HEADER_SIZE;
    while (nbyte >= useable_bufsiz) {
	i = bcwrite(fb, buf, nbyte);
	if (i <= 0) {
	    return nwr ? nwr : -1;
	}

	buf = i + (const char *) buf;
	nwr += i;
	nbyte -= i;

	if (fb->flags & B_EOUT)
	    return -1;
    }
/* copy what's left to the file buffer */
    fb->outcnt = 0;
    if (fb->flags & B_CHUNK)
	start_chunk(fb);
    if (nbyte > 0)
	memcpy(fb->outbase + fb->outcnt, buf, nbyte);
    fb->outcnt += nbyte;
    nwr += nbyte;
    return nwr;
}


static int bflush_core(BUFF *fb)
{
    int i;

    while (fb->outcnt > 0) {
	i = write_with_errors(fb, fb->outbase, fb->outcnt);
	if (i <= 0)
	    return -1;

	/*
	 * We should have written all the data, but if the fd was in a
	 * strange (non-blocking) mode, then we might not have done so.
	 */
	if (i < fb->outcnt) {
	    int j, n = fb->outcnt;
	    unsigned char *x = fb->outbase;
	    for (j = i; j < n; j++)
		x[j - i] = x[j];
	}
	fb->outcnt -= i;

	/* If a soft timeout occurs while flushing, the handler should
	 * have set the buffer flag B_EOUT.
	 */
	if (fb->flags & B_EOUT)
	    return -1;
    }

    return 0;
}

/*
 * Flushes the buffered stream.
 * Returns 0 on success or -1 on error
 */
API_EXPORT(int) ap_bflush(BUFF *fb)
{
    int ret;

    if ((fb->flags & (B_WRERR | B_EOUT | B_WR)) != B_WR)
	return -1;

    if (fb->flags & B_CHUNK)
	end_chunk(fb);

    ret = bflush_core(fb);

    if (ret == 0 && (fb->flags & B_CHUNK)) {
	start_chunk(fb);
    }

    return ret;
}

/*
 * Flushes and closes the file, even if an error occurred.
 * Discards an data that was not read, or not written by bflush()
 * Sets the EOF flag to indicate no futher data can be read,
 * and the EOUT flag to indicate no further data can be written.
 */
API_EXPORT(int) ap_bclose(BUFF *fb)
{
    int rc1, rc2, rc3;

    if (fb->flags & B_WR)
	rc1 = ap_bflush(fb);
    else
	rc1 = 0;
#ifdef WIN32
    if (fb->flags & B_SOCKET) {
	rc2 = ap_pclosesocket(fb->pool, fb->fd);
	if (fb->fd_in != fb->fd) {
	    rc3 = ap_pclosesocket(fb->pool, fb->fd_in);
	}
	else {
	    rc3 = 0;
	}
    }
    else if (fb->hFH != INVALID_HANDLE_VALUE) {
	    rc2 = ap_pcloseh(fb->pool, fb->hFH);
	    rc3 = 0;
    }
    else {
#elif defined(BEOS)
    if (fb->flags & B_SOCKET) {
	rc2 = ap_pclosesocket(fb->pool, fb->fd);
	if (fb->fd_in != fb->fd) {
	    rc3 = ap_pclosesocket(fb->pool, fb->fd_in);
	}
	else {
	    rc3 = 0;
	}
    } else {
#endif
	rc2 = ap_pclosef(fb->pool, fb->fd);
	if (fb->fd_in != fb->fd) {
	    rc3 = ap_pclosef(fb->pool, fb->fd_in);
	}
	else {
	    rc3 = 0;
	}
#if defined(WIN32) || defined (BEOS)
    }
#endif

    fb->inptr = fb->inbase;
    fb->incnt = 0;
    fb->outcnt = 0;

    fb->flags |= B_EOF | B_EOUT;
    fb->fd = -1;
    fb->fd_in = -1;

#ifdef B_SFIO
    sfclose(fb->sf_in);
    sfclose(fb->sf_out);
#endif

    if (rc1 != 0)
	return rc1;
    else if (rc2 != 0)
	return rc2;
    else
	return rc3;
}

/*
 * returns the number of bytes written or -1 on error
 */
API_EXPORT(int) ap_bputs(const char *x, BUFF *fb)
{
    int i, j = strlen(x);
    i = ap_bwrite(fb, x, j);
    if (i != j)
	return -1;
    else
	return j;
}

/*
 * returns the number of bytes written or -1 on error
 */
API_EXPORT_NONSTD(int) ap_bvputs(BUFF *fb,...)
{
    int i, j, k;
    va_list v;
    const char *x;

    va_start(v, fb);
    for (k = 0;;) {
	x = va_arg(v, const char *);
	if (x == NULL)
	    break;
	j = strlen(x);
	i = ap_bwrite(fb, x, j);
	if (i != j) {
	    va_end(v);
	    return -1;
	}
	k += i;
    }

    va_end(v);

    return k;
}

API_EXPORT(void) ap_bonerror(BUFF *fb, void (*error) (BUFF *, int, void *),
			  void *data)
{
    fb->error = error;
    fb->error_data = data;
}

struct bprintf_data {
    ap_vformatter_buff vbuff;
    BUFF *fb;
};

static int bprintf_flush(ap_vformatter_buff *vbuff)
{
    struct bprintf_data *b = (struct bprintf_data *)vbuff;
    BUFF *fb = b->fb;

#ifdef CHARSET_EBCDIC
    /* Characters were pushed into the buffer without conversion. Do it now */
    if (fb->flags & B_EBCDIC2ASCII)
        ebcdic2ascii(&fb->outbase[fb->outcnt],
		     &fb->outbase[fb->outcnt],
		     b->vbuff.curpos - (char *)&fb->outbase[fb->outcnt]);
#endif /*CHARSET_EBCDIC*/
    fb->outcnt += b->vbuff.curpos - (char *)&fb->outbase[fb->outcnt];
    if (fb->outcnt == fb->bufsiz) {
	if (ap_bflush(fb)) {
	    return -1;
	}
    }
    vbuff->curpos = (char *)&fb->outbase[fb->outcnt];
    vbuff->endpos = (char *)&fb->outbase[fb->bufsiz];
    return 0;
}

API_EXPORT_NONSTD(int) ap_bprintf(BUFF *fb, const char *fmt, ...)
{
    va_list ap;
    int res;
    struct bprintf_data b;

    /* XXX: only works with buffered writes */
    if ((fb->flags & (B_WRERR | B_EOUT | B_WR)) != B_WR)
	return -1;
    b.vbuff.curpos = (char *)&fb->outbase[fb->outcnt];
    b.vbuff.endpos = (char *)&fb->outbase[fb->bufsiz];
    b.fb = fb;
    va_start(ap, fmt);
    res = ap_vformatter(bprintf_flush, &b.vbuff, fmt, ap);
    va_end(ap);
    if (res != -1) {
#ifdef CHARSET_EBCDIC
	/* Characters were pushed into the buffer without conversion. Do it now */
	if (fb->flags & B_EBCDIC2ASCII)
	    ebcdic2ascii(&fb->outbase[fb->outcnt],
			 &fb->outbase[fb->outcnt],
			 b.vbuff.curpos - (char *)&fb->outbase[fb->outcnt]);
#endif /*CHARSET_EBCDIC*/
	fb->outcnt += b.vbuff.curpos - (char *)&fb->outbase[fb->outcnt];
    }
    return res;
}

API_EXPORT(int) ap_vbprintf(BUFF *fb, const char *fmt, va_list ap)
{
    struct bprintf_data b;
    int res;

    /* XXX: only works with buffered writes */
    if ((fb->flags & (B_WRERR | B_EOUT | B_WR)) != B_WR)
	return -1;
    b.vbuff.curpos = (char *)&fb->outbase[fb->outcnt];
    b.vbuff.endpos = (char *)&fb->outbase[fb->bufsiz];
    b.fb = fb;
    res = ap_vformatter(bprintf_flush, &b.vbuff, fmt, ap);
    if (res != -1) {
#ifdef CHARSET_EBCDIC
	/* Characters were pushed into the buffer without conversion. Do it now */
	if (fb->flags & B_EBCDIC2ASCII)
	    ebcdic2ascii(&fb->outbase[fb->outcnt],
			 &fb->outbase[fb->outcnt],
			 b.vbuff.curpos - (char *)&fb->outbase[fb->outcnt]);
#endif /*CHARSET_EBCDIC*/
	fb->outcnt += b.vbuff.curpos - (char *)&fb->outbase[fb->outcnt];
    }
    return res;
}
 
