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.PL,v 1.7 1999/07/29 12:01:00 skimo Exp $ */

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

#include "fcgiapp.h"

#ifndef FALSE
#define FALSE (0)
#endif

#ifndef TRUE
#define TRUE  (1)
#endif

#ifdef WIN32
#define environ _environ
#endif

extern char **environ;

#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

static int isCGI = -1; /* -1: not checked; 0: FCGI; 1: FCGI */

static FCGX_Request global_fcgx_request;

typedef struct FCGP_Request {
    int		    compat;
    int		    acceptCalled;
    int		    finishCalled;
    SV*		    svin;
    SV*		    svout;
    SV*		    sverr;
    GV*		    gv[3];
    GV*		    gvNew[3];
    HV*		    hvEnv;
    FCGX_Stream*    in;
    FCGX_ParamArray envp;
    FCGX_Request*   requestPtr;
#ifdef USE_SFIO
    int		    sfcreated[3];
#endif
} FCGP_Request;

static FCGP_Request global_request;
static SV*	    global_sverr;

static int 
FCGI_Flush(FCGP_Request* request)
{
    if(!request->acceptCalled || isCGI) {
	return;
    }
#ifdef USE_SFIO
    sfsync(IoOFP(GvIOp(request->gv[1])));
    sfsync(IoOFP(GvIOp(request->gv[2])));
#else
    FCGX_FFlush((FCGX_Stream *) SvIV((SV*) SvRV(request->svout)));
    FCGX_FFlush((FCGX_Stream *) SvIV((SV*) SvRV(request->sverr)));
#endif
}

static void
FCGI_UndoBinding(FCGP_Request* request)
{
#ifdef USE_SFIO
    IO *io[3];
    int i;
#endif

#ifdef USE_SFIO
    sfdcdelfcgi(sfdisc(IoIFP(io[0] = GvIOp(request->gv[0])), SF_POPDISC));
    sfdcdelfcgi(sfdisc(IoOFP(io[1] = GvIOp(request->gv[1])), SF_POPDISC));
    sfdcdelfcgi(sfdisc(IoOFP(io[2] = GvIOp(request->gv[2])), SF_POPDISC));
    for (i = 0; i < 3; ++i) {
	if (request->sfcreated[i]) {
	    sfclose(IoIFP(io[i]));
	    IoIFP(io[i]) = IoOFP(io[i]) = Nullfp;
	    request->sfcreated[i] = FALSE;
	}
    }
#else
    FCGI_Flush(request);
    sv_unmagic((SV *)request->gv[0], 'q');
    sv_unmagic((SV *)request->gv[1], 'q');
    sv_unmagic((SV *)request->gv[2], 'q');
#endif
}

static int 
FCGI_Accept(FCGP_Request* request)
{
    if(isCGI == -1) {
        /*
         * First call to FCGI_Accept.  Is application running
         * as FastCGI or as CGI?
         */
        isCGI = FCGX_IsCGI();
    } else if(isCGI) {
        /*
         * Not first call to FCGI_Accept and running as CGI means
         * application is done.
         */
        return(EOF);
    } 
    if(request->acceptCalled && !request->finishCalled) {
	FCGI_UndoBinding(request);
    }
    if(!isCGI) {
#ifdef USE_SFIO
	IO *io[3];
	int i;
#endif
	FCGX_Stream *out, *error;
        int acceptResult = FCGX_Accept_r(&request->in, &out, &error, 
					 &request->envp,
				         request->requestPtr);
        if(acceptResult < 0) {
            return acceptResult;
        }
#ifdef USE_SFIO
	for (i = 0; i < 3; ++i) {
	    io[i] = GvIOn(request->gv[i] = request->gvNew[i]);
	    if (!(i == 0 ? IoIFP(io[i]) : IoOFP(io[i]))) {
		IoIFP(io[i]) = sftmp(0);
		/*IoIFP(io[i]) = sfnew(NULL, NULL, SF_UNBOUND, 0, 
				     SF_STRING | (i ? SF_WRITE : SF_READ));*/
		if (i != 0) IoOFP(io[i]) = IoIFP(io[i]);
		request->sfcreated[i] = TRUE;
	    }
	}
        sfdisc(IoIFP(io[0]), sfdcnewfcgi(request->in));
        sfdisc(IoOFP(io[1]), sfdcnewfcgi(out));
        sfdisc(IoOFP(io[2]), sfdcnewfcgi(error));
#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_magic((SV *)request->gv[1] = request->gvNew[1], 
		request->svout, 'q', Nullch, 0);
	sv_magic((SV *)request->gv[2] = request->gvNew[2], 
		request->sverr, 'q', Nullch, 0);
	sv_magic((SV *)request->gv[0] = request->gvNew[0], 
		request->svin, 'q', Nullch, 0);
	sv_setiv(SvRV(request->svout), (IV) out);
	sv_setiv(SvRV(request->sverr), (IV) error);
	sv_setiv(SvRV(request->svin), (IV) request->in);

	if (request->compat) {
	    global_sverr = request->sverr;
	    if (PL_warnhook) SvREFCNT_dec(PL_warnhook);
	    PL_warnhook = SvREFCNT_inc(GvCV(gv_fetchmethod(Nullhv, "FCGI::WARN")));
	    if (PL_diehook) SvREFCNT_dec(PL_diehook);
	    PL_diehook = SvREFCNT_inc(GvCV(gv_fetchmethod(Nullhv, "FCGI::DIE")));
	}
#endif
	request->finishCalled = FALSE;
    }
    request->acceptCalled = TRUE;
    return 0;
}

static void 
FCGI_Finish(FCGP_Request* request)
{
    if(!request->acceptCalled || isCGI) {
	return;
    }
    FCGI_UndoBinding(request);
    request->in = NULL;
    FCGX_Finish_r(request->requestPtr);
    request->finishCalled = TRUE;
#ifndef USE_SFIO
    if (request->compat) {
	if (PL_warnhook == (SV*)GvCV(gv_fetchmethod(Nullhv, "FCGI::WARN"))) {
	    SvREFCNT_dec(PL_warnhook);
	    PL_warnhook = Nullsv;
	}
	if (PL_diehook == (SV*)GvCV(gv_fetchmethod(Nullhv, "FCGI::DIE"))) {
	    SvREFCNT_dec(PL_diehook);
	    PL_diehook = Nullsv;
	}
    }
#endif
}

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

static void
FCGI_SetExitStatus(FCGP_Request* request, int status)
{
    if (request->in) FCGX_SetExitStatus(status, request->in);
}

static FCGP_Request *
FCGI_Request()
{
    FCGX_Request* fcgx_req;
    FCGP_Request* req;

    Newz(551, fcgx_req, 1, FCGX_Request);
    Newz(551, req, 1, FCGP_Request);
    req->requestPtr = fcgx_req;

    return req;
}

static void
FCGI_Release_Request(FCGP_Request *req)
{
    Safefree(req->requestPtr);
    Safefree(req);
}

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

    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);
    }
}

/*
 * For each variable in the array envp, either set or unset it
 * in the global hash %ENV.
 */
static void
DoPerlEnv(envp, set)
char **envp;
int set;
{
    if (!set)
	perl_eval_pv("%ENV = %FCGI::ENV", 0);
    else {
	perl_eval_pv("%FCGI::ENV = %ENV", 0);
	populate_env(envp, perl_get_hv("ENV", TRUE));
    }
}

#define REQUEST_ARG(arg,request) \
	if (sv_isa(ST(arg), "FCGI")) { \
	    request = (FCGP_Request*) SvIV((SV*)SvRV(ST(arg))); \
	} else \
	    croak("request is not of type FCGI")

typedef FCGX_Stream *	FCGI__Stream;
typedef FCGP_Request *	FCGI;

MODULE = FCGI		PACKAGE = FCGI

BOOT:
    FCGX_Init();
    FCGX_InitRequest(&global_fcgx_request);
    memset(&global_request, 0, sizeof(global_request));
    global_request.compat = 1;
    global_request.requestPtr = &global_fcgx_request;
    global_request.gvNew[0] = gv_fetchpv("STDIN",TRUE, SVt_PVIO);
    global_request.gvNew[1] = gv_fetchpv("STDOUT",TRUE, SVt_PVIO);
    global_request.gvNew[2] = gv_fetchpv("STDERR",TRUE, SVt_PVIO);


SV *
request()

    PROTOTYPE:
    CODE:
    RETVAL = Perl_sv_setref_pv(Perl_newSV(0), "FCGI", FCGI_Request());

    OUTPUT:
    RETVAL


int
accept(...)

    PROTOTYPE: ;$***$

    PREINIT:
    FCGP_Request* request = &global_request;

    CODE:
    if (items != 0 && items != 5)
	croak("Usage: FCGI::accept() or "
	      "FCGI::accept(request, IN, OUT, ERR, env)");
    if (items) {
	static const char* names[] = {"", "IN", "OUT", "ERR"};
	int i;

	REQUEST_ARG(0,request);
	for(i = 1; i <= 3; ++i) {
	    if (SvROK(ST(i)) && isGV(SvRV(ST(i)))) {
		request->gvNew[i-1] = (GV*)SvRV(ST(i));
	    } else
		croak("%s is not a GLOB reference", names[i]);
	}
	if (SvROK(ST(4)) && SvTYPE(SvRV(ST(4))) == SVt_PVHV) {
	    request->hvEnv = (HV*)SvRV(ST(4));
	} else
	    croak("env is not a reference to a hash");
    }
    {
        int acceptStatus;
        /*
         * Unmake Perl variable settings for the request just completed.
         */
        if(request->envp != NULL) {
            DoPerlEnv(request->envp, FALSE);
            request->envp = NULL;
        }
        /*
         * Call FCGI_Accept but preserve environ.
         */
        acceptStatus = FCGI_Accept(request);
        /*
         * Make Perl variable settings for the new request.
         */
        if(acceptStatus >= 0 && !isCGI) {
	    if (request->compat)
		DoPerlEnv(request->envp, TRUE);
	    else {
		populate_env(request->envp, request->hvEnv);
		request->envp = NULL;
	    }
        } else {
            request->envp = NULL;
        }
        RETVAL = acceptStatus;
    }
    OUTPUT:
    RETVAL


void
finish(...)

    PROTOTYPE: ;$

    PREINIT:
    FCGP_Request* request = &global_request;

    CODE:
    if (items != 0 && items != 1)
	croak("Usage: FCGI::finish() or "
	      "FCGI::finish(request)");
    if (items) {
	REQUEST_ARG(0,request);
    }
    {
        /*
         * Unmake Perl variable settings for the completed request.
         */
        if(request->envp != NULL) {
            DoPerlEnv(request->envp, FALSE);
            request->envp = NULL;
        }
        /*
         * Finish the request.
         */
        FCGI_Finish(request);
    }


void
flush(...)

    PROTOTYPE: ;$

    PREINIT:
    FCGP_Request* request = &global_request;

    CODE:
    if (items != 0 && items != 1)
	croak("Usage: FCGI::flush([request])");
    if (items) {
	REQUEST_ARG(0,request);
    }
    FCGI_Flush(request);

void
set_exit_status(...)

    PROTOTYPE: $;$

    PREINIT:
    FCGP_Request* request = &global_request;
    int status;

    CODE:
    if (items != 1 && items != 2)
	croak("Usage: FCGI::set_exit_status(status[,request])");
    if (items == 2) {
	REQUEST_ARG(0,request);
    }
    status = (int)SvIV(ST(items-1));
    FCGI_SetExitStatus(request, status);

int
start_filter_data(...)

    PROTOTYPE: ;$

    PREINIT:
    FCGP_Request* request = &global_request;

    CODE:
    if (items != 0 && items != 1)
	croak("Usage: FCGI::flush([request])");
    if (items) {
	REQUEST_ARG(0,request);
    }
    RETVAL = FCGI_StartFilterData(request);

    OUTPUT:
    RETVAL


void
DESTROY(request)
    FCGI    request;

    CODE:
    FCGI_Release_Request(request);



MODULE = FCGI		PACKAGE = FCGI::Stream

#ifndef USE_SFIO
void
DIE(msg)
	char *	msg;

	CODE:
	if (!PL_in_eval) /* ? maybe !(PL_in_eval & 1) */
	    FCGX_PutS(msg, (FCGX_Stream *) SvIV((SV*) SvRV(global_sverr)));

void
WARN(msg)
	char *	msg;

	CODE:
	FCGX_PutS(msg, (FCGX_Stream *) SvIV((SV*) SvRV(global_sverr)));

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
