/**
 * \file Synchronization.c
 * \brief Synchronization for loading the only one trastlet at a moment.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 2013
 * \par In Samsung Ukraine R&D Center (SURC) under a contract between
 * \par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * \par Copyright: (c) Samsung Electronics Co, Ltd 2012. All rights reserved.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>

#include <assert.h>
#include <errno.h>
#include <string.h>

#include "CommLayerData.h"
#include "Synchronization.h"
#include "log.h"

#if (NWD_SYNC_DRIVER == NWD_SYNC_DRIVER_LOCK_FILE)
/* this code use flock on a file for synchronization */

#define LOCK_FILE_PATH "/efs/prov/mldap.lock"
/* Not system users use daemon, therefore they don't need permissions
 * to the lock file */
#define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int kmLockFD = -1;

static inline int _kmLockTADriver(void)
{
	int err = PLATFORM_INTERNAL_ERROR;
	int timeout = 0;

	if (kmLockFD >= 0) {
		LOGD("The only one call may be performed at a moment");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (access(LOCK_FILE_PATH, F_OK) == -1) {
		if (errno == ENOENT) {
			kmLockFD = creat(LOCK_FILE_PATH, LOCK_FILE_MODE);
			if (kmLockFD < 0) {
				LOGD("creat: %s", strerror(errno));
				return PLATFORM_INTERNAL_ERROR;
			}
		} else {
			LOGD("access: %s", strerror(errno));
			return PLATFORM_INTERNAL_ERROR;
		}
	}

	if (kmLockFD < 0) {
		kmLockFD = open(LOCK_FILE_PATH, O_RDONLY);
	}
	if (kmLockFD < 0) {
		LOGD("open: %s", strerror(errno));
		return PLATFORM_INTERNAL_ERROR;
	}

	timeout = 0;
	do {
		err = flock(kmLockFD, LOCK_EX | LOCK_NB);
		if (err == 0) {
			break;
		} else if (errno != EWOULDBLOCK) {
			goto return_with_err;
		}

		/* sleep for 1ms */
		usleep(1000);
	} while (++timeout < NWD_SYNC_TIMEOUT);

	if (timeout >= NWD_SYNC_TIMEOUT) {
		goto return_with_err;
	}

	return NO_ERROR;

return_with_err:
	close(kmLockFD);
	kmLockFD = -1;
	return PLATFORM_INTERNAL_ERROR;
}

static inline int _kmUnlockTADriver(void)
{
	if (kmLockFD < 0) {
		return NO_ERROR;
	}

	/* if unlocking fails it will be unlocked after close() */
	(void)flock(kmLockFD, LOCK_UN);
	close(kmLockFD);
	kmLockFD = -1;

	return NO_ERROR;
}

#elif (NWD_SYNC_DRIVER == NWD_SYNC_DRIVER_NONE)
/* without synchronization, see LibDevKMApi.c */

static inline int _kmLockTADriver(void)
{
	return NO_ERROR;
}

static inline int _kmUnlockTADriver(void)
{
	return NO_ERROR;
}

#else /* NWD_SYNC_DRIVER */
#error "Set proper value for define NWD_SYNC_DRIVER (see Synchronization.h)"
#endif /* NWD_SYNC_DRIVER */

int kmLockTA(void)
{
	return _kmLockTADriver();
}

int kmUnlockTA(void)
{
	return _kmUnlockTADriver();
}