//-----------------------------------------------------------------------------------------------
// <copyright file="LqrSMACSharedMemory.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//  Implementation of the SharedMemory class, which wraps a Linux shared memory segment.  Methods
//	return true if successful, false on failure; most can be configured to throw an exception vs.
//	returning an error
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------------------

#include "LqrSMACSharedMemory.h"
#include "LqrSMACGetErrorDescription.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

template<class T>
void DoThrow(unsigned lineNum, const string& whatArg)
{
	std::stringstream msg;
	msg << "SharedMemory(" << lineNum << "): " << whatArg;

	throw T(msg.str());
}

template<class T>
void DoThrow(unsigned lineNum, int errNum, const string& whatArg)
{
	std::stringstream msg;
	msg << "SharedMemory(" << "(" << lineNum << "): " << whatArg << " (errNum=" << errNum << ")";

	throw T(msg.str(), errNum);
}

SHARED_LIB_PUBLIC SharedMemory::SystemError::SystemError(const string& whatArg, int _errNum)
: runtime_error(whatArg)
, errNum(_errNum)
{
}

SHARED_LIB_PUBLIC string SharedMemory::SystemError::describe(int errNum)
{
	return GetErrorDescription(errNum);
}

SHARED_LIB_PUBLIC SharedMemory::SystemCallInterrupted::SystemCallInterrupted(const string& whatArg)
: SystemError(whatArg, EINTR)
{
}


SharedMemory::SharedMemory()
: openType(SHMO_OPERATION_MASK)
, fd(-1)
, mapped(false)
, keepLocked(false)
{
}

SharedMemory::SharedMemory(SHMOpenType _openType, int _fd, bool _keepLocked)
: openType(_openType)
, fd(_fd)
, mapped(false)
, keepLocked(_keepLocked)
{
	if (fd == -1) throw invalid_argument("SharedMemory cannot be initialized with fd=-1");
}

SharedMemory::SharedMemory(SHMOpenType _openType, const string& _segmentName, int createMode, bool _keepLocked)
: openType(SHMO_OPERATION_MASK)
, fd(-1)
, mapped(false)
, keepLocked(_keepLocked)
{
	Open(_openType, _segmentName, createMode, _keepLocked, true);
}

SharedMemory::~SharedMemory()
{
	if (fd != -1)
	{
		if (mapped)
			Unmap();

		close(fd);
	}
}

bool SharedMemory::ChMod(int newMode, bool throwOnFail)
{
	return RunAPI(fd, fchmod(fd, newMode), throwOnFail, "SharedMemory::ChMod", __LINE__);
}

bool SharedMemory::ChOwn(uid_t newUID, gid_t newGID, bool throwOnFail)
{
	return RunAPI(fd, fchown(fd, newUID, newGID), throwOnFail, "SharedMemory::ChOwn", __LINE__);
}

void SharedMemory::Close()
{
	if (fd != -1)
	{
		if (mapped)
			Unmap(false);

		close(fd);
		fd = -1;
	}
}

int SharedMemory::GetFD() const
{
	return fd;
}

off_t SharedMemory::GetSize(bool throwOnFail) const
{
	off_t result = -1;
	int thestat;
	StatStruct statbuf;

	if (Stat(thestat, statbuf))
		result = statbuf.st_size;

	return result;
}

bool SharedMemory::IsMapped() const
{
	return mapped;
}

bool SharedMemory::IsOpen() const
{
	return fd != -1;
}

void* SharedMemory::Map(bool writeable, bool throwOnFail)
{
	Unmap(false);

	void* result = NULL;
	const string msg = "SharedMemory::Map";

	if (!CheckOpen(throwOnFail, msg))
	{
		const off_t segSize = GetSize(throwOnFail);
		const int prot = writeable ? (PROT_WRITE|PROT_READ) : PROT_READ;

		result = mmap(NULL, segSize, prot, MAP_SHARED, fd, 0);

		if (result == ((void*) -1))
		{
			ThrowSystemError(throwOnFail, errno, msg, __LINE__);
			result = NULL;
		}
		else
		{
			mapAddr = result;
			mapLen = segSize;
			mapped = true;
		}
	}

	return result;
}

bool SharedMemory::Open(SHMOpenType _openType, const string& _segmentName, int createMode, bool _keepLocked, bool throwOnFail)
{
	Close();

	openType = _openType;
	keepLocked = _keepLocked;

	int oflag = ((_openType & SHMO_WRITEABLE) ? O_RDWR : O_RDONLY) | ((_openType & SHMO_TRUNCATE) ? O_TRUNC : 0);

	switch(_openType & SHMO_OPERATION_MASK)
	{
	case SHMO_CREATE:		oflag |= O_CREAT;				break;
	case SHMO_CREATE_NEW:	oflag |= O_CREAT | O_EXCL;		break;
	case SHMO_OPEN:											break;
	default:				throw runtime_error("Unexpected missing operation case in SharedMemory::Open");
	}

	fd = shm_open(_segmentName.c_str(), oflag, createMode);

	if (fd == -1)
		ThrowSystemError(throwOnFail, errno, "SharedMemory::Open", __LINE__);

	return fd != -1;
}

void SharedMemory::SetFD(SHMOpenType _openType, int _fd, bool _keepLocked)
{
	Close();

	openType = _openType;
	fd = _fd;
	keepLocked = _keepLocked;
}

bool SharedMemory::SetSize(off_t newLength, bool throwOnFail)
{
	return RunAPI(fd, ftruncate(fd, newLength), throwOnFail, "SharedMemory::SetSize", __LINE__);
}

bool SharedMemory::Stat(int& st, StatStruct& statbuf, bool throwOnFail) const
{
	return RunAPI(fd, st = fstat(fd, &statbuf), throwOnFail, "SharedMemory::Stat", __LINE__);
}

bool SharedMemory::Unlink(bool throwOnFail)
{
	if (throwOnFail)
		throw runtime_error("SharedMemory::Unlink not implemented");

	return false;
}

bool SharedMemory::Unmap(bool throwOnFail)
{
	bool result = !mapped;

	if (mapped)
	{
		result = RunAPI(fd, munmap(mapAddr, mapLen), throwOnFail, "SharedMemory::Unmap", __LINE__);
		mapped = false;
	}

	return result;
}

bool SharedMemory::CheckOpen(bool throwOnFail, const string& msg)
{
	return CheckOpen2(fd, throwOnFail, msg);
}

bool SharedMemory::CheckOpen2(int theFD, bool throwOnFail, const string& msg)
{
	bool result = (theFD != -1);

	if (!result && throwOnFail)
	{
		stringstream ss;
		ss << msg << ": memory segment is not open";

		string s = ss.str();
		throw runtime_error(s);
	}

	return result;
}

bool SharedMemory::RunAPI(int theFD, int status, bool throwOnFail, const string& msg, unsigned lineNum)
{
	bool result = CheckOpen2(theFD, throwOnFail, msg);

	if (result)
		if (status == -1)
		{
			ThrowSystemError(throwOnFail, errno, msg, lineNum);
			result = false;
		}

	return result;
}

void SharedMemory::ThrowSystemError(bool throwOnFail, int errNum, const string& msg, unsigned lineNum)
{
	if (throwOnFail)
	{
		stringstream ss;
		ss << msg << " failed: " << GetErrorDescription(errNum);

		string s = ss.str();

		if (errNum == EINTR)
			DoThrow<SharedMemory::SystemCallInterrupted>(lineNum, s);
		else
			DoThrow<SharedMemory::SystemError>(lineNum, errNum, s);
	}
}

}
}
