/* semafori.c
 *
 * Operacijski sustavi -- vjezba 3, zadatak 3a
 *
 * Rijesio:
 * Filip Niksic (fniksic@gmail.com)
 * 2008-05-26
 **/

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

#define maxInputSize 1024

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

int idShm;
void *shmBase;
int *ulaz, *izlaz;
char *M;

int idSem;

int child = 0, nStarted = 0;
pid_t children[3];

void oslobodiResurse(int ubijDjecu) {
    shmdt(shmBase);
    if (!child) {
	if (ubijDjecu) {
	    int i;
	    for (i = 0; i < nStarted; ++i)
		kill(children[i], SIGINT);
	    for (i = 0; i < nStarted; ++i)
		wait(NULL);
	}
	shmctl(idShm, IPC_RMID, NULL);
	semctl(idSem, 0, IPC_RMID);
    }
}

void sigInt_handler(int sig) {
    /* printf("Proces %d ulovio CTRL+C.\n", getpid()); */
    oslobodiResurse(1);
    exit(EXIT_SUCCESS);
}

int semOp(int semNum, int semOp) {
    struct sembuf sb;
    sb.sem_num = semNum;
    sb.sem_op = semOp;
    sb.sem_flg = 0;
    return semop(idSem, &sb, 1);
}

void proizvodjac(int num) {
    char s[maxInputSize];
    int i = -1;

    if (num == 2)
	semOp(4, -1); /* Cekam da prvi proizvodjac obavi unos. */
    printf("Unesi znakove za proizvodjaca %d: ", num);
    scanf("%s", s);
    if (num == 1)
	semOp(4, 1); /* Oslobadjam drugog proizvodjaca. */

    /* Barijera */
    semOp(3, -1);
    semOp(3, 0);

    /* Pocinjem slanje */
    do {
	sleep(1);
	++i;
	semOp(0, -1); /* cekaj_PUN */
	semOp(2, -1); /* cekaj_PISI */
	M[*ulaz] = s[i];
	*ulaz = (*ulaz + 1) % 5;
	printf("Proizvodjac %d -> %c\n", num, s[i]);
	semOp(2, 1); /* postavi_PISI */
	semOp(1, 1); /* postavi_PRAZAN */
    } while (s[i] != 0);
    
    oslobodiResurse(0);
}

void potrosac() {
    char s[maxInputSize * 2];
    int i = 0, numEnds = 0;

    do {
	semOp(1, -1); /* cekaj_PRAZAN */
	s[i] = M[*izlaz];
	*izlaz = (*izlaz + 1) % 5;
	printf("Potrosac <- %c\n", s[i]);
	semOp(0, 1); /* postavi_PUN */
	if (s[i] == 0) {
	    ++numEnds;
	    --i;
	}
	++i;
    } while (numEnds < 2);

    printf("\nPrimljeno je %s\n", s);

    oslobodiResurse(0);
}

int main(void) {
    union semun arg;
    unsigned short semInit[] = {5, 0, 1, 2, 0};
    struct sigaction sigInt;
    int i;

    /* Odmah na pocetku postavljam SIGINT handler */
    sigInt.sa_handler = sigInt_handler;
    sigemptyset(&sigInt.sa_mask);
    sigInt.sa_flags = 0;

    if (sigaction(SIGINT, &sigInt, NULL) == -1) {
	printf("Greska prilikom postavljanja SIGINT handlera.\n");
	return EXIT_FAILURE;
    }

    /* Zauzimam zajednicku memoriju:
     *   int ulaz, izlaz
     *   char M[5]
     **/
    if ((idShm = shmget(IPC_PRIVATE, 2 * sizeof(int) + 5 * sizeof(char), 0600)) == -1) {
	printf("Greska prilikom rezerviranja zajednicke memorije.\n");
	return EXIT_FAILURE;
    }
    shmBase = shmat(idShm, NULL, 0);
    ulaz = (int *)shmBase;
    izlaz = (int *)shmBase + 1;
    M = (char *)shmBase + 2 * sizeof(int);
    *ulaz = *izlaz = 0;

    /* Postavljam semafore. Trebam 5 komada:
     *   2 za baratanje medjuspremnikom (pun, prazan)
     *   1 za pisanje u medjuspremnik
     *   1 za barijeru
     *   1 za reguliranje cekanja proizvodjaca 2 na unos proizvodjaca 1
     **/
    if ((idSem = semget(IPC_PRIVATE, 5, 0600)) == -1) {
	printf("Greska prilikom dobavljanja semafora.\n");
	oslobodiResurse(0); /* Nema veze sto pokusavam obrisati semafore. Ignoriram gresku. */
	return EXIT_FAILURE;
    }

    /* Inicijaliziram semafore */
    arg.array = semInit; /* = {5, 0, 1, 2, 0} */
    semctl(idSem, 0, SETALL, arg);

    /* Pokrecem proizvodjace i potrosaca: */
    for (i = 0; i <= 2; ++i) {
	pid_t tmp = fork();
	switch (tmp) {
	case 0:
	    child = 1;
	    if (i == 0)
		potrosac();
	    else
		proizvodjac(i);
	    exit(EXIT_SUCCESS);
	    break;
	case -1:
	    printf("Greska pri startanju procesa.\n");
	    oslobodiResurse(1);
	    exit(EXIT_FAILURE);
	    break;
	default:
	    children[nStarted] = tmp;
	    ++nStarted;
	}
    }

    /* Cekam da procesi zavrse */
    for (i = 0; i < nStarted; ++i)
	wait(NULL);
    oslobodiResurse(0);

    return EXIT_SUCCESS;
}
