/*
 *  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.
 */

/* 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.
 */

#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 */

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

static 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(void)
{
	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 | PROT_READ,
			  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;
}
