/*
 * Copyright (C) 2005 The Android Open Source Project
 *
 * 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.
 */

//
// Unidirectional pipe.
//

#include <utils/Pipe.h>
#include <utils/Log.h>

#if defined(HAVE_WIN32_IPC)
# include <windows.h>
#else
# include <fcntl.h>
# include <unistd.h>
# include <errno.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

using namespace android;

const unsigned long kInvalidHandle = (unsigned long) -1;


/*
 * Constructor.  Do little.
 */
Pipe::Pipe(void)
    : mReadNonBlocking(false), mReadHandle(kInvalidHandle),
      mWriteHandle(kInvalidHandle)
{
}

/*
 * Destructor.  Use the system-appropriate close call.
 */
Pipe::~Pipe(void)
{
#if defined(HAVE_WIN32_IPC)
    if (mReadHandle != kInvalidHandle) {
        if (!CloseHandle((HANDLE)mReadHandle))
            LOG(LOG_WARN, "pipe", "failed closing read handle (%ld)\n",
                mReadHandle);
    }
    if (mWriteHandle != kInvalidHandle) {
        FlushFileBuffers((HANDLE)mWriteHandle);
        if (!CloseHandle((HANDLE)mWriteHandle))
            LOG(LOG_WARN, "pipe", "failed closing write handle (%ld)\n",
                mWriteHandle);
    }
#else
    if (mReadHandle != kInvalidHandle) {
        if (close((int) mReadHandle) != 0)
            LOG(LOG_WARN, "pipe", "failed closing read fd (%d)\n",
                (int) mReadHandle);
    }
    if (mWriteHandle != kInvalidHandle) {
        if (close((int) mWriteHandle) != 0)
            LOG(LOG_WARN, "pipe", "failed closing write fd (%d)\n",
                (int) mWriteHandle);
    }
#endif
}

/*
 * Create the pipe.
 *
 * Use the POSIX stuff for everything but Windows.
 */
bool Pipe::create(void)
{
    assert(mReadHandle == kInvalidHandle);
    assert(mWriteHandle == kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    /* we use this across processes, so they need to be inheritable */
    HANDLE handles[2];
    SECURITY_ATTRIBUTES saAttr;

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    if (!CreatePipe(&handles[0], &handles[1], &saAttr, 0)) {
        LOG(LOG_ERROR, "pipe", "unable to create pipe\n");
        return false;
    }
    mReadHandle = (unsigned long) handles[0];
    mWriteHandle = (unsigned long) handles[1];
    return true;
#else
    int fds[2];

    if (pipe(fds) != 0) {
        LOG(LOG_ERROR, "pipe", "unable to create pipe\n");
        return false;
    }
    mReadHandle = fds[0];
    mWriteHandle = fds[1];
    return true;
#endif
}

/*
 * Create a "half pipe".  Please, no Segway riding.
 */
bool Pipe::createReader(unsigned long handle)
{
    mReadHandle = handle;
    assert(mWriteHandle == kInvalidHandle);
    return true;
}

/*
 * Create a "half pipe" for writing.
 */
bool Pipe::createWriter(unsigned long handle)
{
    mWriteHandle = handle;
    assert(mReadHandle == kInvalidHandle);
    return true;
}

/*
 * Return "true" if create() has been called successfully.
 */
bool Pipe::isCreated(void)
{
    // one or the other should be open
    return (mReadHandle != kInvalidHandle || mWriteHandle != kInvalidHandle);
}


/*
 * Read data from the pipe.
 *
 * For Linux and Darwin, just call read().  For Windows, implement
 * non-blocking reads by calling PeekNamedPipe first.
 */
int Pipe::read(void* buf, int count)
{
    assert(mReadHandle != kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    DWORD totalBytesAvail = count;
    DWORD bytesRead;

    if (mReadNonBlocking) {
        // use PeekNamedPipe to adjust read count expectations
        if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL,
                &totalBytesAvail, NULL))
        {
            LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n");
            return -1;
        }

        if (totalBytesAvail == 0)
            return 0;
    }

    if (!ReadFile((HANDLE) mReadHandle, buf, totalBytesAvail, &bytesRead,
            NULL))
    {
        DWORD err = GetLastError();
        if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE)
            return 0;
        LOG(LOG_ERROR, "pipe", "ReadFile failed (err=%ld)\n", err);
        return -1;
    }

    return (int) bytesRead;
#else
    int cc;
    cc = ::read(mReadHandle, buf, count);
    if (cc < 0 && errno == EAGAIN)
        return 0;
    return cc;
#endif
}

/*
 * Write data to the pipe.
 *
 * POSIX systems are trivial, Windows uses a different call and doesn't
 * handle non-blocking writes.
 *
 * If we add non-blocking support here, we probably want to make it an
 * all-or-nothing write.
 *
 * DO NOT use LOG() here, we could be writing a log message.
 */
int Pipe::write(const void* buf, int count)
{
    assert(mWriteHandle != kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    DWORD bytesWritten;

    if (mWriteNonBlocking) {
        // BUG: can't use PeekNamedPipe() to get the amount of space
        // left.  Looks like we need to use "overlapped I/O" functions.
        // I just don't care that much.
    }

    if (!WriteFile((HANDLE) mWriteHandle, buf, count, &bytesWritten, NULL)) {
        // can't LOG, use stderr
        fprintf(stderr, "WriteFile failed (err=%ld)\n", GetLastError());
        return -1;
    }

    return (int) bytesWritten;
#else
    int cc;
    cc = ::write(mWriteHandle, buf, count);
    if (cc < 0 && errno == EAGAIN)
        return 0;
    return cc;
#endif
}

/*
 * Figure out if there is data available on the read fd.
 *
 * We return "true" on error because we want the caller to try to read
 * from the pipe.  They'll notice the read failure and do something
 * appropriate.
 */
bool Pipe::readReady(void)
{
    assert(mReadHandle != kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    DWORD totalBytesAvail;

    if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL,
            &totalBytesAvail, NULL))
    {
        LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n");
        return true;
    }

    return (totalBytesAvail != 0);
#else
    errno = 0;
    fd_set readfds;
    struct timeval tv = { 0, 0 };
    int cc;

    FD_ZERO(&readfds);
    FD_SET(mReadHandle, &readfds);

    cc = select(mReadHandle+1, &readfds, NULL, NULL, &tv);
    if (cc < 0) {
        LOG(LOG_ERROR, "pipe", "select() failed\n");
        return true;
    } else if (cc == 0) {
        /* timed out, nothing available */
        return false;
    } else if (cc == 1) {
        /* our fd is ready */
        return true;
    } else {
        LOG(LOG_ERROR, "pipe", "HUH? select() returned > 1\n");
        return true;
    }
#endif
}

/*
 * Enable or disable non-blocking mode for the read descriptor.
 *
 * NOTE: the calls succeed under Mac OS X, but the pipe doesn't appear to
 * actually be in non-blocking mode.  If this matters -- i.e. you're not
 * using a select() call -- put a call to readReady() in front of the
 * ::read() call, with a PIPE_NONBLOCK_BROKEN #ifdef in the Makefile for
 * Darwin.
 */
bool Pipe::setReadNonBlocking(bool val)
{
    assert(mReadHandle != kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    // nothing to do
#else
    int flags;

    if (fcntl(mReadHandle, F_GETFL, &flags) == -1) {
        LOG(LOG_ERROR, "pipe", "couldn't get flags for pipe read fd\n");
        return false;
    }
    if (val)
        flags |= O_NONBLOCK;
    else
        flags &= ~(O_NONBLOCK);
    if (fcntl(mReadHandle, F_SETFL, &flags) == -1) {
        LOG(LOG_ERROR, "pipe", "couldn't set flags for pipe read fd\n");
        return false;
    }
#endif

    mReadNonBlocking = val;
    return true;
}

/*
 * Enable or disable non-blocking mode for the write descriptor.
 *
 * As with setReadNonBlocking(), this does not work on the Mac.
 */
bool Pipe::setWriteNonBlocking(bool val)
{
    assert(mWriteHandle != kInvalidHandle);

#if defined(HAVE_WIN32_IPC)
    // nothing to do
#else
    int flags;

    if (fcntl(mWriteHandle, F_GETFL, &flags) == -1) {
        LOG(LOG_WARN, "pipe",
            "Warning: couldn't get flags for pipe write fd (errno=%d)\n",
            errno);
        return false;
    }
    if (val)
        flags |= O_NONBLOCK;
    else
        flags &= ~(O_NONBLOCK);
    if (fcntl(mWriteHandle, F_SETFL, &flags) == -1) {
        LOG(LOG_WARN, "pipe",
            "Warning: couldn't set flags for pipe write fd (errno=%d)\n",
            errno);
        return false;
    }
#endif

    mWriteNonBlocking = val;
    return true;
}

/*
 * Specify whether a file descriptor can be inherited by a child process.
 * Under Linux this means setting the close-on-exec flag, under Windows
 * this is SetHandleInformation(HANDLE_FLAG_INHERIT).
 */
bool Pipe::disallowReadInherit(void)
{
    if (mReadHandle == kInvalidHandle)
        return false;

#if defined(HAVE_WIN32_IPC)
    if (SetHandleInformation((HANDLE) mReadHandle, HANDLE_FLAG_INHERIT, 0) == 0)
        return false;
#else
    if (fcntl((int) mReadHandle, F_SETFD, FD_CLOEXEC) != 0)
        return false;
#endif
    return true;
}
bool Pipe::disallowWriteInherit(void)
{
    if (mWriteHandle == kInvalidHandle)
        return false;

#if defined(HAVE_WIN32_IPC)
    if (SetHandleInformation((HANDLE) mWriteHandle, HANDLE_FLAG_INHERIT, 0) == 0)
        return false;
#else
    if (fcntl((int) mWriteHandle, F_SETFD, FD_CLOEXEC) != 0)
        return false;
#endif
    return true;
}

/*
 * Close read descriptor.
 */
bool Pipe::closeRead(void)
{
    if (mReadHandle == kInvalidHandle)
        return false;

#if defined(HAVE_WIN32_IPC)
    if (mReadHandle != kInvalidHandle) {
        if (!CloseHandle((HANDLE)mReadHandle)) {
            LOG(LOG_WARN, "pipe", "failed closing read handle\n");
            return false;
        }
    }
#else
    if (mReadHandle != kInvalidHandle) {
        if (close((int) mReadHandle) != 0) {
            LOG(LOG_WARN, "pipe", "failed closing read fd\n");
            return false;
        }
    }
#endif
    mReadHandle = kInvalidHandle;
    return true;
}

/*
 * Close write descriptor.
 */
bool Pipe::closeWrite(void)
{
    if (mWriteHandle == kInvalidHandle)
        return false;

#if defined(HAVE_WIN32_IPC)
    if (mWriteHandle != kInvalidHandle) {
        if (!CloseHandle((HANDLE)mWriteHandle)) {
            LOG(LOG_WARN, "pipe", "failed closing write handle\n");
            return false;
        }
    }
#else
    if (mWriteHandle != kInvalidHandle) {
        if (close((int) mWriteHandle) != 0) {
            LOG(LOG_WARN, "pipe", "failed closing write fd\n");
            return false;
        }
    }
#endif
    mWriteHandle = kInvalidHandle;
    return true;
}

/*
 * Get the read handle.
 */
unsigned long Pipe::getReadHandle(void)
{
    assert(mReadHandle != kInvalidHandle);

    return mReadHandle;
}

/*
 * Get the write handle.
 */
unsigned long Pipe::getWriteHandle(void)
{
    assert(mWriteHandle != kInvalidHandle);

    return mWriteHandle;
}