#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <sys/shm.h>

/*
 * Creates dirty data and issue sync at the end.
 * Child creates enough dirty data, issues fsync. Parent synchronizes with
 * child and soon as fsync is issued, dispatches KILL.
 * If KILL was unsuccessful, a flag in shared memory is set.
 * Parent verifies this flag to ensure test result. 
 */

union semun {
	int val;
	struct semid_ds *buf;
	unsigned short  *array;
	struct seminfo  *__buf;
};

int main(int argc, char ** argv)
{
	int shm_id;
	char* shm_addr, *data_array;
	struct shmid_ds shm_desc;
	union semun data;
	struct sembuf op;
	int sem_id;

	int status, pid, fd, len, loop;
	int count = 0, ret = 1, data_size;
	int *post_sync;

	if (argc != 3) {
		printf("Usage : synctest <len> <loop> \n");
		exit(1);
	}
	
	len = atoi(argv[1]);
	loop = atoi(argv[2]);
	
	data_size = len * 1024 * 1024;

	/* allocate a shared memory segment with size of 10 bytes. */
	shm_id = shmget(IPC_PRIVATE, 10, IPC_CREAT | IPC_EXCL | 0600);
	if (shm_id == -1) {
		perror("main : shmget \n");
		exit(1);
	}

	/* attach the shared memory segment to our process's address space. */
	shm_addr = shmat(shm_id, NULL, 0);
	if (!shm_addr) { /* operation failed. */
		perror("main : shmat \n");
		goto early_out;
	}

	post_sync = (int*) shm_addr;
	*post_sync = 0;

	fd = open("testfile", O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK);
	if (!fd) {
		perror("main : Failed to create data file \n");
		goto out;
	}
	
	data_array = (char *)malloc(data_size * sizeof(char));
	if (!data_array) {
		perror("main : Not enough memory \n");
		goto out;
	}
	
	op.sem_num = 0;
	sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT);

	if (sem_id < 0){
		perror("main : semget \n");
		goto out;
	}

	data.val = 0;
	semctl(sem_id, 0, SETVAL, data);

	pid = fork();
	if (pid < 0)
	{
		perror("main : fork failed \n");
		goto out;
	}
	if (!pid)
	{
		/* child process */
		while (count++ < loop) {
			write(fd, data_array, data_size * (sizeof(char)));
		}

		printf("CHLD : start sync \n");
		/* increment sema */
		op.sem_op = 1;
		semop(sem_id, &op, 1);

		/* wait for parent */
		op.sem_op = 0;
		semop(sem_id, &op, 1);
		fsync(fd);
		*post_sync = 1;
		return 0 ;
	} else {
		/* parent process */
		/* waiting for child to increment sema */
		op.sem_op = -1;
		semop(sem_id, &op, 1);
		/* some sleep so fsync gets started before we kill*/
		sleep(1);
		
		ret = kill(pid, SIGKILL);
		if (ret) {
			perror("main : kill failed \n");
			goto out;
		}
		
		printf("PAR : waiting\n");
		wait(&status);
	}

	ret = *post_sync;

	if (!ret)
		printf("PASS : sync interrupted \n");
	else
		printf("FAIL : sync not interrupted \n");

out:
	/* detach the shared memory segment from our process's address space. */
	if (shmdt(shm_addr) == -1) {
		perror("main : shmdt");
	}

	close(fd);
	system("rm -f testfile \n");

early_out:

	/* de-allocate the shared memory segment. */
	if (shmctl(shm_id, IPC_RMID, &shm_desc) == -1) {
		perror("main : shmctl");
	}

	return ret;
}