/* $Id: exec.c,v 1.51 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1998-2001 Atsushi Onoe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>

#include "cue.h"

int
exec_del(struct folder *folder, int pos)
{
	struct msginfo *msg;
	char *suf;
	char path1[MAXPATH], path2[MAXPATH];
	size_t len;

	msg = &folder->msg[pos];
	snprintf(path1, sizeof(path1), "%s/%d", folder->name.ptr + 1, msg->num);
	snprintf(path2, sizeof(path2), "%s/#%d", folder->name.ptr + 1, msg->num);
	if (rename(path1, path2) < 0)
		return -1;
	fdb_purge(path1);
	folder->nmsg--;
	if (folder->pos > 0 && folder->pos >= pos)
		folder->pos--;
	if (msg->mark)
		folder->nmark--;
	memmove(msg, msg + 1, sizeof(*msg) * (folder->nmsg - pos));
	if (folder->flags & FOL_DRAFT) {
		suf = path1 + strlen(path1);
		len = sizeof(path1) - strlen(path1);
		strlcpy(suf, SUF_INFO, len);
		(void)unlink(path1);
		strlcpy(suf, SUF_BAK, len);
		(void)unlink(path1);
	}
	(void)stat(folder->name.ptr + 1, &folder->stbuf);
	return 0;
}

static int
exec_refile(struct folder *folder, int pos)
{
	struct msginfo *msg, *p;
	char path1[MAXPATH], path2[MAXPATH];
	struct folder *tfl;
	int i, n;
	int keep = 0;

	msg = &folder->msg[pos];
	snprintf(path1, sizeof(path1), "%s/%d", folder->name.ptr + 1, msg->num);
	for (i = 0; i < MAX_REFILE; i++) {
		if (msg->refile[i] == NULL)
			break;
		if (strcmp(folder->name.ptr, msg->refile[i]) == 0) {
			keep = 1;
			continue;
		}
		tfl = folder_open(msg->refile[i], 1);
		if (tfl == NULL)
			goto error;
		if (tfl->nmsg > 0)
			n = tfl->msg[tfl->nmsg-1].num + 1;
		else
			n = 1;
		snprintf(path2, sizeof(path2), "%s/%d", tfl->name.ptr + 1, n);
		if (link(path1, path2) < 0)
			goto error;
		if (tfl->nmsg >= tfl->szmsg) {
			p = realloc(tfl->msg,
			    sizeof(*msg) * (tfl->szmsg + LINEBLOCK));
			if (!p)
				goto error;
			tfl->msg = p;
			tfl->szmsg += LINEBLOCK;
		}
		memset(&tfl->msg[tfl->nmsg], 0, sizeof(*msg));
		tfl->msg[tfl->nmsg++].num = n;
		(void)stat(tfl->name.ptr + 1, &tfl->stbuf);
	}
	if (keep) {
		if (msg->mark) {
			msg->mark = 0;
			folder->nmark--;
		}
	} else
		(void)exec_del(folder, pos);
	return 0;

  error:
	while (--i > 0) {
		if (msg->refile[i] == NULL)
			continue;
		tfl = folder_open(msg->refile[i], 1);
		if (tfl == NULL)
			continue;
		tfl->nmsg--;
		n = tfl->msg[tfl->nmsg].num;
		snprintf(path2, sizeof(path2), "%s/%d", tfl->name.ptr + 1, n);
		(void)unlink(path2);
		(void)stat(tfl->name.ptr + 1, &tfl->stbuf);
	}
	return -1;
}

static int
exec_spam(struct folder *folder, int pos, char *spam)
{
	struct msginfo *msg, *p;
	char path1[MAXPATH], path2[MAXPATH];
	struct folder *tfl;
	int n;

	if (spam == NULL)
		return exec_del(folder, pos);
	msg = &folder->msg[pos];
	snprintf(path1, sizeof(path1), "%s/%d", folder->name.ptr + 1, msg->num);
	tfl = folder_open(spam, 1);
	if (tfl == NULL)
		return -1;
	if (tfl->nmsg > 0)
		n = tfl->msg[tfl->nmsg-1].num + 1;
	else
		n = 1;
	snprintf(path2, sizeof(path2), "%s/%d", tfl->name.ptr + 1, n);
	if (link(path1, path2) < 0)
		return -1;
	if (tfl->nmsg >= tfl->szmsg) {
		p = realloc(tfl->msg,
		    sizeof(*msg) * (tfl->szmsg + LINEBLOCK));
		if (!p)
			return -1;
		tfl->msg = p;
		tfl->szmsg += LINEBLOCK;
	}
	memset(&tfl->msg[tfl->nmsg], 0, sizeof(*msg));
	tfl->msg[tfl->nmsg++].num = n;
	(void)stat(tfl->name.ptr + 1, &tfl->stbuf);
	(void)exec_del(folder, pos);
	return 0;
}

void
exec_mark(struct state *state)
{
	struct folder *folder;
	int i;
	int changed = 0;

	folder = state->folder;
	strlcpy(state->status, "Refiling and deleting ... ", sizeof(state->status));
	disp_update(state);
	for (i = 0; i < folder->nmsg; ) {
		switch (folder->msg[i].mark) {
		case MARK_DELETE:
			changed++;
			if (exec_del(folder, i) == 0)
				continue;
			break;
		case MARK_REFILE:
			changed++;
			if (exec_refile(folder, i) == 0)
				continue;
			break;
		case MARK_SPAM:
			changed++;
			if (exec_spam(folder, i, state->config.spam) == 0)
				continue;
			break;
		}
		i++;
	}
	if (changed == 0) {
		strlcpy(state->status, "No marks", sizeof(state->status));
		return;
	}
	/* XXX */
	if (!proc_running(PTYPE_SYNC))
		proc_exec(PTYPE_SYNC, -1, -1, NULL, NULL, "sync");
	if (folder->pos >= folder->nmsg) {
		if (folder->nmsg)
			folder->pos = folder->nmsg - 1;
		else
			folder->pos = 0;
	}
	state->message = NULL;
	strlcpy(state->status, "Refiling and deleting ... done", sizeof(state->status));
}

void
exec_pack(struct state *state)
{
	int i;
	struct folder *folder = state->folder;
	struct msginfo *msg;
	char *suf1, *suf2;
	char path1[MAXPATH], path2[MAXPATH];

	if (folder->msg[folder->nmsg - 1].num == folder->nmsg) {
		strlcpy(state->status, "Already packed", sizeof(state->status));
		return;
	}
	if (!edit_yn(state, "Are you sure to pack %s? ", folder->name.ptr))
		return;
	snprintf(state->status, sizeof(state->status), "Packing %s ...",
	    folder->name.ptr);
	disp_update(state);
	for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
		if (msg->num <= i + 1)
			continue;
		snprintf(path1, sizeof(path1), "%s/%d", folder->name.ptr + 1,
		    msg->num);
		snprintf(path2, sizeof(path2), "%s/%d", folder->name.ptr + 1,
		    i + 1);
		if (link(path1, path2) < 0) {
			snprintf(state->status, sizeof(state->status),
			    "%s: %s", path2, strerror(errno));
			return;
		}
		(void)unlink(path1);
		if (folder->flags & FOL_DRAFT) {
			suf1 = path1 + strlen(path1);
			suf2 = path2 + strlen(path2);
			strlcpy(suf1, SUF_INFO, sizeof(state->status));
			strlcpy(suf2, SUF_INFO, sizeof(state->status));
			(void)rename(path1, path2);
			strlcpy(suf1, SUF_BAK, sizeof(state->status));
			strlcpy(suf2, SUF_BAK, sizeof(state->status));
			(void)rename(path1, path2);
		}
		msg->num = i + 1;
		if (msg->scan.ptr) {
			free(msg->scan.ptr);
			msg->scan.ptr = NULL;
		}
		msg->scan.len = 0;
	}
	(void)stat(folder->name.ptr + 1, &folder->stbuf);
	snprintf(state->status, sizeof(state->status), "Packing %s ... done",
	    folder->name.ptr);
}

static int
msgdatecmp(const void *p1, const void *p2)
{
	const struct msginfo *m1 = p1, *m2 = p2;

	return m1->date - m2->date;
}

void
exec_sort(struct state *state)
{
	int i, j;
	struct folder *fl;
	struct msginfo *msg, *nmsg;
	char opath[MAXPATH], npath[MAXPATH], tpath[MAXPATH];

	fl = state->folder;
	if (fl->nmsg == 0)
		return;
	if (!edit_yn(state, "Are you sure to sort %s? ", fl->name.ptr))
		return;
	snprintf(state->status, sizeof(state->status), "Sorting %s ...",
	    fl->name.ptr);
	disp_update(state);
	/* scan all messages to get date */
	for (i = 0; folder_read(state, i) != NULL; i++)
		;
	qsort(fl->msg, fl->nmsg, sizeof(struct msginfo), msgdatecmp);
	snprintf(tpath, sizeof(tpath), "%s/.sort.tmp", fl->name.ptr + 1);
	for (i = 0, msg = fl->msg; i < fl->nmsg; i++, msg++) {
		if (msg->num == i + 1)
			continue;
		if (msg->scan.ptr != NULL) {
			free(msg->scan.ptr);
			msg->scan.ptr = NULL;
			msg->scan.len = 0;
		}
		snprintf(opath, sizeof(opath), "%s/%d", fl->name.ptr + 1,
		    msg->num);
		snprintf(npath, sizeof(npath), "%s/%d", fl->name.ptr + 1,
		    i + 1);
		if (link(opath, npath) == 0) {
			(void)unlink(opath);
			msg->num = i + 1;
			continue;
		}
		if (errno == EEXIST) {
			for (j = i + 1, nmsg = msg + 1; ; j++, nmsg++) {
				if (j == fl->nmsg) {
					nmsg = NULL;
					break;
				}
				if (nmsg->num == i + 1)
					break;
			}
		} else
			nmsg = NULL;
		if (nmsg == NULL || rename(npath, tpath) < 0) {
 err:
			snprintf(state->status, sizeof(state->status),
			    "%s: %s", npath, strerror(errno));
			folder_update(fl, 1);
			return;
		}
		if (link(opath, npath) < 0) {
			(void)rename(tpath, npath);
			goto err;
		}
		if (rename(tpath, opath) < 0)
			goto err;
		nmsg->num = msg->num;
		if (nmsg->scan.ptr != NULL) {
			free(nmsg->scan.ptr);
			nmsg->scan.ptr = NULL;
			nmsg->scan.len = 0;
		}
		msg->num = i + 1;
	}
	(void)stat(fl->name.ptr + 1, &fl->stbuf);
	snprintf(state->status, sizeof(state->status), "Sorting %s ... done",
	    fl->name.ptr);
}

void
exec_inc(struct state *state)
{
	struct folder *folder = state->folder;
	int n;
	char nbuf[10];

	if (proc_running(PTYPE_INC)) {
		strlcpy(state->status, "Child process is running",
		    sizeof(state->status));
		return;
	}
	if (folder->nmsg > 0)
		n = folder->msg[folder->nmsg-1].num + 1;
	else
		n = 1;
	conf_update(state);
	snprintf(nbuf, sizeof(nbuf), "%d", n);
	if (proc_exec(PTYPE_INC, -1, -1, NULL, NULL,
	    state->config.inc, state->folder->name.ptr + 1, nbuf) < 0) {
		snprintf(state->status, sizeof(state->status), "%s",
		    strerror(errno));
		return;
	}
	strlcpy(state->status, "Incing ... ", sizeof(state->status));
	state->folder->incpos = state->folder->nmsg;
	return;
}

static void
exec_send_one(struct state *state, int pos)
{
	struct folder *fl;
	char path[MAXPATH];
	int n, wstatus;
	int fd;

	fl = state->folder;
	n = fl->msg[pos].num;
	snprintf(path, sizeof(path), "%d", n);

	/* sendmail read stdin ... */
	snprintf(path, sizeof(path), "%s/%d", fl->name.ptr + 1, n);
	if ((fd = open(path, 0)) < 0) {
		snprintf(state->status, sizeof(state->status), "%s: %s", path,
		    strerror(errno));
		return;
	}
	strlcpy(state->status, "Sending ...", sizeof(state->status));
	disp_update(state);
	if (proc_exec(PTYPE_SEND, fd, -1, NULL, NULL,
	    state->config.send, fl->name.ptr + 1, path) < 0) {
		snprintf(state->status, sizeof(state->status), "%s", strerror(errno));
		close(fd);
		return;
	}
	close(fd);
	proc_getstate(PTYPE_SEND, &wstatus, state->status,
	    sizeof(state->status));
	if (!WIFSIGNALED(wstatus) && WEXITSTATUS(wstatus) == 0) {
		exec_anno(state, path, sizeof(path));
		exec_del(fl, pos);
		if (fl->nmsg == 0)
			state->folder = folder_open(state->config.folder, 1);
		strlcpy(state->status, "Sending ... done",
		    sizeof(state->status));
	}
}

void
exec_send(struct state *state, int marked)
{
	conf_update(state);

	if (!marked) {
		exec_send_one(state, state->folder->pos);
	} else {
		struct msginfo *msg = state->folder->msg;
		int nmsg = state->folder->nmsg;
		int pos = 0;
		int j = 0;

		while (nmsg--) {
			if (msg->mark == MARK_MARK) {
				exec_send_one(state, pos);
				j++;
			} else {
				msg++;
				pos++;
			}
		}
		if (j == 0)
			strlcpy(state->status, "No mark", sizeof(state->status));
	}
	disp_update(state);
}

int
exec_editor(struct state *state, const char *folname, int num, const char *suf)
{
	int err;
	void (*handler)(int);
	struct stat stbuf, nstbuf;
	char *file, path[MAXPATH];

	snprintf(path, sizeof(path), "%s/%d", folname, num);
	file = path + strlen(folname) + 1;
	if (suf)
		strlcat(path, suf, sizeof(path));
	if (stat(path, &stbuf) < 0)
		memset(&stbuf, 0, sizeof(stbuf));
	handler = signal(SIGTSTP, SIG_DFL);
	endwin();
	err = proc_exec(PTYPE_EDITOR, -1, -1, NULL, NULL,
	    state->config.editor, folname, file);
	if (err != 0) {
		snprintf(state->status, sizeof(state->status), "%s",
		    strerror(errno));
	} else {
		/*
		 * Some of the editors exit with code other than 0 for
		 * non-fatal errors, such as pattern match fails.
		 * So we should ignore exit code from editor.
		 */
		proc_getstate(PTYPE_EDITOR, NULL, state->status,
		    sizeof(state->status));
	}
	(void)signal(SIGTSTP, handler);
	refresh();
	if (err)
		return -1;
	if (stat(path, &nstbuf) < 0)
		return -1;
	if (stbuf.st_size == nstbuf.st_size
	&&  stbuf.st_ino == nstbuf.st_ino
	&&  stbuf.st_mtime == nstbuf.st_mtime)
		return -1;
	return 0;
}
