/*
 * TUX - Integrated HTTP layer and Object Cache
 *
 * Copyright (C) 2000, Ingo Molnar <mingo@redhat.com>
 *
 * CAD.c: Implementation of the SPECweb99 dynamic web application
 *	using TUXAPI.
 */

#include "tuxmodule.h"

#undef BUG
#define BUG()							\
do {								\
	printf("TUX BUG at %d:%s!\n", __LINE__, __FILE__);	\
	exit(-1);						\
} while (0)

#include <time.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/sendfile.h>

static inline int send_reply_head (user_req_t *req, size_t body_size);
static inline int send_reply_tail (user_req_t *req);

static unsigned int last_mtime;

#define POSTLOG "/postlog"

static int postlog_file;
static int *postlog_sem;
int this_pid;

#define CAD_TAG_HEAD	"<!WEB99CAD><IMG SRC=\"/file_set/"
#define CAD_TAG_BODY	"dirNNNNN/classX_Y"
#define CAD_TAG_TAIL	"\"><!/WEB99CAD>"

#define CAD_TAG CAD_TAG_HEAD CAD_TAG_BODY CAD_TAG_TAIL

#define CAD_TAG_BODY_POS (sizeof(CAD_TAG_HEAD)-1)
#define CAD_TAG_BODY_LEN (sizeof(CAD_TAG_BODY)-1)
#define CAD_TAG_TAIL_LEN (sizeof(CAD_TAG_TAIL)-1)
#define CAD_TAG_LEN (sizeof(CAD_TAG)-1)

typedef struct CAD_struct {
	int user_id;
	int last_ad;
	char ad_filename [100];
	int reply_cookies_len;
	char reply_cookies [MAX_COOKIE_LEN];
} CAD_t;

static int my_send (int sock, char *buf, int total_len, int flags)
{
	int len = total_len, ret;

repeat:
	ret = send(sock, buf, len, flags);
	if (ret == -EAGAIN)
		goto repeat;
	if (ret <= 0)
		return ret;
	len -= ret;
	buf += ret;
	if (len)
		goto repeat;
	return total_len;
}

#define sock_write_push(s,b,l) my_send(s, b, l, 0x0000)
#define sock_write_more(s,b,l) my_send(s, b, l, 0x8000)

/*
 * Determine whether the URL is class1 or 2;
 */
static inline int is_class12 (char *str)
{
	unsigned char *tmp;

	tmp = strstr(str, "class");
	if (tmp) {
		tmp += sizeof("class")-1;
		if ((tmp[0] != '1') && (tmp[0] != '2'))
			return 0;
		return 1;
	}
	return 0;
}

/*
 * Return SPECweb99 error message.
 */
static int error (user_req_t *req, char *message)
{
	CAD_t *CADp = (CAD_t *)req->priv;
	int len = strlen(message);
	int bytes, ret;

	printf("---=> error(), from %p: %s\n<=---\n",
				__builtin_return_address(0), message);
	/*
	 * Return a -1 Ad_id in the reply cookie.
	 */
	if (CADp)
		CADp->reply_cookies_len = sprintf(CADp->reply_cookies,
				"found_cookie=Ad_id=-1&Ad_weight=0&Expired=1");

	bytes = send_reply_head(req, len);
	ret = sock_write_more(req->sock, message, len);
	if (ret != len) {
		printf("error(%s), did not succeed (ret %p, err %d).\n",
			message, __builtin_return_address(0), ret);
	}

	bytes += len;
	bytes += send_reply_tail(req);

	req->bytes_sent = bytes;
	return 0;
}

typedef struct user_dem_s {
	unsigned int dem;
} user_dem_t;

static int max_userid;
static user_dem_t *user_dem;

#define USER_PERS_FILE "/User.Personality"
#define USER_PERS_RECLEN 15

typedef struct ad_s {
	unsigned int dem;
	unsigned int gender_weight;
	unsigned int age_weight;
	unsigned int region_weight;
	unsigned int interest_1_weight;
	unsigned int interest_2_weight;
	unsigned int min_match;
	unsigned int expires;
} ad_t;

static int max_adid;
static ad_t *ad;

#define AD_FILE "/Custom.Ads"
#define AD_RECLEN 39

static int read_custom_ads (user_req_t *req)
{
	int file;
	int ret, len, err = -2;
	char *buf = NULL, *tmp;
	unsigned int adid, i, dem, min_match, weight, expires;
	struct stat sbuf;

	file = open(AD_FILE, O_RDONLY);
	if (file == -1)
		goto error;

	if (fstat(file, &sbuf))
		goto error;
	len = sbuf.st_size;

	if (!len)
		goto error;
	if (ad) {
		free(ad);
		ad = NULL;
	}
	max_adid = len/AD_RECLEN + 1;
	ad = malloc((max_adid) * sizeof(ad_t));
	buf = malloc(len);
	if (!ad || !buf)
		goto error;

	ret = read(file, buf, len);
	close(file);
	if (ret != len)
		goto error;
/*
 * Sample ad record:
	"   54 24808100    97F61  75  952393980\n"
 */

	tmp = buf;
	i = 0;
	adid = 0;
	for (tmp = buf; tmp < buf+len; tmp++) {

		while (*tmp == ' ') tmp++;
		adid = strtoul(tmp, &tmp, 10);
		if (adid != i)
			goto error;
		if (adid >= max_adid)
			goto error;
		i++;
		if (*tmp != ' ')
			goto error;
		tmp++;
		while (*tmp == ' ') tmp++;
		dem = strtoul(tmp, &tmp, 16);
		tmp++;
		while (*tmp == ' ') tmp++;
		weight = strtoul(tmp, &tmp, 16);
		while (*tmp == ' ') tmp++;
		min_match = strtoul(tmp, &tmp, 10);
		while (*tmp == ' ') tmp++;
		expires = strtoul(tmp, &tmp, 10);
		if (*tmp != '\n')
			goto error;
		ad[adid].dem = dem;

		ad[adid].gender_weight		= (weight & 0x000f0000) >> 16;
		ad[adid].age_weight		= (weight & 0x0000f000) >> 12;
		ad[adid].region_weight		= (weight & 0x00000f00) >> 8;
		ad[adid].interest_1_weight	= (weight & 0x000000f0) >> 4;
		ad[adid].interest_2_weight	= (weight & 0x0000000f);

		ad[adid].min_match = min_match;
		ad[adid].expires = expires;

	}
	err = 0;
error:
	if (buf)
		free(buf);
	if (err)
		return error(req, "error while parsing the ad file.\n");
	return err;
}

static int read_user_personality (user_req_t *req)
{
	int file;
	int ret, len, err = -2;
	char *buf = NULL, *tmp;
	unsigned int uid, i, dem;
	struct stat sbuf;

	file = open(USER_PERS_FILE, O_RDONLY);
	if (file == -1)
		goto error;
	if (fstat(file, &sbuf))
		goto error;
	len = sbuf.st_size;

	if (!len)
		goto error;
	if (user_dem) {
		free(user_dem);
		user_dem = NULL;
	}
	max_userid = len/USER_PERS_RECLEN + 1;
	user_dem = malloc((max_userid) * sizeof(user_dem_t));
	buf = malloc(len);
	if (!user_dem || !buf)
		goto error;

	ret = read(file, buf, len);
	close(file);
	if (ret <= 0)
		goto error;

	tmp = buf;
	i = 0;
	for (tmp = buf; tmp != buf+len; tmp++) {
		if (*tmp == ' ')
			continue;
		uid = strtoul(tmp, &tmp, 10);
		if (uid != i)
			goto error;
		if (uid >= max_userid)
			goto error;
		i++;
		if (*tmp != ' ')
			goto error;
		while (*tmp == ' ') tmp++;
		dem = strtoul(tmp, &tmp, 16);
		if (*tmp != '\n')
			goto error;
		(user_dem)[uid].dem = dem;
	}
	err = 0;
error:
	if (buf)
		free(buf);
	if (err)
		return error(req, "error while parsing the user file.\n");
	return err;
}

#define MAX_CUSTOM_ADS 360

static inline int find_ad (int user_id, int last_ad, int *weight_p)
{
	int adid, weight = 0, dem;

	if ((user_id < 0) || (user_id > max_userid))
		return -1;
	for (adid = last_ad + 1; adid != last_ad; adid++) {
		if (adid >= MAX_CUSTOM_ADS) {
			adid = 0;
			if (last_ad == 0)
				break;
		}

		dem = (user_dem)[user_id].dem & ad[adid].dem;
		weight = 0;

		if (dem & 0x30000000)
			weight += ad[adid].gender_weight;
		if (dem & 0x0f000000)
			weight += ad[adid].age_weight;
		if (dem & 0x00f00000)
			weight += ad[adid].region_weight;
		if (dem & 0x000ffc00)
			weight += ad[adid].interest_1_weight;
		if (dem & 0x000003ff)
			weight += ad[adid].interest_2_weight;
		if (weight >= ad[adid].min_match)
			break;
	}

	*weight_p = weight;
	return adid;
}

static inline int reread_files (user_req_t *req)
{
	int ret = -2;
	struct stat buf;

	stat(USER_PERS_FILE, &buf);

	if (buf.st_mtime != last_mtime) {
		void *tmp = user_dem;

		user_dem = NULL;
		free(tmp);
		if (read_user_personality(req))
			goto error;
		if (read_custom_ads(req))
			goto error;
		last_mtime = buf.st_mtime;
	}
	ret = 0;

error:
	return ret;
}

#define TOKEN_EQUAL(input,token) \
		(!memcmp(input, token, sizeof(token)-1))

#define PARSE_STRING(token,input,output)				\
	({								\
		int __ret = 0;						\
		if (TOKEN_EQUAL(input, token)) {			\
			char *tmp;					\
									\
			input += sizeof(token)-1;			\
			tmp = output;					\
			while (*input && *input != '&' &&		\
						*input != ',')		\
				*tmp++ = *input++;			\
			*tmp = 0;					\
			__ret = 1;					\
		}							\
		__ret;							\
	})

#define PARSE_UINT(token,input,output)					\
	({								\
		int __ret = 0;						\
		if (TOKEN_EQUAL(input, token)) {			\
									\
			input += sizeof(token)-1;			\
			output = strtoul(input, &input, 10);	\
			__ret = 1;					\
		}							\
		__ret;							\
	})

static void open_postlog_file (user_req_t *req)
{
	if (postlog_file != -1)
		error(req, "POST-log already exists!\n");
	postlog_file = open(POSTLOG, O_RDWR);
	if ((postlog_file) == -1)
		error(req, "could not open POST-log!\n");
}

static int init_postlog_file (user_req_t *req)
{
	char count_buf[11];

	if (postlog_file != -1)
		close(postlog_file);
	postlog_file = open(POSTLOG, O_CREAT|O_TRUNC|O_RDWR, S_IRWXU);
	if ((postlog_file) == -1)
		return error(req, "could not creat POST-log!\n");
	sprintf(count_buf, "%10d\n", 0);
	if (write(postlog_file, count_buf, 11) != 11)
		return error(req, "could not sock_write POST-log!\n");
	return 0;
}

#define COMMAND_STRING "command/"
#define COMMAND_RESET "Reset"
#define COMMAND_FETCH "Fetch"

static int do_reset (user_req_t *req, char *query)
{
	char maxload [20], pttime[20], maxthread[20],
			exp1[20], exp2[20], urlroot [100];
	char tmpstr1[256], tmpstr2[256];

	sleep(1);
	if (!PARSE_STRING("&maxload=", query, maxload))
		return error(req,"invalid &maxload field!\n");
	if (!PARSE_STRING("&pttime=", query, pttime))
		return error(req,"invalid &pttime field!\n");
	if (!PARSE_STRING("&maxthread=", query, maxthread))
		return error(req,"invalid &maxthread field!\n");
	if (!PARSE_STRING("&exp=", query, exp1))
		return error(req,"invalid &exp1 field!\n");
	if (!PARSE_STRING(",", query, exp2))
		return error(req,"invalid &exp2 field!\n");
	if (!PARSE_STRING("&urlroot=", query, urlroot))
		return error(req,"invalid &urlroot field!\n");

#define TOPDIR "/"
// TUXAPI_docroot

	strcpy(tmpstr1, TOPDIR); strcat(tmpstr1, "/upfgen99");
	strcpy(tmpstr2, TOPDIR); strcat(tmpstr2, "/cadgen99");

#define UPFGEN tmpstr1
#define CADGEN tmpstr2

	{
		int pid = 0;
		char *argv_upfgen[] = { UPFGEN, "-C", TOPDIR, "-n", maxload,
					"-t", maxthread, NULL};
		char *argv_cadgen[] = { CADGEN, "-C", TOPDIR, "-e", pttime,
					"-t", maxthread, exp1, exp2, NULL};
		char * envp[] = { "HOME=/", "TERM=linux",
			"PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };

		pid = fork();
		if (!pid)
			if (execve(UPFGEN, argv_upfgen, envp) < 0)
				return error(req,"could not execute UPFGEN!\n");
		waitpid(pid, NULL, 0);

		pid = fork();
		if (!pid)
			if (execve(CADGEN, argv_cadgen, envp) < 0)
				return error(req,"could not execute CADGEN!\n");
		waitpid(pid, NULL, 0);
	}
	/*
	 * Clear post log
	 */
	init_postlog_file(req);

	/*
	 * mtime has a 1 second resolution, sleep 1 second so that
	 * the check for modified User.Personality and Custom.Ads
	 * files notices multiple resets correctly.
	 */
	sleep(1);
	last_mtime = 0;
	reread_files(req);

	req->bytes_sent = send_reply_head(req, 0);
	req->bytes_sent += send_reply_tail(req);

	return 0;
}

/*
** #define BLOCKLEN 8192
*/
#define BLOCKLEN 24576

static int send_postlog (user_req_t *req)
{
	int file;
	int delay=0;
	int ret, len, bytes, bytes_sent, bytes_read;
	struct stat sbuf;
	char buf[BLOCKLEN], count_buf[10];

	file = open(POSTLOG, O_RDWR);
	if (file == -1)
		return error(req, "no POST-log file!\n");

	if (fstat(file, &sbuf))
		return error(req, "fstat failed!\n");
	len = sbuf.st_size;

	sprintf(count_buf, "%10d", len / 139);
	lseek(file, 0, SEEK_SET);
	if (write(file, count_buf, 10) != 10)
		return error(req, "write postlog count failed!\n");
	lseek(file, 0, SEEK_SET);


	bytes = send_reply_head(req, len);

	for (bytes_sent = 0, delay = 0; bytes_sent < len;
				bytes_sent+= bytes_read, delay += bytes_read) {

		bytes_read = read(file, buf, BLOCKLEN);
		if (bytes_read <= 0)
			return error(req, "postlog read failed!\n");
		if (delay > 5000000) {
			delay = 0;
			sleep(1);
		}
		ret = write(req->sock, buf, bytes_read);
		if (ret != bytes_read)
			return error(req, "postlog write failed!\n");
	}

	bytes += bytes_sent;

	close(file);
	bytes += send_reply_tail(req);
	req->bytes_sent = bytes;
	sleep(2);
	return 0;
}

static int do_command (user_req_t *req, char *query)
{
	if (TOKEN_EQUAL(query, COMMAND_RESET))
		return do_reset(req, query + sizeof(COMMAND_RESET)-1);
	if (TOKEN_EQUAL(query, COMMAND_FETCH)) {
		int ret;

		TUXAPI_down(postlog_sem);
		ret = send_postlog(req);
		TUXAPI_up(postlog_sem);
		return ret;
	}
	return error(req,"got invalid command!\n");
}

static CAD_t * parse_GET_cookies (user_req_t *req)
{
	int uid, last_ad;
	CAD_t *CADp;
	char *tmp;

	if (!req->cookies_len)
		return NULL;

	if (req->priv)
		error(req, "error, priv structure already exists!\n");
	CADp = malloc(sizeof(*CADp));
	req->priv = CADp;
	CADp->reply_cookies_len = 0;

	tmp = req->cookies + sizeof("my_cookie=user_id=")-1;
	uid = strtoul(tmp, &tmp, 10) - 10000;

	tmp += sizeof("&last_ad=")-1;
	last_ad = strtoul(tmp, &tmp, 10);

	CADp->user_id = uid;
	CADp->last_ad = last_ad;

	return CADp;
}

static inline int custom_ad_rotate (user_req_t *req, CAD_t *CADp)
{
	int adid, weight, expired;
	int user_id, last_ad, err;
	time_t now;

	user_id = CADp->user_id;
	last_ad = CADp->last_ad;

	err = reread_files(req);
	if (err)
		return err;

	/*
	 * Any error in either reading or parsing of the files results
	 * in a returned -1 adid.
	 */
	adid = -1;
	expired = 1;
	weight = 0;

	adid = find_ad(user_id, last_ad, &weight);
	if (adid < 0)
		goto error;
	now = time(NULL);
	if (now <= ad[adid].expires)
		expired = 0;

error:
	CADp->reply_cookies_len = sprintf(CADp->reply_cookies,
		"found_cookie=Ad_id=%d&Ad_weight=%d&Expired=%d",
			adid, weight, expired);

	sprintf(CADp->ad_filename, "dir%05d/class%d_%d",
		adid / 36, ((adid % 36) / 9), adid % 9);
	return 0;
}

/*
 * This function reads the object, scans the temporary buffer for
 * the SPECweb99 tag string, does CAD replacement and sends the
 * result out to the client.
 */
static int scan_send_file (user_req_t *req)
{
	char *tmpbuf, *target, *last_target;
	unsigned int size, scan_size, ret, len;
	int left;
	CAD_t *CADp = (CAD_t *) req->priv;
	off_t offset = 0;
	int fd;

	if (!req->objectname[0])
		BUG();
	if (!req->objectlen)
		BUG();
	fd = open(req->objectname, O_RDONLY);
	if (fd < 0) {
		printf("objectname: %s cannot be opened. (error %d, errno %d, sock %d)\n", req->objectname, fd, errno, req->sock);
		BUG();
	}
	tmpbuf = TUXAPI_alloc_read_objectbuf(req, fd);
	if (!tmpbuf)
		BUG();

	size = req->objectlen;
	scan_size = size - CAD_TAG_LEN + 1;

	target = tmpbuf;
	last_target = tmpbuf;

	for (;;) {
		left = scan_size - (target - tmpbuf);
		if (left < 0) {
			printf("HUH?? %p (left: %d)\n", target, left);
			BUG();
		}
		if (!left)
			break;
		target = memchr(target, CAD_TAG[0], left);
		if (!target)
			break; // no such character left

		if (memcmp(target, CAD_TAG, CAD_TAG_LEN)) {
			target++; // skip past the '<'
			continue;
		}
		target += CAD_TAG_BODY_POS;
		len = target-last_target;
repeat_sendfile:
		ret = sendfile(req->sock, fd, &offset, len);
		if (ret != len) {
//			printf("sendfile (sock %d) returned: %d, != %d.\n", req->sock, ret, len);
			if (ret > 0) {
				len -= ret;
				goto repeat_sendfile;
			}
			BUG();
		}

		len = CAD_TAG_BODY_LEN;
		ret = sock_write_more(req->sock, CADp->ad_filename, len);
		if (ret != len)
			BUG();
		offset += len;
		last_target = target + CAD_TAG_BODY_LEN;
		target += CAD_TAG_BODY_LEN + CAD_TAG_TAIL_LEN;
	}

	len = size-(last_target-tmpbuf);
repeat_sendfile2:
	ret = sendfile(req->sock, fd, &offset, len);
	if (ret != len) {
//		printf("sendfile #2 returned: %d, != %d.\n", ret, len);
		if (ret > 0) {
			len -= ret;
			goto repeat_sendfile2;
		}
		BUG();
	}
	TUXAPI_free_objectbuf(req, tmpbuf);
	close(fd);

	return size;
}

static int do_POST (user_req_t *req)
{
	char post_data[301], buf[400], count_buf[10],
		urlroot[100], *tmp, *curr;
	int dir = -1, class = -1, num = -1, client = -1;
	CAD_t *CADp = (CAD_t *) req->priv;
	int size, ret;

	req->object_addr = post_data;
	req->objectlen = 300;
	ret = tux(TUX_ACTION_READ_POST_DATA, req);
	if (ret < 0)
		BUG();
	post_data[req->objectlen] = 0;
	if (!req->objectlen)
		goto parse_error;
	curr = post_data;

#define POST_URLROOT "urlroot="
#define POST_CLASS "class="
#define POST_CLIENT "client="
#define POST_DIR "dir="
#define POST_NUM "num="

	for (;;) {
		switch (*curr) {
			case 'u':
				if (PARSE_STRING( POST_URLROOT, curr, urlroot))
					continue;
				goto parse_error;
			case 'c':
				if (PARSE_UINT( POST_CLASS, curr, class))
					continue;
				if (PARSE_UINT( POST_CLIENT, curr, client))
					continue;
				goto parse_error;
			case 'd':
				if (PARSE_UINT( POST_DIR, curr, dir))
					continue;
				goto parse_error;
			case 'n':
				if (PARSE_UINT( POST_NUM, curr, num))
					continue;
				goto parse_error;
			case '&':
				curr++;
				continue;
			case 0:
				goto out;
			default:
				goto parse_error;
		}
		goto parse_error;
	}
out:
	if (!CADp)
		goto parse_error;
	tmp = CADp->ad_filename;
	tmp += sprintf(tmp, "%sdir%05d/class%d_%d", urlroot, dir, class, num);

	/*
	 * Acquire lock guaranteeing atomic operations
	 * on the postlog file.
	 */
	TUXAPI_down(postlog_sem);
	if ((postlog_file) == -1)
		open_postlog_file(req);
	if (postlog_file == -1)
		goto file_error;

	/* Go to the end of file, and add a POST record */
	size = lseek (postlog_file, 0, SEEK_END);
	if (size == -1)
		goto file_error;
	sprintf(count_buf, "%10d", size/139 + 1);

	tmp = buf;
	tmp += sprintf(tmp, "%10d %10ld %10d %5d %2d %2d %10d %-60.60s %10d %10d\n", atoi(count_buf), time(NULL), this_pid, dir, class, num, client, CADp->ad_filename, this_pid, CADp->user_id + 10000);

	ret = write(postlog_file, buf, tmp-buf);
	lseek(postlog_file, 0, SEEK_SET);
	ret += write(postlog_file, count_buf, 10);
	TUXAPI_up(postlog_sem);

	if (ret != tmp-buf+10)
		goto sock_write_error;

	CADp->reply_cookies_len = sprintf(CADp->reply_cookies,
			"my_cookie=%u", 10000 + CADp->user_id);
	return 0;

parse_error:
	return error(req,"error while parsing POST request!\n");

file_error:
	TUXAPI_up(postlog_sem);
	return error(req, "POST-log file error!\n");

sock_write_error:
	return error(req, "POST-log sock_write error!\n");
}

static int CAD_query (user_req_t *req)
{
	int ret = 0;
	CAD_t *CADp;

	if (req->priv)
		goto CADp_error;
	CADp = parse_GET_cookies(req);

	if (req->http_method == METHOD_POST) {
		req->event = 3;
		if (!CADp)
			goto CADp_error;
		ret = do_POST(req);
		if (ret)
			return ret;
		CADp = (CAD_t *)req->priv;
		strcpy(req->objectname, CADp->ad_filename);
	} else {
		char *tmp = req->query;

		if (req->http_method != METHOD_GET)
			goto url_error;
		if (TOKEN_EQUAL(req->query, COMMAND_STRING)) {
			req->event = 3;
			tmp += sizeof(COMMAND_STRING)-1;
			return do_command(req, tmp);
		}
		strcpy(req->objectname, req->query);
		if (CADp && (CADp->user_id != -1)) {
			ret = custom_ad_rotate(req, CADp);
			if (ret)
				goto CAD_error;
		}
	}

	req->event = 1;
	ret = tux(TUX_ACTION_GET_OBJECT, req);
	if (ret < 0)
		goto get_object_error;

	return ret;

url_error:
	return error(req, "error while parsing CAD request!\n");

get_object_error:
	return error(req, "error while trying to fetch URL object!\n");
CADp_error:
	return error(req, "CADp error!\n");
CAD_error:
	return error(req, "CAD-rotate error!\n");
}

#define REPLY_HEAD_HEAD \
	"HTTP/1.1 200 OK\r\n" \
	"Content-Type: text/html\r\n" \
	"Connection: Keep-Alive\r\n" \
	"Content-Length: %d\r\n\r\n"

#define REPLY_HEAD_HEAD_COOKIE \
	"HTTP/1.1 200 OK\r\n" \
	"Content-Type: text/html\r\n" \
	"Connection: Keep-Alive\r\n" \
	"Content-Length: %d\r\n" \
	"Set-Cookie: %s\r\n" \
	"\r\n"

#define REPLY_HEAD_TAIL \
	"<html>\n" \
	"<head><title>SPECweb99 Dynamic GET & POST Test</title></head>\n"\
	"<body>\n" \
	"<p>SERVER_SOFTWARE = %s\n" \
	"<p>REMOTE_ADDR = %d.%d.%d.%d\n" \
	"<p>SCRIPT_NAME = %s\n" \
	"<p>QUERY_STRING = %s\n" \
	"<pre>\n"

#define REPLY_TAIL \
	"\n</pre>\n" \
	"</body></html>\n"

static int inline send_reply_head (user_req_t *req, size_t body_size)
{
	int ret;
	char buf [1000];
	char *tmp, *head, *tail;
	CAD_t *CADp = (CAD_t *)req->priv;
	unsigned int host, head_len, tail_len, total_len;

	host = req->client_host;

#define IP(x) (((unsigned char *)&host)[x])

	tmp = tail = buf;
	tmp += sprintf(tmp, REPLY_HEAD_TAIL, TUXAPI_version,
		IP(0), IP(1), IP(2), IP(3), TUXAPI_modulename_array[req->module_index], req->query);

	tail_len = tmp-buf;

	total_len = tail_len + sizeof(REPLY_TAIL)-1 + body_size;

	tmp = (char *)(((unsigned int)tmp + sizeof(int)-1)&~(sizeof(int)-1));
	head = tmp;
	if (CADp && CADp->reply_cookies_len)
		tmp += sprintf(tmp, REPLY_HEAD_HEAD_COOKIE,
					total_len, CADp->reply_cookies);
	else
		tmp += sprintf(tmp, REPLY_HEAD_HEAD, total_len);

	head_len = tmp-head;
	ret = sock_write_more(req->sock, head, head_len);
	if (ret != head_len)
		goto sock_write1_err;
	ret = sock_write_more(req->sock, tail, tail_len);
	if (ret != tail_len)
		goto sock_write2_err;
	req->http_status = 200;

	return tail_len;

sock_write1_err:
	printf("error1 %d while trying to send reply head!\n", ret);
	return 0;

sock_write2_err:
	printf("error2 %d while trying to send reply head!\n", ret);
	return 0;
}

static inline int send_reply_tail (user_req_t *req)
{
	int len = sizeof(REPLY_TAIL)-1, ret;

	ret = sock_write_push(req->sock, REPLY_TAIL, len);
	if (ret != len) {
		printf("error %d while trying to send reply tail!\n", ret);
		return 0;
	}
	return len;
}

/*
 * Send a dynamicly generated buffer. (this is the typical
 * CAD case) Every reply is generated dynamically based on
 * the template and cookie values. The template is scanned
 * for every send.
 */
static int CAD_send (user_req_t *req)
{
	int bytes;
	CAD_t *CADp = (CAD_t *) req->priv;

	bytes = send_reply_head(req, req->objectlen);

	if ((req->http_method != METHOD_GET) || !CADp ||
			 !req->query[0] || !is_class12(req->query)) {
		int ret;

		req->event = 2;
		ret = tux(TUX_ACTION_SEND_OBJECT, req);
		return ret;
	} else
		bytes += scan_send_file(req);
	bytes += send_reply_tail(req);

	req->bytes_sent += bytes;
	req->event = 3;

	return 0;
}

static int CAD_finish_send (user_req_t *req)
{
	req->bytes_sent += send_reply_tail(req);
	req->event = 3;

	return 0;
}

int TUXAPI_handle_events (user_req_t *req)
{
	int ret = TUX_RETURN_USERSPACE_REQUEST;

	switch (req->event) {
		case 0:
			ret = CAD_query (req);
			break;

		case 1:
			ret = CAD_send (req);
			break;

		case 2:
			ret = CAD_finish_send (req);
			break;

		case 3:
			if (req->priv)
				free(req->priv);
			ret = tux(TUX_ACTION_FINISH_REQ, req);
			break;

		default:
			return error(req, "invalid event!\n");
	}
	return ret;
}

static void fatal_signal (int signo, struct sigcontext_struct s)
{
	printf("TUX process %d got fatal signal %d at %08lx (cr2: %08lx)\n",
		getpid(), signo, s.eip, s.cr2);
	fflush(stdout);
	kill(getpid(), SIGKILL);
}

void TUXAPI_init (void)
{
	postlog_file = -1;
	postlog_sem = (int *) TUXAPI_malloc_shared(sizeof(postlog_sem));

	signal(SIGPIPE, SIG_IGN);
	signal(SIGSEGV, (void *) fatal_signal);
	signal(SIGBUS, (void *) fatal_signal);
	this_pid = getpid();
}
