/*
 * Copyright (C) 2007 Nokia Corporation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Author: Adrian Hunter
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <mntent.h>
#include <signal.h>

#include "tests.h"

#define MAX_NAME_SIZE 1024

struct gcd_pid
{
	struct gcd_pid *next;
	int pid;
	char *name;
	int mtd_index;
};

struct gcd_pid *gcd_pid_list = NULL;

static int add_gcd_pid(const char *number)
{
	int pid;
	FILE *f;
	char file_name[MAX_NAME_SIZE];
	char program_name[MAX_NAME_SIZE];

	pid = atoi(number);
	if (pid <= 0)
		return 0;
	snprintf(file_name, MAX_NAME_SIZE, "/proc/%s/stat", number);
	f = fopen(file_name, "r");
	if (f == NULL)
		return 0;
	if (fscanf(f, "%d %s", &pid, program_name) != 2) {
		fclose(f);
		return 0;
	}
	if (strncmp(program_name, "(jffs2_gcd_mtd", 14) != 0)
		pid = 0;
	if (pid) {
		size_t sz;
		struct gcd_pid *g;

		sz = sizeof(struct gcd_pid);
		g = (struct gcd_pid *) malloc(sz);
		g->pid = pid;
		g->name = (char *) malloc(strlen(program_name) + 1);
		if (g->name)
			strcpy(g->name, program_name);
		else
			exit(1);
		g->mtd_index = atoi(program_name + 14);
		g->next = gcd_pid_list;
		gcd_pid_list = g;
	}
	fclose(f);
	return pid;
}

static int get_pid_list(void)
{
	DIR *dir;
	struct dirent *entry;

	dir = opendir("/proc");
	if (dir == NULL)
		return 1;
	for (;;) {
		entry = readdir(dir);
		if (entry) {
			if (strcmp(".",entry->d_name) != 0 &&
					strcmp("..",entry->d_name) != 0)
				add_gcd_pid(entry->d_name);
		} else
			break;
	}
	closedir(dir);
	return 0;
}

static int parse_index_number(const char *name)
{
	const char *p, *q;
	int all_zero;
	int index;

	p = name;
	while (*p && !isdigit(*p))
		++p;
	if (!*p)
		return -1;
	all_zero = 1;
	for (q = p; *q; ++q) {
		if (!isdigit(*q))
			return -1;
		if (*q != '0')
			all_zero = 0;
	}
	if (all_zero)
		return 0;
	index = atoi(p);
	if (index <= 0)
		return -1;
	return index;
}

static int get_mtd_index(void)
{
	FILE *f;
	struct mntent *entry;
	struct stat f_info;
	struct stat curr_f_info;
	int found;
	int mtd_index = -1;

	if (stat(tests_file_system_mount_dir, &f_info) == -1)
		return -1;
	f = fopen("/proc/mounts", "rb");
	if (!f)
		f = fopen("/etc/mtab", "rb");
	if (f == NULL)
		return -1;
	found = 0;
	for (;;) {
		entry = getmntent(f);
		if (!entry)
			break;
		if (stat(entry->mnt_dir, &curr_f_info) == -1)
			continue;
		if (f_info.st_dev == curr_f_info.st_dev) {
			int i;

			i = parse_index_number(entry->mnt_fsname);
			if (i != -1) {
				if (found && i != mtd_index)
					return -1;
				found = 1;
				mtd_index = i;
			}
		}
	}
	fclose(f);
	return mtd_index;
}

static int get_gcd_pid(void)
{
	struct gcd_pid *g;
	int mtd_index;

	if (get_pid_list())
		return 0;
	mtd_index = get_mtd_index();
	if (mtd_index == -1)
		return 0;
	for (g = gcd_pid_list; g; g = g->next)
		if (g->mtd_index == mtd_index)
			return g->pid;
	return 0;
}

static void gcd_hupper(void)
{
	int64_t repeat;
	int pid;

	pid = get_gcd_pid();
	CHECK(pid != 0);
	repeat = tests_repeat_parameter;
	for (;;) {
		CHECK(kill(pid, SIGHUP) != -1);
		/* Break if repeat count exceeded */
		if (tests_repeat_parameter > 0 && --repeat <= 0)
			break;
		/* Sleep */
		if (tests_sleep_parameter > 0) {
			unsigned us = tests_sleep_parameter * 1000;
			unsigned rand_divisor = RAND_MAX / us;
			unsigned s = (us / 2) + (rand() / rand_divisor);
			usleep(s);
		}
	}
}

/* Title of this test */

static const char *gcd_hupper_get_title(void)
{
	return "Send HUP signals to gcd";
}

/* Description of this test */

static const char *gcd_hupper_get_description(void)
{
	return
		"Determine the PID of the gcd process. " \
		"Send it SIGHUP (may require root privileges). " \
		"If a sleep value is specified, the process sleeps. " \
		"If a repeat count is specified, then the task repeats " \
		"that number of times. " \
		"The repeat count is given by the -n or --repeat option, " \
		"otherwise it defaults to 1. " \
		"A repeat count of zero repeats forever. " \
		"The sleep value is given by the -p or --sleep option, " \
		"otherwise it defaults to 1. "
		"Sleep is specified in milliseconds.";
}

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

	/* Set default test repetition */
	tests_repeat_parameter = 1;

	/* Set default test sleep */
	tests_sleep_parameter = 1;

	/* Handle common arguments */
	run_test = tests_get_args(argc, argv, gcd_hupper_get_title(),
			gcd_hupper_get_description(), "np");
	if (!run_test)
		return 1;
	/* Change directory to the file system and check it is ok for testing */
	tests_check_test_file_system();
	/* Do the actual test */
	gcd_hupper();
	return 0;
}
