/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */
/* $Id: zoolib.c,v 1.8 2009/06/09 17:59:46 subrata_modak Exp $ */
/*
 * ZooLib
 *
 * A Zoo is a file used to record what test tags are running at the moment.
 * If the system crashes, we should be able to look at the zoo file to find out
 * what was currently running.  This is especially helpful when running multiple
 * tests at the same time.
 *
 * The zoo file is meant to be a text file that fits on a standard console.
 * You should be able to watch it with `cat zoofile`
 *
 * zoo file format:
 * 	80 characters per line, ending with a \n
 * 	available lines start with '#'
 * 	expected line fromat: pid_t,tag,cmdline
 *
 */

#include <signal.h>
#include <stdlib.h>		/* for getenv */
#include <string.h>
#include "zoolib.h"

char zoo_error[ZELEN];

#ifdef __linux__
/* glibc2.2 definition needs -D_XOPEN_SOURCE, which breaks other things. */
extern int sighold(int __sig);
extern int sigrelse(int __sig);
#endif

/* zoo_mark(): private function to make an entry to the zoo
 * 	returns 0 on success, -1 on error */
static int zoo_mark(zoo_t z, char *entry);
static int zoo_lock(zoo_t z);
static int zoo_unlock(zoo_t z);
/* cat_args(): helper function to make cmdline from argc, argv */
char *cat_args(int argc, char **argv);

/* zoo_getname(): create a filename to use for the zoo */
char *zoo_getname(void)
{
	char buf[1024];
	char *zoo;

	zoo = getenv("ZOO");
	if (zoo) {
		snprintf(buf, 1024, "%s/%s", zoo, "active");
		return strdup(buf);
	} else {
		/* if there is no environment variable, we don't know where to put it */
		return NULL;
	}
}

/* zoo_open(): open a zoo for use */
zoo_t zoo_open(char *zooname)
{
	zoo_t new_zoo;

	new_zoo = (zoo_t) fopen(zooname, "r+");
	if (!new_zoo) {
		if (errno == ENOENT) {
			/* file doesn't exist, try fopen(xxx, "a+") */
			new_zoo = (zoo_t) fopen(zooname, "a+");
			if (!new_zoo) {
				/* total failure */
				snprintf(zoo_error, ZELEN,
					 "Could not open zoo as \"%s\", errno:%d %s",
					 zooname, errno, strerror(errno));
				return 0;
			}
			fclose(new_zoo);
			new_zoo = fopen(zooname, "r+");
		} else {
			snprintf(zoo_error, ZELEN,
				 "Could not open zoo as \"%s\", errno:%d %s",
				 zooname, errno, strerror(errno));
		}
	}
	return new_zoo;
}

int zoo_close(zoo_t z)
{
	int ret;

	ret = fclose(z);
	if (ret) {
		snprintf(zoo_error, ZELEN,
			 "closing zoo caused error, errno:%d %s",
			 errno, strerror(errno));
	}
	return ret;
}

static int zoo_mark(zoo_t z, char *entry)
{
	FILE *fp = (FILE *) z;
	int found = 0;
	long pos;
	char buf[BUFLEN];

	if (fp == NULL)
		return -1;

	if (zoo_lock(z))
		return -1;

	/* first fit */
	rewind(fp);

	do {
		pos = ftell(fp);

		if (fgets(buf, BUFLEN, fp) == NULL)
			break;

		if (buf[0] == '#') {
			rewind(fp);
			if (fseek(fp, pos, SEEK_SET)) {
				/* error */
				snprintf(zoo_error, ZELEN,
					 "seek error while writing to zoo file, errno:%d %s",
					 errno, strerror(errno));
				return -1;
			}
			/* write the entry, left justified, and padded/truncated to the
			 * same size as the previous entry */
			fprintf(fp, "%-*.*s\n", (int)strlen(buf) - 1,
				(int)strlen(buf) - 1, entry);
			found = 1;
			break;
		}
	} while (1);

	if (!found) {
		if (fseek(fp, 0, SEEK_END)) {
			snprintf(zoo_error, ZELEN,
				 "error seeking to end of zoo file, errno:%d %s",
				 errno, strerror(errno));
			return -1;
		}
		fprintf(fp, "%-*.*s\n", 79, 79, entry);
	}
	fflush(fp);

	if (zoo_unlock(z))
		return -1;
	return 0;
}

int zoo_mark_cmdline(zoo_t z, pid_t p, char *tag, char *cmdline)
{
	char new_entry[BUFLEN];

	snprintf(new_entry, 80, "%d,%s,%s", p, tag, cmdline);
	return zoo_mark(z, new_entry);
}

int zoo_mark_args(zoo_t z, pid_t p, char *tag, int ac, char **av)
{
	char *cmdline;
	int ret;

	cmdline = cat_args(ac, av);
	ret = zoo_mark_cmdline(z, p, tag, cmdline);

	free(cmdline);
	return ret;
}

int zoo_clear(zoo_t z, pid_t p)
{
	FILE *fp = (FILE *) z;
	long pos;
	char buf[BUFLEN];
	pid_t that_pid;
	int found = 0;

	if (fp == NULL)
		return -1;

	if (zoo_lock(z))
		return -1;
	rewind(fp);

	do {
		pos = ftell(fp);

		if (fgets(buf, BUFLEN, fp) == NULL)
			break;

		if (buf[0] == '#')
			continue;

		that_pid = atoi(buf);
		if (that_pid == p) {
			if (fseek(fp, pos, SEEK_SET)) {
				/* error */
				snprintf(zoo_error, ZELEN,
					 "seek error while writing to zoo file, errno:%d %s",
					 errno, strerror(errno));
				return -1;
			}
			if (ftell(fp) != pos) {
				printf("fseek failed\n");
			}
			fputs("#", fp);
			found = 1;
			break;
		}
	} while (1);

	fflush(fp);

	/* FIXME: unlock zoo file */
	if (zoo_unlock(z))
		return -1;

	if (!found) {
		snprintf(zoo_error, ZELEN,
			 "zoo_clear() did not find pid(%d)", p);
		return 1;
	}
	return 0;

}

pid_t zoo_getpid(zoo_t z, char *tag)
{
	FILE *fp = (FILE *) z;
	char buf[BUFLEN], *s;
	pid_t this_pid = -1;

	if (fp == NULL)
		return -1;

	if (zoo_lock(z))
		return -1;

	rewind(fp);
	do {
		if (fgets(buf, BUFLEN, fp) == NULL)
			break;

		if (buf[0] == '#')
			continue;	/* recycled line */

		if ((s = strchr(buf, ',')) == NULL)
			continue;	/* line was not expected format */

		if (strncmp(s + 1, tag, strlen(tag)))
			continue;	/* tag does not match */

		this_pid = atoi(buf);
		break;
	} while (1);

	if (zoo_unlock(z))
		return -1;
	return this_pid;
}

int zoo_lock(zoo_t z)
{
	FILE *fp = (FILE *) z;
	struct flock zlock;
	sigset_t block_these;
	int ret;

	if (fp == NULL)
		return -1;

	zlock.l_whence = zlock.l_start = zlock.l_len = 0;
	zlock.l_type = F_WRLCK;

	sigemptyset(&block_these);
	sigaddset(&block_these, SIGINT);
	sigaddset(&block_these, SIGTERM);
	sigaddset(&block_these, SIGHUP);
	sigaddset(&block_these, SIGUSR1);
	sigaddset(&block_these, SIGUSR2);
	sigprocmask(SIG_BLOCK, &block_these, NULL);

	do {
		ret = fcntl(fileno(fp), F_SETLKW, &zlock);
	} while (ret == -1 && errno == EINTR);

	sigprocmask(SIG_UNBLOCK, &block_these, NULL);
	if (ret == -1) {
		snprintf(zoo_error, ZELEN,
			 "failed to unlock zoo file, errno:%d %s",
			 errno, strerror(errno));
		return -1;
	}
	return 0;

}

int zoo_unlock(zoo_t z)
{
	FILE *fp = (FILE *) z;
	struct flock zlock;
	sigset_t block_these;
	int ret;

	if (fp == NULL)
		return -1;

	zlock.l_whence = zlock.l_start = zlock.l_len = 0;
	zlock.l_type = F_UNLCK;

	sigemptyset(&block_these);
	sigaddset(&block_these, SIGINT);
	sigaddset(&block_these, SIGTERM);
	sigaddset(&block_these, SIGHUP);
	sigaddset(&block_these, SIGUSR1);
	sigaddset(&block_these, SIGUSR2);
	sigprocmask(SIG_BLOCK, &block_these, NULL);

	do {
		ret = fcntl(fileno(fp), F_SETLKW, &zlock);
	} while (ret == -1 && errno == EINTR);

	sigprocmask(SIG_UNBLOCK, &block_these, NULL);

	if (ret == -1) {
		snprintf(zoo_error, ZELEN,
			 "failed to lock zoo file, errno:%d %s",
			 errno, strerror(errno));
		return -1;
	}
	return 0;
}

char *cat_args(int argc, char **argv)
{
	int a, size;
	char *cmd;

	for (size = a = 0; a < argc; a++) {
		size += strlen(argv[a]);
		size++;
	}

	if ((cmd = (char *)malloc(size)) == NULL) {
		snprintf(zoo_error, ZELEN,
			 "Malloc Error, %s/%d", __FILE__, __LINE__);
		return NULL;
	}

	*cmd = '\0';
	for (a = 0; a < argc; a++) {
		if (a != 0)
			strcat(cmd, " ");
		strcat(cmd, argv[a]);
	}

	return cmd;
}

#if defined(UNIT_TEST)

void zt_add(zoo_t z, int n)
{
	char cmdline[200];
	char tag[10];

	snprintf(tag, 10, "%s%d", "test", n);
	snprintf(cmdline, 200, "%s%d %s %s %s", "runtest", n, "one", "two",
		 "three");

	zoo_mark_cmdline(z, n, tag, cmdline);
}

int main(int argc, char *argv[])
{

	char *zooname;
	zoo_t test_zoo;
	char *test_tag = "unittest";
	int i, j;

	zooname = zoo_getname();

	if (!zooname) {
		zooname = strdup("test_zoo");
	}
	printf("Test zoo filename is %s\n", zooname);

	if ((test_zoo = zoo_open(zooname)) == NULL) {
		printf("Error opennning zoo\n");
		exit(-1);
	}

	zoo_mark_args(test_zoo, getpid(), test_tag, argc, argv);

	for (j = 0; j < 5; j++) {
		for (i = 0; i < 20; i++) {
			zt_add(test_zoo, i);
		}

		for (; i >= 0; i--) {
			zoo_clear(test_zoo, i);
		}
	}

	zoo_clear(test_zoo, getpid());

	return 0;
}

#endif
