#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <signal.h>

#define DEF_PROC_NUM 13
#define PROC_NUM_LIMIT 666
#define TCP_PORT 12345

int server(int s)
{
	int dsock;
	struct sockaddr_in client;
	unsigned int addrlen = sizeof(struct sockaddr_in);
	char *hello = "Hello, world! I'm prefork server.\n";
	char *bye = "Goodbye, world!\n";
	int r;

	signal(SIGPIPE, SIG_IGN);

	for (;;) {

		/*
		 * NOTE: "thundering herd" is not a problem for accept(), see for ex.
		 * https://stackoverflow.com/questions/2213779/does-the-thundering-herd-problem-exist-on-linux-anymore
		 * https://lwn.net/Articles/663474/
		 */
		dsock = accept(s, (struct sockaddr *)&client, &addrlen);
		if ( -1 == dsock) {
			perror("accept()\n");
			continue;
		}

		printf("got client\n");

		/* serve client */
		r = write(dsock, hello, strlen(hello));
		if ( (-1 == r) || (!r)) {
			printf("write 1 ...\n");
			close(dsock);
			continue;
		};

		r = sleep(10); /* kinda working ... */
		if (r) {
			printf("interrupted sleep()\n");
		}

		r = write(dsock, bye, strlen(bye));
		if ( (-1 == r) || (!r)) {
			printf("write 2 ...\n");
			close(dsock);
			continue;
		};
		close(dsock);
	};
}

struct sockaddr_in serv_addr;
int procnum = DEF_PROC_NUM;
int lsock;

int main(int argc, char *argv[])
{
	int r, p, pid;
	int yes = 1;

	if (argc > 2) {
		printf("usage: %s [number of server processes]\n", argv[0]);
		exit(1);
	};

	if (argc == 1) {
		printf("number of server process is not specified, defaults to %d\n", procnum);
	} else {
		r = sscanf(argv[1], "%d", &procnum);
		if ( r != 1) {
			printf("number of server process is bad, defaults to %d\n", procnum);
		};
		if (procnum > PROC_NUM_LIMIT) {
			procnum = PROC_NUM_LIMIT;
			printf("number of server process is too large, using max (%d)\n", procnum);
		}
	}

	/* create listening socket */
	lsock = socket(AF_INET, SOCK_STREAM, 0);
	if ( -1 == lsock ) {
		perror("socket()");
		exit(1);
	};

	r = setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
	if ( r == -1 ) {	
		perror("setsockopt()");
		exit(1);
	};

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(TCP_PORT);

	r = bind(lsock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	if ( -1 == r ) {
		perror("bind()");
		exit(1);
	};

	r = listen(lsock, PROC_NUM_LIMIT);
	if ( -1 == r ) {
		perror("listen()");
		exit(1);
	};

	/* prefork */
	for (p = 0; p < procnum; p++) {

		pid = fork();
		switch (pid) {

			case -1:
				;;;
				perror("fork()");
				;;;
			break;
			
			/* child process */
			case 0:
				;;;
				server(lsock);
				;;;
			break;
			
			/* parent */
			default:
				;;;
				printf("child process (pid = %d) created\n", pid);
				;;;
		};
	};

	/* let orphaned children be parentised by init */
	exit(0);
}