/* $Id: mod_q.c,v 1.6 2004/02/27 14:36:22 agraef Exp $ */ /* A minimalistic Q module for Apache. */ #include #include #include #include #include #include #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 #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