/*
 *   Copyright (C) Bull S.A. 1996
 *   Copyright (c) International Business Machines  Corp., 2001
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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 Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
/*---------------------------------------------------------------------+
|                           shmem_test_02                              |
| ==================================================================== |
|                                                                      |
| Description:  Verify anonymous shared memory mapping, with exclusive |
|               writes and shared reads                                |
|                                                                      |
| Algorithm:    o  Obtain two shared memory segments using             |
|                  mmap (MAP_ANON), one for random data created by the |
|                  the parent, and another for the childs checksums    |
|               o  Spawn N child processes                             |
|               o  Parent:                                             |
|                     - obtain write lock on data                      |
|                     - fill shared memory segment with data           |
|                     - compute data checksum                          |
|                     - release lock                                   |
|               o  Child:                                              |
|                     - obtain read lock on data                       |
|                     - compute data checksum                          |
|                     - release lock                                   |
|                     - store checksum in other shared memory segment  |
|               o  Parent:                                             |
|                     - wait for child proceeses to complete           |
|                     - compare child's checksum (from shared memory)  |
|                       with that of the parent                        |
|                                                                      |
| System calls: The following system calls are tested:                 |
|                                                                      |
|               mmap () - Maps a file-system object into memory        |
|                                                                      |
| Usage:        shmem_test_02 [-c num_children] [-s shmem_size]        |
|                                                                      |
| To compile:   cc -o shmem_test_02 shmem_test_02.c -lbsd              |
|                                                                      |
|**********************************************************************|
|******!!!!!! LINUX PORT - must be using kernel >= 2.4.3  !!!!!!*******|
|**********************************************************************|
|                                                                      |
| Last update:   Ver. 1.2, 2/8/94 00:08:39                             |
|                                                                      |
| Change Activity                                                      |
|                                                                      |
|   Version  Date    Name  Reason                                      |
|    0.1     111593  DJK   Initial version for AIX 4.1                 |
|    1.2     020794  DJK   Moved to "prod" directory                   |
|    1.3     060601  VHM   "ported" to linux                           |
|                                                                      |
+---------------------------------------------------------------------*/

#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/wait.h>

/* Defines
 *
 * USAGE: usage statement
 */
#define LOCK_FILE "lockfile"
#define NLOOPS		20
#define	SHMEM_MODE	(SHM_R | SHM_W)
#define USAGE	"\nUsage: %s [-c num_children] [-s shmem_size]\n\n" \
		"\t-c num_children   number of child processes to spawn\n" \
		"\t-s shmem_size     size of shared memory segment (bytes)\n" \
		"\t                  (must be less than 256MB!)\n\n"

#define GOTHERE printf("got to line %d\n", __LINE__);

/*
 * Function prototypes
 *
 * parse_args (): Parse command line arguments
 * sys_error (): System error message function
 * error (): Error message function
 */

struct data {
	int who;
	int value;
};

void cleanup();
void handler(int, int, struct sigcontext *);
void setup_signal_handlers();
static void child(int, unsigned char *);
static int create_lock_file(char *);
static void unlock_file(int);
static void write_lock(int);
static void read_lock(int);
void parse_args(int, char **);
void sys_error(const char *, int);
void error(const char *, int);
void loop(int, char);
void tell(int, char *);

enum { READ, WRITE };		/* Pipe read & write end indices */
enum { PARENT, CHILD };		/* Pipe read & write end indices */

#define MAX_CHILDREN		400
#define BUFFER_SIZE		50
#define DEFAULT_NUM_CHILDREN	2
#define DEFAULT_SHMEM_SIZE	100000

int num_children = DEFAULT_NUM_CHILDREN;
int buffer_size = DEFAULT_SHMEM_SIZE;

unsigned long *checksum;	/* Shared memory segment address */
int lockfd;
pid_t parent_pid;
pid_t pid[MAX_CHILDREN];

/*---------------------------------------------------------------------+
|                               main                                   |
| ==================================================================== |
|                                                                      |
| Function:  Main program  (see prolog for more details)               |
|                                                                      |
| Returns:   (0)  Successful completion                                |
|            (-1) Error occurred                                       |
|                                                                      |
+---------------------------------------------------------------------*/
int main(int argc, char **argv)
{
	unsigned char *shmptr,	/* Shared memory segment address */
	 value = 0;		/* Value written into shared memory segment */
	int i;
	unsigned char *ptr;
	int status;
	int shmem_size;
	unsigned long cksum;

	lockfd = create_lock_file(LOCK_FILE);
	setup_signal_handlers();

	/*
	 * Parse command line arguments and print out program header
	 */
	parse_args(argc, argv);
	printf("%s: IPC Shared Memory TestSuite program\n", *argv);

	parent_pid = getpid();
	for (i = 0; i < num_children; i++)
		pid[i] = (pid_t) 0;

	/*
	 * Get chunk of shared memory for storing num_children checksum
	 */
	shmem_size = sizeof(unsigned long) * num_children;
	if ((checksum = (unsigned long *)
	     mmap(0, shmem_size, PROT_READ | PROT_WRITE,
		  MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)
		sys_error("mmap failed", __LINE__);

	for (i = 0; i < num_children; i++)
		*(checksum + (sizeof(unsigned long) * i)) = 0;

	/*
	 * Get chunk of memory for writing scratch data
	 */
	printf("\n\tGet shared memory segment (%d bytes)\n", buffer_size);
	if ((shmptr = mmap(0, buffer_size, PROT_READ | PROT_WRITE,
			   MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)
		sys_error("mmap failed", __LINE__);

	/*
	 * Parent:
	 *
	 * Fill buffer with data..
	 */
	cksum = value = 0;

	printf("\n\tParent: calculate shared memory segment checksum\n");
	write_lock(lockfd);
	for (ptr = shmptr; ptr < (shmptr + buffer_size); ptr++) {
		*ptr = (value++) % (UCHAR_MAX + 1);
		cksum += *ptr;
	}
	unlock_file(lockfd);
	printf("\t        shared memory checksum %08lx\n", cksum);

	printf("\n\tSpawning %d child processes ... \n", num_children);
	for (i = 0; i < num_children; i++) {

		if ((pid[i] = fork()) == (pid_t) 0) {
			child(i, shmptr);
			exit(0);
		} else if (pid[i] < (pid_t) 0)
			sys_error("fork failed", __LINE__);
	}

	/*
	 * Wait for child processes to compute checksum and complete...
	 */
	for (i = 0; i < num_children; i++) {
		waitpid(pid[i], &status, 0);

		if (!WIFEXITED(status))
			sys_error("child process terminated abnormally",
				  __LINE__);
		if (cksum != *(checksum + (sizeof(unsigned long) * i))) {
			printf("checksum [%d] = %08lx\n", i, checksum[i]);
			error("checksums do not match", __LINE__);
		}
	}
	printf("\n\tParent: children calculated segment successfully\n");
	/*
	 * Program completed successfully -- exit
	 */
	printf("\nsuccessful!\n");

	return (0);
}

static void child(int num, unsigned char *shmptr)
{
	unsigned long cksum = 0;
	int i;

	read_lock(lockfd);
	for (i = 0; i < buffer_size; i++)
		cksum += *shmptr++;
	unlock_file(lockfd);

	*(checksum + (sizeof(unsigned long) * num)) = cksum;
	printf("\t\tchild (%02d): checksum %08lx\n", num,
	       *(checksum + (sizeof(unsigned long) * num)));
}

static void write_lock(int fd)
{
	if (lockf(fd, F_LOCK, 0) < 0)
		sys_error("lockf (LOCK) failed", __LINE__);
}

static void read_lock(int fd)
{
	if (lockf(fd, F_TEST, 0) < 0)
		sys_error("lockf (LOCK) failed", __LINE__);
}

static void unlock_file(int fd)
{
	if (lockf(fd, F_ULOCK, 0) < 0)
		sys_error("lockf (UNLOCK) failed", __LINE__);
}

/*---------------------------------------------------------------------+
|                          setup_handler ()                            |
| ==================================================================== |
|                                                                      |
| Function:  Setup the signal handler for SIGPIPE.                     |
|                                                                      |
+---------------------------------------------------------------------*/
void setup_signal_handlers()
{
	struct sigaction invec;

	invec.sa_handler = (void (*)(int))handler;
	sigemptyset(&invec.sa_mask);
	invec.sa_flags = 0;

	if (sigaction(SIGINT, &invec, NULL) < 0)
		sys_error("sigaction failed", __LINE__);

}

/*---------------------------------------------------------------------+
|                             handler ()                               |
| ==================================================================== |
|                                                                      |
| Function:  Signal catching function for SIGPIPE signal.              |
|                                                                      |
|            o  SIGPIPE: Print message and abort program...            |
|                                                                      |
|            o  SIGINT:  Parent process calls cleanup, child processes |
|                        simply exit                                   |
|                                                                      |
|            o  Other:   Print message and abort program...            |
|                                                                      |
+---------------------------------------------------------------------*/
void handler(int sig, int code, struct sigcontext *scp)
{
	char msg[100];		/* Buffer for error message */

	if (sig == SIGINT) {
		if (getpid() == parent_pid) {

			fprintf(stderr, "Received SIGINT -- cleaning up...\n");
			fflush(stderr);

			cleanup();
		} else
			exit(-1);
	} else {
		sprintf(msg, "Received an unexpected signal (%d)", sig);
		error(msg, __LINE__);
	}
}

/*---------------------------------------------------------------------+
|                             cleanup ()                               |
| ==================================================================== |
|                                                                      |
| Function:  Closes all of the pipes, kills all of the child           |
|            processes and exits the program...                        |
|                                                                      |
+---------------------------------------------------------------------*/
void cleanup()
{
	int i;

	if (getpid() == parent_pid) {
		for (i = 0; i < num_children; i++) {
			if (pid[i] > (pid_t) 0 && kill(pid[i], SIGKILL) < 0)
				sys_error("signal failed", __LINE__);
		}
	}

	exit(-1);
}

static int create_lock_file(char *lock_name)
{
	int fd;

	if ((fd = open(lock_name, O_RDWR)) < 0) {
		if ((fd = open(lock_name, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
			perror("cannot create lock file");
			exit(-1);
		}
	}
	return (fd);
}

/*---------------------------------------------------------------------+
|                             parse_args ()                            |
| ==================================================================== |
|                                                                      |
| Function:  Parse the command line arguments & initialize global      |
|            variables.                                                |
|                                                                      |
| Updates:   (command line options)                                    |
|                                                                      |
|            [-s] size: shared memory segment size                     |
|                                                                      |
|            [-c] num_children: number of child processes              |
|                                                                      |
+---------------------------------------------------------------------*/
void parse_args(int argc, char **argv)
{
	int i;
	int errflag = 0;
	char *program_name = *argv;
	extern char *optarg;	/* Command line option */

	while ((i = getopt(argc, argv, "c:s:?")) != EOF) {
		switch (i) {
		case 'c':
			num_children = atoi(optarg);
			break;
		case 's':
			buffer_size = atoi(optarg);
			break;
		case '?':
			errflag++;
			break;
		}
	}

	if (errflag) {
		fprintf(stderr, USAGE, program_name);
		exit(2);
	}
}

/*---------------------------------------------------------------------+
|                             sys_error ()                             |
| ==================================================================== |
|                                                                      |
| Function:  Creates system error message and calls error ()           |
|                                                                      |
+---------------------------------------------------------------------*/
void sys_error(const char *msg, int line)
{
	char syserr_msg[256];

	sprintf(syserr_msg, "%s: %s\n", msg, strerror(errno));
	error(syserr_msg, line);
}

/*---------------------------------------------------------------------+
|                               error ()                               |
| ==================================================================== |
|                                                                      |
| Function:  Prints out message and exits...                           |
|                                                                      |
+---------------------------------------------------------------------*/
void error(const char *msg, int line)
{
	fprintf(stderr, "ERROR [line: %d] %s\n", line, msg);
	cleanup();
}
