/**
 * @file llpluginsharedmemory.cpp
 * LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
 *
 * @cond
 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 * @endcond
 */

#include "linden_common.h"

#include "llpluginsharedmemory.h"

// on Mac and Linux, we use the native shm_open/mmap interface by using
//  #define USE_SHM_OPEN_SHARED_MEMORY 1
// in the appropriate sections below.

// For Windows, use:
//  #define USE_WIN32_SHARED_MEMORY 1

// If we ever want to fall back to the apr implementation for a platform, use:
//  #define USE_APR_SHARED_MEMORY 1

#if LL_WINDOWS
//  #define USE_APR_SHARED_MEMORY 1
    #define USE_WIN32_SHARED_MEMORY 1
#elif LL_DARWIN
    #define USE_SHM_OPEN_SHARED_MEMORY 1
#elif LL_LINUX
    #define USE_SHM_OPEN_SHARED_MEMORY 1
#endif


// FIXME: This path thing is evil and unacceptable.
#if LL_WINDOWS
    #define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_"
    // Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista.
    // Other options I've seen referenced are "Local\\" and "Session\\".
    #define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_"
#else
    // mac and linux
    #define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_"
    #define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL"
#endif

#if USE_APR_SHARED_MEMORY
    #include "llapr.h"
    #include "apr_shm.h"
#elif USE_SHM_OPEN_SHARED_MEMORY
    #include <sys/fcntl.h>
    #include <sys/mman.h>
    #include <errno.h>
#elif USE_WIN32_SHARED_MEMORY
#include <windows.h>
#endif // USE_APR_SHARED_MEMORY


int LLPluginSharedMemory::sSegmentNumber = 0;

std::string LLPluginSharedMemory::createName(void)
{
    std::stringstream newname;

#if LL_WINDOWS
    newname << GetCurrentProcessId();
#else // LL_WINDOWS
    newname << getpid();
#endif // LL_WINDOWS

    newname << "_" << sSegmentNumber++;

    return newname.str();
}

/**
 * @brief LLPluginSharedMemoryImpl is the platform-dependent implementation of LLPluginSharedMemory. TODO:DOC is this necessary/sufficient? kinda obvious.
 *
 */
class LLPluginSharedMemoryPlatformImpl
{
public:
    LLPluginSharedMemoryPlatformImpl();
    ~LLPluginSharedMemoryPlatformImpl();

#if USE_APR_SHARED_MEMORY
    apr_shm_t* mAprSharedMemory;
#elif USE_SHM_OPEN_SHARED_MEMORY
    int mSharedMemoryFD;
#elif USE_WIN32_SHARED_MEMORY
    HANDLE mMapFile;
#endif

};

/**
 * Constructor. Creates a shared memory segment.
 */
LLPluginSharedMemory::LLPluginSharedMemory()
{
    mSize = 0;
    mMappedAddress = NULL;
    mNeedsDestroy = false;

    mImpl = new LLPluginSharedMemoryPlatformImpl;
}

/**
 * Destructor. Uses destroy() and detach() to ensure shared memory segment is cleaned up.
 */
LLPluginSharedMemory::~LLPluginSharedMemory()
{
    if(mNeedsDestroy)
        destroy();
    else
        detach();

    unlink();

    delete mImpl;
}

#if USE_APR_SHARED_MEMORY
// MARK: apr implementation

LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
{
    mAprSharedMemory = NULL;
}

LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
{

}

bool LLPluginSharedMemory::map(void)
{
    mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory);
    if(mMappedAddress == NULL)
    {
        return false;
    }

    return true;
}

bool LLPluginSharedMemory::unmap(void)
{
    // This is a no-op under apr.
    return true;
}

bool LLPluginSharedMemory::close(void)
{
    // This is a no-op under apr.
    return true;
}

bool LLPluginSharedMemory::unlink(void)
{
    // This is a no-op under apr.
    return true;
}


bool LLPluginSharedMemory::create(size_t size)
{
    mName = APR_SHARED_MEMORY_PREFIX_STRING;
    mName += createName();
    mSize = size;

    apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), gAPRPoolp );

    if(ll_apr_warn_status(status))
    {
        return false;
    }

    mNeedsDestroy = true;

    return map();
}

bool LLPluginSharedMemory::destroy(void)
{
    if(mImpl->mAprSharedMemory)
    {
        apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory);
        if(ll_apr_warn_status(status))
        {
            // TODO: Is this a fatal error?  I think not...
        }
        mImpl->mAprSharedMemory = NULL;
    }

    return true;
}

bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
{
    mName = name;
    mSize = size;

    apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), gAPRPoolp );

    if(ll_apr_warn_status(status))
    {
        return false;
    }

    return map();
}


bool LLPluginSharedMemory::detach(void)
{
    if(mImpl->mAprSharedMemory)
    {
        apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory);
        if(ll_apr_warn_status(status))
        {
            // TODO: Is this a fatal error?  I think not...
        }
        mImpl->mAprSharedMemory = NULL;
    }

    return true;
}


#elif USE_SHM_OPEN_SHARED_MEMORY
// MARK: shm_open/mmap implementation

LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
{
    mSharedMemoryFD = -1;
}

LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
{
}

bool LLPluginSharedMemory::map(void)
{
    mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0);
    if(mMappedAddress == NULL)
    {
        return false;
    }

    LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;

    return true;
}

bool LLPluginSharedMemory::unmap(void)
{
    if(mMappedAddress != NULL)
    {
        LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL;
        if(::munmap(mMappedAddress, mSize) == -1)
        {
            // TODO: Is this a fatal error?  I think not...
        }

        mMappedAddress = NULL;
    }

    return true;
}

bool LLPluginSharedMemory::close(void)
{
    if(mImpl->mSharedMemoryFD != -1)
    {
        LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL;
        if(::close(mImpl->mSharedMemoryFD) == -1)
        {
            // TODO: Is this a fatal error?  I think not...
        }

        mImpl->mSharedMemoryFD = -1;
    }
    return true;
}

bool LLPluginSharedMemory::unlink(void)
{
    if(!mName.empty())
    {
        if(::shm_unlink(mName.c_str()) == -1)
        {
            return false;
        }
    }

    return true;
}


bool LLPluginSharedMemory::create(size_t size)
{
    mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING;
    mName += createName();
    mSize = size;

    // Preemptive unlink, just in case something didn't get cleaned up.
    unlink();

    mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if(mImpl->mSharedMemoryFD == -1)
    {
        return false;
    }

    mNeedsDestroy = true;

    if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1)
    {
        return false;
    }


    return map();
}

bool LLPluginSharedMemory::destroy(void)
{
    unmap();
    close();

    return true;
}


bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
{
    mName = name;
    mSize = size;

    mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
    if(mImpl->mSharedMemoryFD == -1)
    {
        return false;
    }

    // unlink here so the segment will be cleaned up automatically after the last close.
    unlink();

    return map();
}

bool LLPluginSharedMemory::detach(void)
{
    unmap();
    close();
    return true;
}

#elif USE_WIN32_SHARED_MEMORY
// MARK: Win32 CreateFileMapping-based implementation

// Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx

LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
{
    mMapFile = NULL;
}

LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
{

}

bool LLPluginSharedMemory::map(void)
{
    mMappedAddress = MapViewOfFile(
        mImpl->mMapFile,            // handle to map object
        FILE_MAP_ALL_ACCESS,        // read/write permission
        0,
        0,
        mSize);

    if(mMappedAddress == NULL)
    {
        LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL;
        return false;
    }

    LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;

    return true;
}

bool LLPluginSharedMemory::unmap(void)
{
    if(mMappedAddress != NULL)
    {
        UnmapViewOfFile(mMappedAddress);
        mMappedAddress = NULL;
    }

    return true;
}

bool LLPluginSharedMemory::close(void)
{
    if(mImpl->mMapFile != NULL)
    {
        CloseHandle(mImpl->mMapFile);
        mImpl->mMapFile = NULL;
    }

    return true;
}

bool LLPluginSharedMemory::unlink(void)
{
    // This is a no-op on Windows.
    return true;
}


bool LLPluginSharedMemory::create(size_t size)
{
    mName = WIN32_SHARED_MEMORY_PREFIX_STRING;
    mName += createName();
    mSize = size;

    mImpl->mMapFile = CreateFileMappingA(
                 INVALID_HANDLE_VALUE,      // use paging file
                 NULL,                      // default security
                 PAGE_READWRITE,            // read/write access
                 0,                         // max. object size
                 static_cast<DWORD>(mSize), // buffer size
                 mName.c_str());            // name of mapping object

    if(mImpl->mMapFile == NULL)
    {
        LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL;
        return false;
    }

    mNeedsDestroy = true;

    return map();
}

bool LLPluginSharedMemory::destroy(void)
{
    unmap();
    close();
    return true;
}

bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
{
    mName = name;
    mSize = size;

    mImpl->mMapFile = OpenFileMappingA(
                FILE_MAP_ALL_ACCESS,        // read/write access
                false,                      // do not inherit the name
                mName.c_str());             // name of mapping object

    if(mImpl->mMapFile == NULL)
    {
        LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL;
        return false;
    }

    return map();
}

bool LLPluginSharedMemory::detach(void)
{
    unmap();
    close();
    return true;
}



#endif