/* $Id: mod_q.c,v 1.6 2004/02/27 14:36:22 agraef Exp $ */
/* A minimalistic Q module for Apache. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "ap_config.h"
#include "util_script.h"
#ifdef APACHE_RELEASE
#if APACHE_RELEASE >= 20000000
#define APACHE2 1
#endif
#else
#define APACHE2 1
#endif
#ifdef APACHE2
#include "apr_tables.h"
#endif
#include <qint.h>
#define BUFSZ 8192
#define STRSZ 1000
#ifdef _WIN32
#define pipe(fd) _pipe(fd, 256, O_BINARY)
#define DIRSTR "/\\:"
#else
#define DIRSTR "/"
#endif
#ifdef APACHE2
#define STATUS 0,
#else
#define STATUS
#endif
static char *dirname(char *t, char *s)
{
char *s1, *s2 = NULL;
for (s1 = s; *s1; s1++)
if (strchr(DIRSTR, *s1))
s2 = s1+1;
if (s2) {
strncpy(t, s, s2-s);
t[s2-s] = 0;
} else
*t = 0;
return t;
}
static int set_script_env(void *data, const char *key, const char *val)
{
char *envstr = malloc(strlen(key)+strlen(val)+2);
if (envstr) {
sprintf(envstr, "%s=%s", key, val);
if (putenv(envstr)) free(envstr);
}
return TRUE;
}
static int getsfunc(char *buf, int len, void *fp)
{
return fgets(buf, len, (FILE*)fp) != NULL;
}
typedef struct {
request_rec *r;
int fd;
FILE *fp;
} info_t;
static void *writer(void *data)
{
info_t *info = (info_t*)data;
if (ap_should_client_block(info->r)) {
char buf[BUFSZ];
int count;
pthread_testcancel();
while ((count = ap_get_client_block(info->r, buf, BUFSZ)) > 0) {
pthread_testcancel();
write(info->fd, buf, count);
pthread_testcancel();
}
}
close(info->fd);
return (void*)OK;
}
static void *reader(void *data)
{
info_t *info = (info_t*)data;
char msg[STRSZ];
*msg = 0;
if (ap_scan_script_header_err_core(info->r, msg, getsfunc, info->fp) != OK) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, STATUS
info->r, "q header error: %s", msg);
return (void*)500;
}
#ifndef APACHE2
ap_send_http_header(info->r);
#endif
if (!info->r->header_only) {
char buf[BUFSZ];
size_t count = BUFSZ;
pthread_testcancel();
while ((count = fread(buf, 1, BUFSZ, info->fp)) > 0) {
pthread_testcancel();
ap_rwrite(buf, count, info->r);
pthread_testcancel();
}
}
return (void*)OK;
}
static char lastfile[STRSZ];
#ifdef APACHE2
static apr_time_t curtime, lasttime;
#else
static time_t curtime, lasttime;
#endif
static int q_handler(request_rec *r)
{
FILE *fp;
int in[2], out[2], sin, sout;
qexpr x;
long rc = OK;
int status;
pthread_t reader_thr, writer_thr;
info_t info;
char tmp[STRSZ];
if (strcmp(r->handler, "q")) return DECLINED;
/* check that the script file exists */
if ((fp = fopen(r->filename, "r")))
fclose(fp);
else {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, STATUS
r, "could not open file %s", r->filename);
return 404;
}
/* redirect stdin and stdout */
if (pipe(in) || pipe(out) ||
(sin = dup(fileno(stdin))) < 0 || (sout = dup(fileno(stdout))) < 0 ||
dup2(in[0], fileno(stdin)) < 0 || dup2(out[1], fileno(stdout)) < 0 ||
!(fp = fdopen(out[0], "rb"))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, STATUS
r, "q startup error: %s", strerror(errno));
return 500;
}
/* initialize the environment */
ap_add_cgi_vars(r);
ap_add_common_vars(r);
#ifdef APACHE2
apr_table_do(set_script_env, r, r->subprocess_env, NULL);
#else
ap_table_do(set_script_env, r, r->subprocess_env, NULL);
#endif
chdir(dirname(tmp, r->filename));
info.r = r; info.fd = in[1]; info.fp = fp;
/* start the writer thread which feeds the request body into stdin */
if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
goto exit2;
if (pthread_create(&writer_thr, NULL, writer, &info)) {
rc = 500;
goto exit2;
}
/* start the reader thread which digests output from the script */
if (pthread_create(&reader_thr, NULL, reader, &info)) {
pthread_cancel(writer_thr);
pthread_join(writer_thr, NULL);
rc = 500;
goto exit2;
}
/* start the interpreter (only if the script has changed) */
#ifdef APACHE2
curtime = r->finfo.ctime;
#else
curtime = r->finfo.st_ctime;
#endif
if ((lasttime != curtime || strcmp(lastfile, r->filename)) &&
(status = qexecl(r->filename, 0))) {
sprintf(tmp, qstrerror(status), r->filename);
ap_log_rerror(APLOG_MARK, APLOG_ERR, STATUS
r, "q startup error: %s", tmp);
rc = 500;
goto exit;
}
lasttime = curtime;
strncpy(lastfile, r->filename, STRSZ-1);
/* evaluate the cgi_main function if present, check the result */
status = 0;
if ((x = qmksym(qsym(cgi_main)))) {
if (!(x = qevalx(x, &status)) || status) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, STATUS
r, "q execution error: %s", qstrerror(status));
rc = 500;
goto exit;
}
qisint(x, &rc);
qdispose(x);
}
exit:
fflush(stdout);
close(out[1]);
close(fileno(stdout));
if (rc == OK) {
void *res;
/* wait for the reader thread to finish */
pthread_join(reader_thr, &res);
rc = (long)res;
} else {
/* we got an error, cancel the reader thread */
pthread_cancel(reader_thr);
pthread_join(reader_thr, NULL);
}
/* cancel the writer thread now in case it is still active */
pthread_cancel(writer_thr);
pthread_join(writer_thr, NULL);
close(in[0]);
close(fileno(stdin));
goto exit3;
exit2:
fflush(NULL);
close(in[0]);
close(out[1]);
close(fileno(stdin));
close(fileno(stdout));
exit3:
dup2(sin, fileno(stdin));
dup2(sout, fileno(stdout));
close(sin);
close(sout);
fclose(fp);
return rc;
}
#ifdef APACHE2
static void q_register_hooks(apr_pool_t *p)
{
ap_hook_handler(q_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA q_module = {
STANDARD20_MODULE_STUFF,
NULL, /* create per-dir config structures */
NULL, /* merge per-dir config structures */
NULL, /* create per-server config structures */
NULL, /* merge per-server config structures */
NULL, /* table of config file commands */
q_register_hooks /* register hooks */
};
#else
/* Dispatch list of content handlers */
static const handler_rec q_handlers[] = {
{ "q", q_handler },
{ NULL, NULL }
};
/* Dispatch list for API hooks */
module MODULE_VAR_EXPORT q_module = {
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
NULL, /* create per-dir config structures */
NULL, /* merge per-dir config structures */
NULL, /* create per-server config structures */
NULL, /* merge per-server config structures */
NULL, /* table of config file commands */
q_handlers, /* [#8] MIME-typed-dispatched handlers */
NULL, /* [#1] URI to filename translation */
NULL, /* [#4] validate user id from request */
NULL, /* [#5] check if the user is ok _here_ */
NULL, /* [#3] check access by host address */
NULL, /* [#6] determine MIME type */
NULL, /* [#7] pre-run fixups */
NULL, /* [#9] log a transaction */
NULL, /* [#2] header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* [#0] post read-request */
#ifdef EAPI
,NULL, /* EAPI: add_module */
NULL, /* EAPI: remove_module */
NULL, /* EAPI: rewrite_command */
NULL /* EAPI: new_connection */
#endif
};
#endif
syntax highlighted by Code2HTML, v. 0.9.1