/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2.
 *
 *  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.
 *
 * Test that the check for the existence of the shared memory object and the
 * creation of the object if it does not exist is atomic with respect to other
 * processes executing shm_open() naming the same shared memory object with
 * O_EXCL and O_CREAT set.
 *
 * This test launch NPROCESS processes which all try to open NLOOP shared
 * memory objects. If an unexpected error occurs or if the number of created
 * objects is not NLOOP, the test failed. In other case the test is unresolved.
 */

/* ftruncate was formerly an XOPEN extension. We define _XOPEN_SOURCE here to
   avoid warning if the implementation does not program ftruncate as a base 
   interface */

/* adam.li: 2004-04-30: Rewrite the test case. The idea is that with
   O_CREAT and O_EXCL specified, to shm_open() a object can only success
   once, although multiple processes might open with the same name at the 
   same time. 
 */
#define _XOPEN_SOURCE 600

#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "posixtest.h"

#define NAME_SIZE 20
#define SHM_NAME "/posixtest_23-1_%d"

/* The processes communicate by a shared memory object */
#define SHM_RESULT_NAME "/result_23-1"

#define NPROCESS 1000 /* Number of concurrent processes */
#define NLOOP 1000   /* Number of shared memory object */

char name[NAME_SIZE];
int *create_cnt;
sem_t *sem;

int child_func(void)
{
	int i, fd;
	struct timespec ts = {.tv_sec = 0, .tv_nsec = 0};
	int msec = 0;		
	
	sleep(1);
	srand(time(NULL));	
	for(i=0; i<NLOOP; i++){
		sprintf(name, SHM_NAME, i);
		fd = shm_open(name, O_RDONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
		if(fd != -1)
		{
			sem_wait(sem);
			//fprintf(stderr, "%d: %d\n", getpid(), *create_cnt);
			(*create_cnt)++;
			sem_post(sem);
		}
		/* get a random number [0, 20] */
		msec = (int) (20.0 * rand() / RAND_MAX) ;
		ts.tv_nsec = msec * 1000000;
		nanosleep(&ts, NULL);
	}
	return 0;
}

int main() {
	int i, pid, result_fd;
	char semname[20];

	snprintf(semname, 20, "/sem23-1_%d", getpid());
	sem = sem_open(semname, O_CREAT, 0777, 1);
	if( sem == SEM_FAILED || sem == NULL ) {
		perror("error at sem_open");
		return PTS_UNRESOLVED;
	}
	sem_unlink(semname);

	result_fd = shm_open(SHM_RESULT_NAME, 
			     O_RDWR|O_CREAT, 
			     S_IRUSR|S_IWUSR);
	if(result_fd == -1){
		perror("An error occurs when calling shm_open()");
		return PTS_UNRESOLVED;
	}
	shm_unlink(SHM_RESULT_NAME);
	if(ftruncate(result_fd, sizeof(*create_cnt)) != 0) {
		perror("An error occurs when calling ftruncate()");
		shm_unlink(SHM_RESULT_NAME);
		return PTS_UNRESOLVED;	
	}

	create_cnt = mmap(NULL, sizeof(*create_cnt), PROT_WRITE, 
		MAP_SHARED, result_fd, 0);
	if( create_cnt == MAP_FAILED) {
		perror("An error occurs when calling mmap()");
		shm_unlink(SHM_RESULT_NAME);
		return PTS_UNRESOLVED;	
	}	

	*create_cnt = 0;

	for(i=0; i<NPROCESS; i++) {
	        pid = fork();
		if(pid == -1){
			perror("An error occurs when calling fork()");
			return PTS_UNRESOLVED;
		} else if(pid == 0){
			child_func();
			exit(0);
		}
	}

	while (wait(NULL) > 0);
	
	for(i=0; i<NLOOP; i++){
		sprintf(name, SHM_NAME, i);
		shm_unlink(name);
	}	

	fprintf(stderr, "create_cnt: %d\n", *create_cnt);
	if(*create_cnt != NLOOP){
		printf("Test FAILED\n");
		return PTS_FAIL;
	}

	return PTS_PASS;
}
