/* $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