use Config;

open OUT, ">FCGI.xs";

print "Generating FCGI.xs for Perl version $]\n";
#unless (exists $Config{apiversion} && $Config{apiversion} >= 5.005) 
unless ($] >= 5.005) {
    for (qw(sv_undef diehook warnhook in_eval)) {
	print OUT "#define PL_$_ $_\n" 
    }
}
print OUT while <DATA>;
close OUT;
__END__
/* $Id: FCGI.XL,v 1.9 2002/11/11 13:51:20 skimo Exp $ */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "fcgi_config.h"
#include "fcgiapp.h"
#include "fastcgi.h"

#ifndef FALSE
#define FALSE (0)
#endif

#ifndef TRUE
#define TRUE  (1)
#endif

#ifndef dTHX
#define dTHX
#endif

#ifndef INT2PTR
#define INT2PTR(a,b) ((a) (b))
#endif

#ifdef USE_SFIO
typedef struct
{
    Sfdisc_t	disc;
    FCGX_Stream	*stream;
} FCGI_Disc;

static ssize_t
sffcgiread(f, buf, n, disc)
Sfio_t*		f;      /* stream involved */
Void_t*		buf;    /* buffer to read into */
size_t		n;      /* number of bytes to read */
Sfdisc_t*	disc;   /* discipline */
{
    return FCGX_GetStr(buf, n, ((FCGI_Disc *)disc)->stream);
}

static ssize_t
sffcgiwrite(f, buf, n, disc)
Sfio_t*		f;      /* stream involved */
const Void_t*	buf;    /* buffer to read into */
size_t		n;      /* number of bytes to read */
Sfdisc_t*	disc;   /* discipline */
{
    n = FCGX_PutStr(buf, n, ((FCGI_Disc *)disc)->stream);
    FCGX_FFlush(((FCGI_Disc *)disc)->stream);
    return n;
}

Sfdisc_t *
sfdcnewfcgi(stream)
	FCGX_Stream *stream;
{
    FCGI_Disc*	disc;

    New(1000,disc,1,FCGI_Disc);
    if (!disc) return (Sfdisc_t *)disc;

    disc->disc.exceptf = (Sfexcept_f)NULL;
    disc->disc.seekf = (Sfseek_f)NULL;
    disc->disc.readf = sffcgiread;
    disc->disc.writef = sffcgiwrite;
    disc->stream = stream;
    return (Sfdisc_t *)disc;
}

Sfdisc_t *
sfdcdelfcgi(disc)
    Sfdisc_t*	disc;
{
    Safefree(disc);
    return 0;
}
#endif

#if defined(USE_LOCKING) && defined(USE_THREADS)
static perl_mutex   accept_mutex;
#endif

typedef struct FCGP_Request {
    int		    accepted;
    int		    bound;
    SV*		    svin;
    SV*		    svout;
    SV*		    sverr;
    GV*		    gv[3];
    HV*		    hvEnv;
    FCGX_Request*   requestPtr;
#ifdef USE_SFIO
    int		    sfcreated[3];
    IO*		    io[3];
#endif
} FCGP_Request;

static void FCGI_Finish(FCGP_Request* request);

static void 
FCGI_Flush(FCGP_Request* request)
{
    dTHX;

    if(!request->bound) {
	return;
	}
#ifdef USE_SFIO
    sfsync(IoOFP(GvIOp(request->gv[1])));
    sfsync(IoOFP(GvIOp(request->gv[2])));
#else
    FCGX_FFlush(INT2PTR(FCGX_Stream *, SvIV((SV*) SvRV(request->svout))));
    FCGX_FFlush(INT2PTR(FCGX_Stream *, SvIV((SV*) SvRV(request->sverr))));
#endif
}

static void
FCGI_UndoBinding(FCGP_Request* request)
{
    dTHX;

#ifdef USE_SFIO
    sfdcdelfcgi(sfdisc(IoIFP(request->io[0]), SF_POPDISC));
    sfdcdelfcgi(sfdisc(IoOFP(request->io[1]), SF_POPDISC));
    sfdcdelfcgi(sfdisc(IoOFP(request->io[2]), SF_POPDISC));
#else
#  ifdef USE_PERLIO
    sv_unmagic((SV *)GvIOp(request->gv[0]), 'q');
    sv_unmagic((SV *)GvIOp(request->gv[1]), 'q');
    sv_unmagic((SV *)GvIOp(request->gv[2]), 'q');
#  else
    sv_unmagic((SV *)request->gv[0], 'q');
    sv_unmagic((SV *)request->gv[1], 'q');
    sv_unmagic((SV *)request->gv[2], 'q');
#  endif
#endif
    request->bound = FALSE;
}

static void
FCGI_Bind(FCGP_Request* request)
{
    dTHX;

#ifdef USE_SFIO
    sfdisc(IoIFP(request->io[0]), sfdcnewfcgi(request->requestPtr->in));
    sfdisc(IoOFP(request->io[1]), sfdcnewfcgi(request->requestPtr->out));
    sfdisc(IoOFP(request->io[2]), sfdcnewfcgi(request->requestPtr->err));
#else
#  ifdef USE_PERLIO
    /* For tied filehandles, we apply tiedscalar magic to the IO
       slot of the GP rather than the GV itself. */

    if (!GvIOp(request->gv[1]))
	GvIOp(request->gv[1]) = newIO();
    if (!GvIOp(request->gv[2]))
	GvIOp(request->gv[2]) = newIO();
    if (!GvIOp(request->gv[0]))
	GvIOp(request->gv[0]) = newIO();

    sv_magic((SV *)GvIOp(request->gv[1]), request->svout, 'q', Nullch, 0);
    sv_magic((SV *)GvIOp(request->gv[2]), request->sverr, 'q', Nullch, 0);
    sv_magic((SV *)GvIOp(request->gv[0]), request->svin, 'q', Nullch, 0);
#  else
    sv_magic((SV *)request->gv[1], request->svout, 'q', Nullch, 0);
    sv_magic((SV *)request->gv[2], request->sverr, 'q', Nullch, 0);
    sv_magic((SV *)request->gv[0], request->svin, 'q', Nullch, 0);
#  endif
#endif
    request->bound = TRUE;
}

static void
populate_env(envp, hv)
char **envp;
HV *hv;
{
    int i;
    char *p, *p1;
    SV   *sv;
    dTHX;

    hv_clear(hv);
    for(i = 0; ; i++) {
	if((p = envp[i]) == NULL) {
	    break;
	}
	p1 = strchr(p, '=');
	assert(p1 != NULL);
	sv = newSVpv(p1 + 1, 0);
	/* call magic for this value ourselves */
	hv_store(hv, p, p1 - p, sv, 0);
	SvSETMAGIC(sv);
    }
}

static int
FCGI_IsFastCGI(FCGP_Request* request)
{
    static int isCGI = -1; /* -1: not checked; 0: FCGI; 1: CGI */

    if (request->requestPtr->listen_sock == FCGI_LISTENSOCK_FILENO) {
	if (isCGI == -1)
	    isCGI = FCGX_IsCGI();
	return !isCGI;
    }

    /* A explicit socket is being used -> assume FastCGI */
    return 1;
}

static int 
FCGI_Accept(FCGP_Request* request)
{
    dTHX;

    if (!FCGI_IsFastCGI(request)) {
	static int been_here = 0;

        /*
         * Not first call to FCGI_Accept and running as CGI means
         * application is done.
         */
	if (been_here)
	    return EOF;

	been_here = 1;
    } else {
#ifdef USE_SFIO
	int i;
#endif
	FCGX_Request *fcgx_req = request->requestPtr;
        int acceptResult;

	FCGI_Finish(request);
#if defined(USE_LOCKING) && defined(USE_THREADS)
	MUTEX_LOCK(&accept_mutex);
#endif
	acceptResult = FCGX_Accept_r(fcgx_req);
#if defined(USE_LOCKING) && defined(USE_THREADS)
	MUTEX_UNLOCK(&accept_mutex);
#endif
        if(acceptResult < 0) {
            return acceptResult;
        }

	populate_env(fcgx_req->envp, request->hvEnv);

#ifdef USE_SFIO
	for (i = 0; i < 3; ++i) {
	    request->io[i] = GvIOn(request->gv[i]);
	    if (!(i == 0 ? IoIFP(request->io[i]) 
			 : IoOFP(request->io[i]))) {
		IoIFP(request->io[i]) = sftmp(0);
		/*IoIFP(request->io[i]) = sfnew(NULL, NULL, SF_UNBOUND, 0, 
				     SF_STRING | (i ? SF_WRITE : SF_READ));*/
		if (i != 0) 
		    IoOFP(request->io[i]) = IoIFP(request->io[i]);
		request->sfcreated[i] = TRUE;
	    }
	}
#else
	if (!request->svout) {
	    newSVrv(request->svout = newSV(0), "FCGI::Stream");
	    newSVrv(request->sverr = newSV(0), "FCGI::Stream");
	    newSVrv(request->svin = newSV(0), "FCGI::Stream");
	}
	sv_setiv(SvRV(request->svout), INT2PTR(IV, fcgx_req->out));
	sv_setiv(SvRV(request->sverr), INT2PTR(IV, fcgx_req->err));
	sv_setiv(SvRV(request->svin), INT2PTR(IV, fcgx_req->in));
#endif
	FCGI_Bind(request);
	request->accepted = TRUE;
    }
    return 0;
}

static void 
FCGI_Finish(FCGP_Request* request)
{
#ifdef USE_SFIO
    int i;
#endif
    int was_bound;
    dTHX;

    if(!request->accepted) {
	return;
    }

    if (was_bound = request->bound) {
	FCGI_UndoBinding(request);
    }
#ifdef USE_SFIO
    for (i = 0; i < 3; ++i) {
	if (request->sfcreated[i]) {
	    sfclose(IoIFP(request->io[i]));
	    IoIFP(request->io[i]) = IoOFP(request->io[i]) = Nullfp;
	    request->sfcreated[i] = FALSE;
	}
    }
#endif
    if (was_bound)
	FCGX_Finish_r(request->requestPtr);
    else
	FCGX_Free(request->requestPtr, 1);
    request->accepted = FALSE;
}

static int 
FCGI_StartFilterData(FCGP_Request* request)
{
    return request->requestPtr->in ? 
	    FCGX_StartFilterData(request->requestPtr->in) : -1;
}

static FCGP_Request *
FCGI_Request(in, out, err, env, socket, flags)
    GV*	    in;
    GV*	    out;
    GV*	    err;
    HV*	    env;
    int	    socket;
    int	    flags;
{
    FCGX_Request* fcgx_req;
    FCGP_Request* req;

    Newz(551, fcgx_req, 1, FCGX_Request);
    FCGX_InitRequest(fcgx_req, socket, flags);
    Newz(551, req, 1, FCGP_Request);
    req->requestPtr = fcgx_req;
    SvREFCNT_inc(in);
    req->gv[0] = in;
    SvREFCNT_inc(out);
    req->gv[1] = out;
    SvREFCNT_inc(err);
    req->gv[2] = err;
    SvREFCNT_inc(env);
    req->hvEnv = env;

    return req;
}

static void
FCGI_Release_Request(FCGP_Request *req)
{
    SvREFCNT_dec(req->gv[0]);
    SvREFCNT_dec(req->gv[1]);
    SvREFCNT_dec(req->gv[2]);
    SvREFCNT_dec(req->hvEnv);
    FCGI_Finish(req);
    Safefree(req->requestPtr);
    Safefree(req);
}

static void
FCGI_Init()
{
#if defined(USE_LOCKING) && defined(USE_THREADS)
    dTHX;

    MUTEX_INIT(&accept_mutex);
#endif

    FCGX_Init();
}

typedef FCGX_Stream *	FCGI__Stream;
typedef FCGP_Request *	FCGI;
typedef	GV*		GLOBREF;
typedef	HV*		HASHREF;

MODULE = FCGI		PACKAGE = FCGI	    PREFIX = FCGI_

BOOT:
    FCGI_Init();

SV *
RequestX(in, out, err, env, socket, flags)
    GLOBREF in;
    GLOBREF out;
    GLOBREF err;
    HASHREF env;
    int	    socket;
    int	    flags;

    PROTOTYPE: ***$$$
    CODE:
    RETVAL = sv_setref_pv(newSV(0), "FCGI", 
		FCGI_Request(in, out, err, env, socket, flags));

    OUTPUT:
    RETVAL

int
OpenSocket(path, backlog)
    char* path;
    int backlog;

    PROTOTYPE: $$
    CODE:
    RETVAL = FCGX_OpenSocket(path, backlog);
    OUTPUT:
    RETVAL

void
CloseSocket(socket)
    int socket;

    PROTOTYPE: $
    CODE:
    close(socket);

int
FCGI_Accept(request)

    FCGI    request;

    PROTOTYPE: $

void
FCGI_Finish(request)
    FCGI    request;

    PROTOTYPE: $

void
FCGI_Flush(request)
    FCGI    request;

    PROTOTYPE: $

HV *
GetEnvironment(request)
    FCGI    request;

    PROTOTYPE: $

    CODE:
    RETVAL = request->hvEnv;

    OUTPUT: 
    RETVAL

void
GetHandles(request)
    FCGI    request;

    PROTOTYPE: $

    PREINIT:
    int	    i;

    PPCODE:
    EXTEND(sp,3);
    for (i = 0; i < 3; ++i)
	PUSHs(sv_2mortal(newRV((SV *) request->gv[i])));

int
FCGI_IsFastCGI(request)
    FCGI    request;

    PROTOTYPE: $

void
Detach(request)
    FCGI    request;

    PROTOTYPE: $

    CODE:
    if (request->accepted && request->bound)
	FCGI_UndoBinding(request);

void
Attach(request)
    FCGI    request;

    PROTOTYPE: $

    CODE:
    if (request->accepted && !request->bound)
	FCGI_Bind(request);

void
LastCall(request)
    FCGI    request;

    PROTOTYPE: $

    CODE:
    FCGX_ShutdownPending();

int
FCGI_StartFilterData(request)

    FCGI    request;

    PROTOTYPE: $

void
DESTROY(request)
    FCGI    request;

    CODE:
    FCGI_Release_Request(request);



MODULE = FCGI		PACKAGE = FCGI::Stream

#ifndef USE_SFIO

void
PRINT(stream, ...)
	FCGI::Stream	stream;

	PREINIT:
	int	n;

	CODE:
	for (n = 1; n < items; ++n) {
            STRLEN len;
            register char *tmps = (char *)SvPV(ST(n),len);
            FCGX_PutStr(tmps, len, stream);
	}
	if (SvTRUEx(perl_get_sv("|", FALSE))) 
	    FCGX_FFlush(stream);

int
WRITE(stream, bufsv, len, ...)
	FCGI::Stream	stream;
	SV *	bufsv;
	int	len;

	PREINIT:
	int	offset;
	char *	buf;
	STRLEN	blen;
	int	n;

	CODE:
	offset = (items == 4) ? (int)SvIV(ST(3)) : 0;
	buf = SvPV(bufsv, blen);
	if (offset < 0) offset += blen;
	if (len > blen - offset)
	    len = blen - offset;
	if (offset < 0 || offset >= blen ||
		(n = FCGX_PutStr(buf+offset, len, stream)) < 0) 
	    ST(0) = &PL_sv_undef;
	else {
	    ST(0) = sv_newmortal();
	    sv_setpvf(ST(0), "%c", n);
	}

int
READ(stream, bufsv, len, ...)
	FCGI::Stream	stream;
	SV *	bufsv;
	int	len;

	PREINIT:
	int	offset;
	char *	buf;

	CODE:
	offset = (items == 4) ? (int)SvIV(ST(3)) : 0;
	if (! SvOK(bufsv))
	    sv_setpvn(bufsv, "", 0);
	buf = SvGROW(bufsv, len+offset+1);
	len = FCGX_GetStr(buf+offset, len, stream);
	SvCUR_set(bufsv, len+offset);
	*SvEND(bufsv) = '\0';
	(void)SvPOK_only(bufsv);
	SvSETMAGIC(bufsv);
	RETVAL = len;

	OUTPUT:
	RETVAL

SV *
GETC(stream)
	FCGI::Stream	stream;

	PREINIT:
	int	retval;

	CODE:
	if ((retval = FCGX_GetChar(stream)) != -1) {
	    ST(0) = sv_newmortal();
	    sv_setpvf(ST(0), "%c", retval);
	} else ST(0) = &PL_sv_undef;

bool
CLOSE(stream)
	FCGI::Stream	stream;

#	ALIAS:
#	DESTROY = 1

	CODE:
	RETVAL = FCGX_FClose(stream) != -1;

	OUTPUT:
	RETVAL

#endif
