/* 0440949 Andreas van Cranenburgh <andreas@unstable.nl>
 * november 16th, 2008, Operating Systems & Networks course, 
 * university of Amsterdam */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void termination_handler (int signum) {
	printf("received signal: %d\n", signum);
	/* reap zombie processes on SIGCHLD, otherwise exit */
	if (signum == SIGCHLD)
		wait(0);
	else {
		printf("goodbye.\n");
		exit(0);
	}
}

static void gup(FILE * log1, FILE * log2, int pipe_id[2], int myNumber) {
	FILE * log3;
	char c[2];
	/* Put any required declarations and initialisations here */
	int antidote = 0;
	c[1] = 0;

	/* Report your existence */
	fprintf(log1, "This is child number %d\n", myNumber);
	fprintf(log2, "This is child number %d\n", myNumber);

	/* just for sure - open one more log file .... */

	log3 = fopen("child.log3", "wt+");
	setvbuf(log3, NULL, _IOLBF, BUFSIZ);
	/* log3 is line buffered - a line of output is written 
	immediately on encountering an end-of-line */
	fprintf(log3, "This is child number %d\n", myNumber);

	do {
		/* Read 1 character from the pipe into 'c' here.
		   Be sure to test that you have indeed read a character.
		   If you get an error (read returns a negative value) or
		   an end-of-file (read returns zero), end your program
		   with an exit(1) */
		read(pipe_id[0], c, 1);
		if (c <= 0) {
			fprintf(log1, "Child %d read error/EOF\n", myNumber);
			fprintf(log2, "Child %d read error/EOF\n", myNumber);
			fprintf(log3, "Child %d read error/EOF\n", myNumber);
			exit(1);
		}
		
		/* log all characters except newlines: */
		if (c[0] != '\n') {
			fprintf(log1, "Child %d read '%s'\n", myNumber, c);
			fprintf(log2, "Child %d read '%s'\n", myNumber, c);
			fprintf(log3, "Child %d read '%s'\n", myNumber, c);
		}

		/* process the character here */
		if (c[0] == 'A') {
			antidote++;
			fprintf(log1, "Child %d got antidote\n", myNumber);
			fprintf(log2, "Child %d got antidote\n", myNumber);
			fprintf(log3, "Child %d got antidote\n", myNumber);
			printf ("Child %d: I got an antidote!\n", myNumber);
		} else if (c[0] == 'P') {
			if (antidote == 0) {
				fprintf(log1, "Child %d poisoned\n", myNumber);
				fprintf(log2, "Child %d poisoned\n", myNumber);
				fprintf(log3, "Child %d poisoned\n", myNumber);
				printf ("Child %d: I have been poisoned!\n", 
					myNumber);
				break;
			} else {
				antidote--;
			}
		} else if (c[0] == 'q') {
			break;
		}
	} while ( c[0] != EOF );

	fprintf(log1, "Child %d normal exit \n", myNumber);
	fprintf(log2, "Child %d normal exit \n", myNumber);
	fprintf(log3, "Child %d normal exit \n", myNumber);
	fclose(log1);
	fclose(log2);
	fclose(log3);

	exit(0);
}

int main(int argc, char * argv[]) {
	FILE * log1;
	FILE * log2;
	char c[3];
	int kiddoCount = 0, a;
	int pipe_id[2];
	struct sigaction new_action;
	sigset_t block_child;

	/* null-terminated string: */
	c[1] = 0;

	/* Set up the structure to specify the new action. */
	new_action.sa_handler = termination_handler;
	sigemptyset (&new_action.sa_mask);
	new_action.sa_flags = 0;

	/* install the signal handler */
	sigaction (SIGINT, &new_action, NULL);
	sigaction (SIGCHLD, &new_action, NULL);
	sigaction (SIGTERM, &new_action, NULL);

	/* Initialize the signal mask. */
	sigemptyset (&block_child);
	sigaddset (&block_child, SIGCHLD);

	log1 = fopen("child.log1", "wt+");
	log2 = fopen("child.log2", "wt+");
	setvbuf(log2, NULL, _IONBF, BUFSIZ);
	/* So log1 is buffered; log2 is not buffered, which means that output
	to log2 is written immediately. */

	/* create pipe */
	if (pipe(pipe_id) == -1) {
		perror("pipe");
		exit(EXIT_FAILURE);
	}

	/* block child signals since we'll do fgetc() */
	sigprocmask (SIG_BLOCK, &block_child, NULL);
	while ((c[0] = fgetc(stdin))) {
	/* Anyway, read a character, echo it to both log files 
	   and write it into the pipe (unless you have to call fork)
	   */
		/* release block on child signals: */
		sigprocmask (SIG_UNBLOCK, &block_child, NULL);

		if (c[0] == 'q')
			break;

		if (c[0] != 'f') {
			/* write to pipe */
			write(pipe_id[1], c, 1);

			/* old code: (replaced by sigaction handler)
 			if (c == 'P' && kiddoCount > 0)
				wait(0); */
		} else {
			pid_t child_id;
			kiddoCount++;

			/* fork! */
			child_id = fork();
			if (child_id == -1) {
				perror("fork");
				exit(EXIT_FAILURE);
			} else if (child_id != 0) {
				printf("process forked, pid: %d\n", child_id);
			}

			if (child_id == 0) {
				close(pipe_id[1]);
				gup(log1, log2, pipe_id, kiddoCount);
			}
		}
		/* block child signals since we'll do fgetc() */
		sigprocmask (SIG_BLOCK, &block_child, NULL);
	}

	/* normal end of program */
	/* notify children by flooding pipe with quit commands */
	c[0] = 'q';
	c[1] = '\n';
	c[2] = 0;
	for (a = 0; a < kiddoCount; a++)
		write(pipe_id[1], c, 1);

	/* close all open log files and pipes */
	close(pipe_id[0]);
	close(pipe_id[1]);
	fclose(log1);
	fclose(log2);

	exit(0);
}
