/* zip.c - entry point for dealing with possibly gzipped files
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif				/* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include "hook.h"
#include "terminal.h"
#include "viewfile.h"

struct prog {				 /* structure for compression program details */
	char *path;			 /* path to program */
	char *c_name;			 /* program name for compression (argv[0]) */
	char *c_flag;			 /* flags for compression */
	char *d_name;			 /* program name for decompression */
	char *d_flag;			 /* flags for decompression */
};


/* Fill in the variables with appropriate values for the type of compressed
 * file that "file" is, returning a pointer to the last "." in "file" or 0
 * if "file" was not a recognised compressed type.
 */
char *zip_file_type(char *file, struct prog *p)
{
	char *a;

	a = strrchr(file, '.');
	if (!a)
		return (0);

	if (!strcmp(a, ".gz")) {	    /* .gz = gzip */
		p->path = GZIP_PATH;
		p->c_name = "gzip";
		p->d_name = "zcat";
		p->c_flag = "-9";
		p->d_flag = 0;
	} else if (!strcmp(a, ".bz2")) {    /* .bz2 = bzip2 */
		p->path = BZIP2_PATH;
		p->c_name = "bzip2";
		p->d_name = "bzcat";
		p->c_flag = "-9";
		p->d_flag = 0;
	} else
		return (0);

	return (a);
}

/* Read the file whose details are in LDB structure "d", handling compressed
 * (gzipped or bzip2'ed) files properly. Return codes are as read_file().
 * Commentfile is "cm", 0 if none.
 */
int do_read_file(ldb_t d, char *title, unsigned long flags, char *cm)
{
	struct stat sb;
	struct LDB ldb;
	int rfd = -1;
	int wfd = -1;
	pid_t result;
	pid_t child;
	int status;
	int modify;
	time_t t;
	char *a;
	struct prog p;
	char *tmp = 0;
	int i;

	if (stat(d->realname, &sb) != 0) {  /* try to stat() file */
		return (2);
	}

	if (!S_ISREG(sb.st_mode)) {	    /* not a regular file */
		return (2);
	}

	a = zip_file_type(d->realname, &p);

	if (!a) {			    /* not a compressed file */
		return (read_file(d, title, flags, cm));
	}

	memcpy(&ldb, d, sizeof(ldb));

	a = strrchr(ldb.realname, '.');

	rfd = open(ldb.realname, O_RDONLY);
	if (rfd < 0)
		return (2);

	*a = 0;

	modify = 0;
	if (flags & MENU_STATUS_EDIT)
		modify = 1;
	if (flags & MENU_STATUS_ADD)
		modify = 1;
	if (flags & MENU_STATUS_DELETE)
		modify = 1;
	if (flags & MENU_STATUS_READONLY)
		modify = 0;

	if (modify) {			    /* <file>.gz -> <file> */
		*a = 0;
		remove(ldb.realname);
		wfd =
		    open(ldb.realname, O_WRONLY | O_CREAT | O_EXCL,
			 S_IRUSR | S_IWUSR);
		if (wfd < 0) {
			flags |= MENU_STATUS_READONLY;
			modify = 0;
		}
	}

	if (!modify) {			    /* <file>.gz -> tmpfile */
		tmp = (char *) malloc(strlen(P_tmpdir) + 32);
		if (!tmp)
			return (2);
		strcpy(tmp, P_tmpdir);
		strcat(tmp, "/mviewXXXXXX");
		wfd = mkstemp(tmp);
		if (wfd < 0)
			return (2);
		strcpy(ldb.realname, tmp);
	}

	child = fork();
	if (child == 0) {
		dup2(rfd, 0);
		dup2(wfd, 1);
		close(rfd);
		close(wfd);
		bbs_hook(HOOK_DROP_PRIVS, p.path, 0);
		execl(p.path, p.d_name, p.d_flag, 0);
		exit(1);
	} else if (child < 0) {
		close(rfd);
		close(wfd);
		return (2);
	}

	close(rfd);
	close(wfd);

	t_bored(0);
	do {
		result = KEY_NONE;
		bbs_hook(HOOK_KEY_PRESSED, 0, &result);
		result = waitpid(child, &status, WNOHANG);
		if ((result != child) || (!WIFEXITED(status))) {
			sleep(1);
			t_bored(1);
		}
	} while ((result != child) || (!WIFEXITED(status)));
	t_bored(0);

	if (WEXITSTATUS(status) != 0) {
		remove(ldb.realname);
		return (2);
	}

	stat(ldb.realname, &sb);
	t = sb.st_mtime;

	i = read_file(&ldb, title, flags, cm);

	d->last_accessed = ldb.last_accessed;
	d->last_datestamp = ldb.last_datestamp;
	d->datestamp_line = ldb.datestamp_line;
	d->current_line = ldb.current_line;
	d->at_end = ldb.at_end;
	d->sub_status = ldb.sub_status;

	if (!modify) {			    /* just a temp file; remove */
		remove(ldb.realname);
		free(tmp);
		return (i);
	}

	stat(ldb.realname, &sb);
	if (sb.st_mtime == t) {		    /* no change - just remove */
		remove(ldb.realname);
		return (i);
	}

	rfd = open(ldb.realname, O_RDONLY);
	if (rfd < 0) {
		remove(ldb.realname);
		return (i);
	}

	stat(d->realname, &sb);

	wfd = open(d->realname, O_WRONLY | O_TRUNC, sb.st_mode);
	if (wfd < 0) {
		close(rfd);
		remove(ldb.realname);
		return (i);
	}

	child = fork();
	if (child == 0) {
		dup2(rfd, 0);
		dup2(wfd, 1);
		close(rfd);
		close(wfd);
		bbs_hook(HOOK_DROP_PRIVS, p.path, 0);
		execl(p.path, p.c_name, p.c_flag, 0);
		exit(1);
	} else if (child < 0) {
		close(rfd);
		close(wfd);
		return (i);
	}

	close(rfd);
	close(wfd);

	t_bored(0);
	do {
		result = KEY_NONE;
		bbs_hook(HOOK_KEY_PRESSED, 0, &result);
		result = waitpid(child, &status, WNOHANG);
		if ((result != child) || (!WIFEXITED(status))) {
			sleep(1);
			t_bored(1);
		}
	} while ((result != child) || (!WIFEXITED(status)));
	t_bored(0);

	d->last_accessed = time(0);

	remove(ldb.realname);
	return (i);
}


/* Fudge file line scanning for file "file" by decompressing if necessary.
 */
void rf_fudge_linescan(rf_data_t data, char *file)
{
	char *tmp = 0;
	int rfd, wfd;
	pid_t result;
	pid_t child;
	int status;
	char *a;
	struct prog p;

	data->num_lines = 0;

	a = zip_file_type(file, &p);

	if (!a) {			    /* not a compressed file */
		data->fd = open(file, O_RDONLY);
		if (data->fd < 0)
			return;
		rf_get_line_positions(data);
		close(data->fd);
		return;
	}

	rfd = open(file, O_RDONLY);
	if (rfd < 0)
		return;

	tmp = (char *) malloc(strlen(P_tmpdir) + 32);
	if (!tmp)
		return;
	strcpy(tmp, P_tmpdir);
	strcat(tmp, "/mviewXXXXXX");
	wfd = mkstemp(tmp);
	if (wfd < 0) {
		close(rfd);
		free(tmp);
		return;
	}

	child = fork();
	if (child == 0) {
		dup2(rfd, 0);
		dup2(wfd, 1);
		close(rfd);
		close(wfd);
		bbs_hook(HOOK_DROP_PRIVS, p.path, 0);
		execl(p.path, p.d_name, p.d_flag, 0);
		exit(1);
	} else if (child < 0) {
		close(rfd);
		close(wfd);
		remove(tmp);
		free(tmp);
		return;
	}

	close(rfd);
	close(wfd);

	do {
		result = KEY_NONE;
		bbs_hook(HOOK_KEY_PRESSED, 0, &result);
		result = waitpid(child, &status, WNOHANG);
		if ((result != child) || (!WIFEXITED(status))) {
			sleep(1);
			t_bored(1);
		}
	} while ((result != child) || (!WIFEXITED(status)));

	if (WEXITSTATUS(status) != 0) {
		remove(tmp);
		free(tmp);
		return;
	}

	data->fd = open(tmp, O_RDONLY);
	if (data->fd >= 0) {
		rf_get_line_positions(data);
		close(data->fd);
	}

	remove(tmp);
	free(tmp);
}

/* EOF */
