/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * 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.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $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);

const char* const c_IPCDataPath = "/tmp/Funambol/DMClient/IPC";

const char* const c_FifoWrapperLog = "FifoWrapper";

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

    m_fifo = -1;

    if (!CreatePath(c_IPCDataPath))
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't create path for ipc fifos");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Open. 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 << PosixFIFOWrapper::Open. 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 << PosixFIFOWrapper::Open. Return status: %d", openRes);
            return openRes;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Open. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Read(Buffer& buffer, bool exitIfNoWriter)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> PosixFIFOWrapper::Read. 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 << PosixFIFOWrapper::Read. 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 << PosixFIFOWrapper::Read. Return status: %d", openRes);
                return openRes;
            }
        }

        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 << PosixFIFOWrapper::Read. Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (r == 0)
        {
            if (m_blockedMode && (!exitIfNoWriter))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Read. Return status: %d", e_WriteInstanceNotAlive);
                return e_WriteInstanceNotAlive;
            }
        }
        else // r < 0
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to read from fifo");
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Read. Return status: %d", e_Failed);
            return e_Failed;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Read. 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 >> PosixFIFOWrapper::Write(void* buffer). 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 << PosixFIFOWrapper::Write. 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 << PosixFIFOWrapper::Write. Return status: %d", openRes);
                return openRes;
            }
        }

        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 << PosixFIFOWrapper::Write. Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (w == 0)
        {
            if (m_blockedMode && (!exitIfNoReader))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Write. Return status: %d", e_ReadInstanceNotAlive);
                return e_ReadInstanceNotAlive;
            }
        }
        else // w < 0
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to write to fifo");
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Write. Return status: %d", e_Failed);
            return e_Failed;
        }
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Write. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::Write(const Buffer& buffer, bool exitIfNoReader)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> PosixFIFOWrapper::Write(Buffer& buffer). 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 << PosixFIFOWrapper::Write. Return status: %d", e_Failed);
        return e_Failed;
    }

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

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

    NS_Common::Lock lock(m_stateMutex);

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

    if (m_fifo >= 0)
    {
        Close();
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::Release");
    delete this;
}
//------------------------------------------------------------------------------------------------------
PosixFIFOWrapper::~PosixFIFOWrapper()
{
}
//------------------------------------------------------------------------------------------------------
StatusCode PosixFIFOWrapper::open()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> PosixFIFOWrapper::open. 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", m_name.c_str());
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::open. 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 << PosixFIFOWrapper::open. 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 << PosixFIFOWrapper::open. Return status: %d", e_Failed);
        return e_Failed;
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << PosixFIFOWrapper::open. 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 PosixFIFOWrapper;
}
//------------------------------------------------------------------------------------------------------
}

