/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/

/****************************************************************************
**
** Copyright (C) 2023-2026 Linaro Limited (or its affiliates). All rights reserved.
** Copyright (C) 2009-2023 Arm Limited (or its affiliates).
**
****************************************************************************/

//#define QPIPEWRITER_DEBUG

#define BSD_COMP
#include <QFile>
#include <QTimer>
#include <QSocketNotifier>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "qpipewriter_p.h"
#if defined (Q_OS_WIN)
#include <io.h>
#else
#include <unistd.h>
#endif

#if defined QPIPEWRITER_DEBUG
static QByteArray qt_prettyDebug(const char *data, int len, int maxSize)
{
    if (!data) return "(null)";
    QByteArray out;
    for (int i = 0; i < len; ++i) {
        char c = data[i];
        if (isprint(c)) {
            out += c;
        } else switch (c) {
        case '\n': out += "\\n"; break;
        case '\r': out += "\\r"; break;
        case '\t': out += "\\t"; break;
        default:
            QString tmp;
            tmp = QString::asprintf("\\%o", c);
            out += tmp.toLocal8Bit();
        }
    }

    if (len < maxSize)
        out += "...";

    return out;
}
#endif

QPipeWriterPrivate::QPipeWriterPrivate()
{
    q_ptr = 0;
    eof = false;
    notifier = 0;
    pipe = INVALID_Q_PIPE;
    openTimer = 0;
    logFile = 0;
    closeWhenBufferEmpty = false;
}

QPipeWriterPrivate::~QPipeWriterPrivate()
{
    delete notifier;
    delete openTimer;
}

void QPipeWriterPrivate::startTimer()
{
    Q_Q(QPipeWriter);
    openTries = 0;
    openTimer = new QTimer();
    QObject::connect(openTimer, SIGNAL(timeout()), q, SLOT(_q_tryOpen()));
    openTimer->start(200);
}

qint64 QPipeWriterPrivate::write(const char *data, qint64 maxlen)
{
    extern qint64 qt_native_write(Q_PIPE pipe, const char *data, qint64 len);
    extern void qt_ignore_sigpipe();

    qt_ignore_sigpipe();

    qint64 written = qt_native_write(pipe, data, maxlen);
#if defined QPIPEWRITER_DEBUG
    qDebug("QPipeWriterPrivate::write(%p \"%s\", %lld) == %lld",
           data, qt_prettyDebug(data, maxlen, 16).constData(), maxlen, written);
#endif
    // Ticket #12198: AIX select says a pipe is ready for writing even if only
    // one byte can be written, but then fails any writes bigger than that.
    // We want to retry the write rather than closing the file descriptor so
    // change the -1 to 0.
    if (written == -1 && errno == EAGAIN) {
        written = qt_native_write(pipe, data, 1);
        if (written == -1 && errno == EAGAIN)
            written = 0;
    }
    return written;
}

/*! \internal
*/
bool QPipeWriterPrivate::_q_canWrite()
{
    Q_Q(QPipeWriter);
    if (notifier)
        notifier->setEnabled(false);

    if (buffer.isEmpty()) {
#if defined QPIPEWRITER_DEBUG
        qDebug("QPipeWriter::canWrite(), not writing anything (empty write buffer).");
#endif
        return false;
    }

    qint64 written = write(buffer.readPointer(),
                           buffer.nextDataBlockSize());
    if (written < 0) {
        if (errno == EPIPE) {
#if defined(QPIPEWRITER_DEBUG)
            qDebug("QPipeWriterPrivate::canWrite(), end-of-file");
#endif
            eof = true;
            emit q->eof();
            return false;
        } else {
#if defined(QPIPEWRITER_DEBUG)
            qDebug("QPipeWriterPrivate::canWrite(), failed to write (%s)", strerror(errno));
#endif
            q->setErrorString(QPipeWriter::tr("write error"));
            emit q->writeError();
            return false;
        }
    }

#if defined QPIPEWRITER_DEBUG
    qDebug("QPipeWriterPrivate::canWrite(), wrote %d bytes to the pipe", int(written));
#endif

    buffer.free(written);
    emit q->bytesWritten(written);
    if (notifier && !buffer.isEmpty())
        notifier->setEnabled(true);
    else if (buffer.isEmpty() && closeWhenBufferEmpty)
        close();
    return true;
}

void QPipeWriterPrivate::setPipeDescriptor(Q_PIPE pipe)
{
    this->pipe = pipe;
    eof = false;
    createNotifier();
}

void QPipeWriterPrivate::close()
{
    closePipe();
    eof = false;
    buffer.clear();
    delete notifier;
    notifier = 0;
}

void QPipeWriterPrivate::_q_tryOpen()
{
    extern Q_PIPE qt_open_write_pipe(const char* pathname);
    Q_PIPE pipe;
    do 
    {
        pipe = qt_open_write_pipe(nativeFilename.constData());
    } while (pipe == INVALID_Q_PIPE && errno == EINTR);

    if (pipe == INVALID_Q_PIPE && errno == ENXIO && ++openTries < 1000) {
        if (openTries == 10)
            openTimer->setInterval(500);
        else if (openTries == 100)
            openTimer->setInterval(2000);
        return;
    }

    delete openTimer;
    openTimer = 0;

    if (pipe != INVALID_Q_PIPE)
    {
        setPipeDescriptor(pipe);
        _q_canWrite();
    }
}

QPipeWriter::QPipeWriter( QObject *parent ) :
    QIODevice(parent), d_ptr(new QPipeWriterPrivate)
{
    d_ptr->q_ptr = this;
}

QPipeWriter::~QPipeWriter()
{
    flush();
}

qint64 QPipeWriter::readData ( char *, qint64 )
{
    return -1;
}

qint64 QPipeWriter::writeData ( const char *data, qint64 len )
{
    Q_D(QPipeWriter);

    if (d->logFile) {
        if (d->timestampFn)
            d->logFile->write('[' + d->timestampFn() + ']');
        d->logFile->write(data, len);
        d->logFile->flush();
    }

    if (len == 1) {
        d->buffer.putChar(*data);
        if (d->notifier)
            d->notifier->setEnabled(true);
#if defined QPIPEWRITER_DEBUG
        qDebug("QPipeWriter::writeData(%p \"%s\", %lld) == 1 (written to buffer)",
               data, qt_prettyDebug(data, len, 16).constData(), len);
#endif
        return 1;
    }

    char *dest = d->buffer.reserve(len);
    memcpy(dest, data, len);
    if (d->notifier)
        d->notifier->setEnabled(true);
#if defined QPIPEWRITER_DEBUG
    qDebug("QPipeWriter::writeData(%p \"%s\", %lld) == %lld (written to buffer)",
           data, qt_prettyDebug(data, len, 16).constData(), len, len);
#endif
    return len;
}

bool QPipeWriter::open(Q_PIPE pipe, OpenMode mode)
{
    Q_D(QPipeWriter);
    if (isOpen()) {
        qWarning("QPipeWriter::open: Pipe already open");
        return false;
    }
    if (mode & ReadOnly) {
        qWarning("QPipeWriter::open: Pipe must be opened write-only");
        return false;
    }
    if (pipe == INVALID_Q_PIPE) {
        qWarning("QPipeWriter::open: Invalid file descriptor");
        return false;
    }
    d->setPipeDescriptor(pipe);
    if (!QIODevice::open(mode)) {
        qWarning("QPipeWriter::open: QIODevice::open(OpenMode) returned false");
    }
    return true;
}

bool QPipeWriter::open ( const QString& filename, OpenMode mode )
{
    Q_D(QPipeWriter);
    if (isOpen()) {
        qWarning("QPipeWriter::open: Pipe already open");
        return false;
    }
    if (mode & ReadOnly) {
        qWarning("QPipeWriter::open: Pipe must be opened write-only");
        return false;
    }
    if (filename.isEmpty()) {
        qWarning("QPipeWriter::open: No filename specified");
        return false;
    }
    QByteArray nativeFilename = QFile::encodeName(filename);
    extern Q_PIPE qt_open_write_pipe(const char* pathname);
    Q_PIPE pipe;
    do {
        pipe = qt_open_write_pipe(nativeFilename.constData());
    } while (pipe == INVALID_Q_PIPE && errno == EINTR);

    if (pipe == INVALID_Q_PIPE) 
    {
        if (errno == ENXIO) 
        {
            d->nativeFilename = nativeFilename;
            d->startTimer();
        } 
        else 
        {
            return false;
        }
    } 
    else 
    {
        d->setPipeDescriptor(pipe);
    }
    QIODevice::open(mode);
    return true;
}

void QPipeWriter::close()
{
    Q_D(QPipeWriter);
    emit aboutToClose();
    if (d->pipe != INVALID_Q_PIPE) 
    {
        if (!d->buffer.isEmpty())
            d->closeWhenBufferEmpty = true;
        else
            d->close();
    } else if (d->openTimer) {
        d->closeWhenBufferEmpty = true;
    }
    QIODevice::close();
}

void QPipeWriter::flush()
{
    Q_D(QPipeWriter);
    if (d->pipe != INVALID_Q_PIPE)
    {
        d->_q_canWrite();
    }
}

/*! \reimp
*/
bool QPipeWriter::isSequential() const
{
    return true;
}

/*! \reimp
*/
qint64 QPipeWriter::bytesToWrite() const
{
    Q_D(const QPipeWriter);
    return d->buffer.size();
}

bool QPipeWriter::isEof() const
{
    Q_D(const QPipeWriter);
    return d->eof;
}

void QPipeWriter::setLogFile(QFile *logFile, const std::function<QByteArray()>& timestampFn)
{
    Q_D(QPipeWriter);
    d->logFile = logFile;
    d->timestampFn = timestampFn;
}

QFile *QPipeWriter::logFile() const
{
    Q_D(const QPipeWriter);
    return d->logFile;
}

Q_PIPE QPipeWriter::fd() const
{
    Q_D(const QPipeWriter);
    return d->pipe;
}

#include "moc_qpipewriter.cpp"
