/*
* Copyright 2009 Funambol, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

#include "FIFOWrapper.h"

#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>

#include <signal.h>

#include "Logger/Logger.h"

#include <errno.h>

#include "FileSystemUtils.h"

namespace NS_DM_Client
{

const char c_pathSeparator = '/';
const char* const c_pathSeparatorStr = "/";

const mode_t c_fullAccessMode = S_IRWXU | S_IRWXG | S_IRWXO;
const mode_t c_accessMode = c_fullAccessMode;

bool CreateFIFO(const String& name);


#ifdef PLATFORM_ANDROID
////const char* const c_IPCDataPath = "/data/data/com.funambol.omadmclient.android.service/IPC";
const char* const c_IPCDataPath = "/data/data/com.funambol.omadmclient.android.service/files/IPC";
const char* c_IPCPath = "IPC";
#else
const char* const c_IPCDataPath = "/tmp/Funambol/DMClient/IPC";
#endif

const char* const c_FifoWrapperLog = "FifoWrapper";

const int max_try_count = 2;

//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Open(const String& name, bool readMode, bool blockedMode, bool immediateOpen)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog),
        "ENTER :Name: %s, readMode: %s, blockedMode: %s", name.c_str(), readMode ? "true" : "false", blockedMode ? "true" : "false");

    m_fifo = -1;

    if (!CreatePath(c_IPCDataPath, &NS_Logging::GetLogger(c_FifoWrapperLog)))
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't create path for ipc fifos");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    m_name = (String)c_IPCDataPath + c_pathSeparatorStr + name;
    if (!CreateFIFO(m_name))
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't create fifo on next path: %s", m_name.c_str());
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    m_readMode = readMode;
    m_blockedMode = blockedMode;

    if (immediateOpen)
    {
        StatusCode openRes = open();
        if (openRes != e_Ok)
        {
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", openRes);
            return openRes;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Read(Buffer& buffer, bool exitIfNoWriter)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER buffer size: %d", buffer.Size());

    if (buffer.Size() <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    while(true)
    {
        if (m_fifo == -1)
        {   // no opened fifo
            StatusCode openRes = open();
            if (openRes != e_Ok)
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to open fifo");
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", openRes);
                return openRes;
            }
        }

        int try_count = max_try_count;
try_again:
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "BEFORE read: name: %s, size: %d", m_name.c_str(), buffer.Size());
        int r = read(m_fifo, buffer.GetPointer(), buffer.Size());
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "AFTER read: name: %s, size: %d, r=%d", m_name.c_str(), buffer.Size(), r);
        if (r > 0)
        {
            if (size_t(r) < buffer.Size())
            {
                LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "read data size is smaller that requested. Resize buffer with smaller size. Requested: %d, readed: %d", buffer.Size(), r);
                buffer.Allocate(size_t(r));
            }
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (r == 0)
        {
            if (--try_count > 0)
            {
                ::sleep(1);
                goto try_again;
            }

            if (m_blockedMode && (!exitIfNoWriter))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_WriteInstanceNotAlive);
                return e_WriteInstanceNotAlive;
            }
        }
        else // r < 0
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to read from fifo. Error code: %d, Error message: %s", errno, strerror(errno));
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
            return e_Failed;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Write(const void* buffer, size_t length, bool exitIfNoReader)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER Length size: %d", length);

    if (length <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    while(true)
    {
        if (m_fifo == -1)
        {   // no opened fifo
            StatusCode openRes = open();
            if (openRes != e_Ok)
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to open fifo");
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", openRes);
                return openRes;
            }
        }

        int try_count = max_try_count;
try_again:
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "BEFORE write: name: %s, size: %d", m_name.c_str(), length);
        int w = write(m_fifo, buffer, length);
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "AFTER write: name: %s, size: %d, w=%d", m_name.c_str(), length, w);
        if (w > 0)
        {
            if (size_t(w) < length)
            {
                LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "written data size is smaller that requested. Requested: %d, written: %d", length, w);
            }
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (w == 0)
        {
            if (--try_count > 0)
            {
                ::sleep(1);
                goto try_again;
            }

            if (m_blockedMode && (!exitIfNoReader))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_ReadInstanceNotAlive);
                return e_ReadInstanceNotAlive;
            }
        }
        else // w < 0
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to write to fifo. Error code: %d, Error message: %s", errno, strerror(errno));
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
            return e_Failed;
        }
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Write(const Buffer& buffer, bool exitIfNoReader)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER Buffer size: %d", buffer.Size());

    if (buffer.Size() <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    StatusCode writeRes = Write(buffer.GetPointer(), buffer.Size(), exitIfNoReader);

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", writeRes);
    return writeRes;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Close()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER");

    NS_Common::Lock lock(m_stateMutex);

    if (m_fifo >=0)
    {
        close(m_fifo);
    }
    m_fifo = -1;
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
void PosixFIFOWrapper::Release()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER");

    if (m_fifo >= 0)
    {
        Close();
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE");
    delete this;
}
//------------------------------------------------------------------------------------------------------
PosixFIFOWrapper::~PosixFIFOWrapper()
{
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::open()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER Name: %s", m_name.c_str());

    if (m_fifo != -1)
    {
        LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "fifo already opened. Reopen");
        Close();
    }

    int fifo_open_flags = (m_readMode) ?
        ((m_blockedMode) ? O_RDONLY : (O_RDONLY | O_NONBLOCK)) : ((m_blockedMode) ? O_WRONLY : (O_WRONLY | O_NONBLOCK));

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "BEFORE open. Name: %s", m_name.c_str());

    if ((m_fifo = ::open(m_name.c_str(), fifo_open_flags)) < 0)
    {
    LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't open fifo on next path: %s, error code: %d, error message: %s", m_name.c_str(), errno, strerror(errno));
    if (errno == ENXIO)
    {
        StatusCode fres = m_readMode ? e_WriteInstanceNotAlive : e_ReadInstanceNotAlive;
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", fres);
        return fres;
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
    return e_Failed;
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "AFTER open. Name: %s", m_name.c_str());

    if (m_readMode && (!m_blockedMode))
    {
        if (fcntl(m_fifo, F_SETFL, O_RDONLY) == -1)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't set new fifo flags");
            Close();
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
            return e_Failed;
        }
    }

    if (!changePIPESignalDisposition())
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't change SIGPIPE signal disposition");

        Close();

        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Failed);
        return e_Failed;
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
void sigPipeHandler(int Signal)
{
    switch (Signal)
    {
        case SIGPIPE:
        {
            // Currently nothing to do. Skip signal. It prevent program termination
            LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "SIGPIPE signal is handled. No present instance who read from FIFO");
            break;
        }
        default:
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "Signal handler catch unexpected signal type");
            break;
        }
    };
}
//------------------------------------------------------------------------------------------------------
bool PosixFIFOWrapper::changePIPESignalDisposition()
{
    sigset_t SigMask;
    if (sigemptyset(&SigMask) != 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "Failed to clear signal mask");
        return false;
    }

    struct sigaction SignalAction;
    SignalAction.sa_handler = sigPipeHandler;
    SignalAction.sa_mask    = SigMask;
    SignalAction.sa_flags    = 0;
    if (sigaction(SIGPIPE, &SignalAction, NULL) != 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "Failed to set disposition for SIGPIPE signal");
        return false;
    }
    return true;
}
//------------------------------------------------------------------------------------------------------
bool CreateFIFO(const String& name)
{
    mode_t fumask = 0777 - (0777 & 0777);
    umask(fumask);
    mode_t mode = 0777 | 0777 | S_IFIFO;

    if ((mknod(name.c_str(), mode, 0) != 0) && (errno != EEXIST))
    {
        // log error
        umask(0);
        return false;
    }

    umask(0);
    return true;
}
//------------------------------------------------------------------------------------------------------
IFIFOWrapper* CreateFIFOWrapper(bool serverSide)
{
    return new(std::nothrow) PosixFIFOWrapper;
}
//------------------------------------------------------------------------------------------------------
}

