/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qplatformdefs.h"
#include "qfiledevice.h"
#include "qfiledevice_p.h"
#include "qfsfileengine_p.h"

#include <private/qmemory_p.h>

#ifdef QT_NO_QOBJECT
#define tr(X) QString::fromLatin1(X)
#endif

QT_BEGIN_NAMESPACE

#ifndef QFILE_WRITEBUFFER_SIZE
#define QFILE_WRITEBUFFER_SIZE 16384
#endif

QFileDevicePrivate::QFileDevicePrivate()
    : cachedSize(0),
      error(QFile::NoError), lastWasWrite(false)
{
    writeBufferChunkSize = QFILE_WRITEBUFFER_SIZE;
}

QFileDevicePrivate::~QFileDevicePrivate()
    = default;

QAbstractFileEngine * QFileDevicePrivate::engine() const
{
    if (!fileEngine)
        fileEngine = qt_make_unique<QFSFileEngine>();
    return fileEngine.get();
}

void QFileDevicePrivate::setError(QFileDevice::FileError err)
{
    error = err;
    errorString.clear();
}

void QFileDevicePrivate::setError(QFileDevice::FileError err, const QString &errStr)
{
    error = err;
    errorString = errStr;
}

void QFileDevicePrivate::setError(QFileDevice::FileError err, int errNum)
{
    error = err;
    errorString = qt_error_string(errNum);
}

/*!
    \enum QFileDevice::FileError

    This enum describes the errors that may be returned by the error()
    function.

    \value NoError          No error occurred.
    \value ReadError        An error occurred when reading from the file.
    \value WriteError       An error occurred when writing to the file.
    \value FatalError       A fatal error occurred.
    \value ResourceError    Out of resources (e.g., too many open files, out of memory, etc.)
    \value OpenError        The file could not be opened.
    \value AbortError       The operation was aborted.
    \value TimeOutError     A timeout occurred.
    \value UnspecifiedError An unspecified error occurred.
    \value RemoveError      The file could not be removed.
    \value RenameError      The file could not be renamed.
    \value PositionError    The position in the file could not be changed.
    \value ResizeError      The file could not be resized.
    \value PermissionsError The file could not be accessed.
    \value CopyError        The file could not be copied.
*/

/*!
    \enum QFileDevice::Permission

    This enum is used by the permission() function to report the
    permissions and ownership of a file. The values may be OR-ed
    together to test multiple permissions and ownership values.

    \value ReadOwner The file is readable by the owner of the file.
    \value WriteOwner The file is writable by the owner of the file.
    \value ExeOwner The file is executable by the owner of the file.
    \value ReadUser The file is readable by the user.
    \value WriteUser The file is writable by the user.
    \value ExeUser The file is executable by the user.
    \value ReadGroup The file is readable by the group.
    \value WriteGroup The file is writable by the group.
    \value ExeGroup The file is executable by the group.
    \value ReadOther The file is readable by anyone.
    \value WriteOther The file is writable by anyone.
    \value ExeOther The file is executable by anyone.

    \warning Because of differences in the platforms supported by Qt,
    the semantics of ReadUser, WriteUser and ExeUser are
    platform-dependent: On Unix, the rights of the owner of the file
    are returned and on Windows the rights of the current user are
    returned. This behavior might change in a future Qt version.

    \note On NTFS file systems, ownership and permissions checking is
    disabled by default for performance reasons. To enable it,
    include the following line:

    \snippet ntfsp.cpp 0

    Permission checking is then turned on and off by incrementing and
    decrementing \c qt_ntfs_permission_lookup by 1.

    \snippet ntfsp.cpp 1

    \note Since this is a non-atomic global variable, it is only safe
    to increment or decrement \c qt_ntfs_permission_lookup before any
    threads other than the main thread have started or after every thread
    other than the main thread has ended.
*/

//************* QFileDevice

/*!
    \class QFileDevice
    \inmodule QtCore
    \since 5.0

    \brief The QFileDevice class provides an interface for reading from and writing to open files.

    \ingroup io

    \reentrant

    QFileDevice is the base class for I/O devices that can read and write text and binary files
    and \l{The Qt Resource System}{resources}. QFile offers the main functionality,
    QFileDevice serves as a base class for sharing functionality with other file devices such
    as QTemporaryFile, by providing all the operations that can be done on files that have
    been opened by QFile or QTemporaryFile.

    \sa QFile, QTemporaryFile
*/

/*!
    \enum QFileDevice::FileHandleFlag

    This enum is used when opening a file to specify additional
    options which only apply to files and not to a generic
    QIODevice.

    \value AutoCloseHandle The file handle passed into open() should be
    closed by close(), the default behavior is that close just flushes
    the file and the application is responsible for closing the file handle.
    When opening a file by name, this flag is ignored as Qt always owns the
    file handle and must close it.
    \value DontCloseHandle If not explicitly closed, the underlying file
    handle is left open when the QFile object is destroyed.
 */

#ifdef QT_NO_QOBJECT
QFileDevice::QFileDevice()
    : QIODevice(*new QFileDevicePrivate)
{
}
QFileDevice::QFileDevice(QFileDevicePrivate &dd)
    : QIODevice(dd)
{
}
#else
/*!
    \internal
*/
QFileDevice::QFileDevice()
    : QIODevice(*new QFileDevicePrivate, nullptr)
{
}
/*!
    \internal
*/
QFileDevice::QFileDevice(QObject *parent)
    : QIODevice(*new QFileDevicePrivate, parent)
{
}
/*!
    \internal
*/
QFileDevice::QFileDevice(QFileDevicePrivate &dd, QObject *parent)
    : QIODevice(dd, parent)
{
}
#endif

/*!
    Destroys the file device, closing it if necessary.
*/
QFileDevice::~QFileDevice()
{
    close();
}

/*!
    Returns \c true if the file can only be manipulated sequentially;
    otherwise returns \c false.

    Most files support random-access, but some special files may not.

    \sa QIODevice::isSequential()
*/
bool QFileDevice::isSequential() const
{
    Q_D(const QFileDevice);
    return d->fileEngine && d->fileEngine->isSequential();
}

/*!
  Returns the file handle of the file.

  This is a small positive integer, suitable for use with C library
  functions such as \c fdopen() and \c fcntl(). On systems that use file
  descriptors for sockets (i.e. Unix systems, but not Windows) the handle
  can be used with QSocketNotifier as well.

  If the file is not open, or there is an error, handle() returns -1.

  \sa QSocketNotifier
*/
int QFileDevice::handle() const
{
    Q_D(const QFileDevice);
    if (!isOpen() || !d->fileEngine)
        return -1;

    return d->fileEngine->handle();
}

/*!
    Returns the name of the file.
    The default implementation in QFileDevice returns a null string.
*/
QString QFileDevice::fileName() const
{
    return QString();
}

/*!
    Flushes any buffered data to the file. Returns \c true if successful;
    otherwise returns \c false.
*/
bool QFileDevice::flush()
{
    Q_D(QFileDevice);
    if (!d->fileEngine) {
        qWarning("QFileDevice::flush: No file engine. Is IODevice open?");
        return false;
    }

    if (!d->writeBuffer.isEmpty()) {
        qint64 size = d->writeBuffer.nextDataBlockSize();
        qint64 written = d->fileEngine->write(d->writeBuffer.readPointer(), size);
        if (written > 0)
            d->writeBuffer.free(written);
        if (written != size) {
            QFileDevice::FileError err = d->fileEngine->error();
            if (err == QFileDevice::UnspecifiedError)
                err = QFileDevice::WriteError;
            d->setError(err, d->fileEngine->errorString());
            return false;
        }
    }

    if (!d->fileEngine->flush()) {
        QFileDevice::FileError err = d->fileEngine->error();
        if (err == QFileDevice::UnspecifiedError)
            err = QFileDevice::WriteError;
        d->setError(err, d->fileEngine->errorString());
        return false;
    }
    return true;
}

/*!
  Calls QFileDevice::flush() and closes the file. Errors from flush are ignored.

  \sa QIODevice::close()
*/
void QFileDevice::close()
{
    Q_D(QFileDevice);
    if (!isOpen())
        return;
    bool flushed = flush();
    QIODevice::close();

    // reset write buffer
    d->lastWasWrite = false;
    d->writeBuffer.clear();

    // reset cached size
    d->cachedSize = 0;

    // keep earlier error from flush
    if (d->fileEngine->close() && flushed)
        unsetError();
    else if (flushed)
        d->setError(d->fileEngine->error(), d->fileEngine->errorString());
}

/*!
  \reimp
*/
qint64 QFileDevice::pos() const
{
    return QIODevice::pos();
}

/*!
  Returns \c true if the end of the file has been reached; otherwise returns
  false.

  For regular empty files on Unix (e.g. those in \c /proc), this function
  returns \c true, since the file system reports that the size of such a file is
  0. Therefore, you should not depend on atEnd() when reading data from such a
  file, but rather call read() until no more data can be read.
*/
bool QFileDevice::atEnd() const
{
    Q_D(const QFileDevice);

    // If there's buffered data left, we're not at the end.
    if (!d->isBufferEmpty())
        return false;

    if (!isOpen())
        return true;

    if (!d->ensureFlushed())
        return false;

    // If the file engine knows best, say what it says.
    if (d->fileEngine->supportsExtension(QAbstractFileEngine::AtEndExtension)) {
        // Check if the file engine supports AtEndExtension, and if it does,
        // check if the file engine claims to be at the end.
        return d->fileEngine->atEnd();
    }

    // if it looks like we are at the end, or if size is not cached,
    // fall through to bytesAvailable() to make sure.
    if (pos() < d->cachedSize)
        return false;

    // Fall back to checking how much is available (will stat files).
    return bytesAvailable() == 0;
}

/*!
    \fn bool QFileDevice::seek(qint64 pos)

    For random-access devices, this function sets the current position
    to \a pos, returning true on success, or false if an error occurred.
    For sequential devices, the default behavior is to do nothing and
    return false.

    Seeking beyond the end of a file:
    If the position is beyond the end of a file, then seek() will not
    immediately extend the file. If a write is performed at this position,
    then the file will be extended. The content of the file between the
    previous end of file and the newly written data is UNDEFINED and
    varies between platforms and file systems.
*/
bool QFileDevice::seek(qint64 off)
{
    Q_D(QFileDevice);
    if (!isOpen()) {
        qWarning("QFileDevice::seek: IODevice is not open");
        return false;
    }

    if (!d->ensureFlushed())
        return false;

    if (!d->fileEngine->seek(off) || !QIODevice::seek(off)) {
        QFileDevice::FileError err = d->fileEngine->error();
        if (err == QFileDevice::UnspecifiedError)
            err = QFileDevice::PositionError;
        d->setError(err, d->fileEngine->errorString());
        return false;
    }
    unsetError();
    return true;
}

/*!
  \reimp
*/
qint64 QFileDevice::readLineData(char *data, qint64 maxlen)
{
    Q_D(QFileDevice);
    if (!d->ensureFlushed())
        return -1;

    qint64 read;
    if (d->fileEngine->supportsExtension(QAbstractFileEngine::FastReadLineExtension)) {
        read = d->fileEngine->readLine(data, maxlen);
    } else {
        // Fall back to QIODevice's readLine implementation if the engine
        // cannot do it faster.
        read = QIODevice::readLineData(data, maxlen);
    }

    if (read < maxlen) {
        // failed to read all requested, may be at the end of file, stop caching size so that it's rechecked
        d->cachedSize = 0;
    }

    return read;
}

/*!
  \reimp
*/
qint64 QFileDevice::readData(char *data, qint64 len)
{
    Q_D(QFileDevice);
    if (!len)
        return 0;
    unsetError();
    if (!d->ensureFlushed())
        return -1;

    const qint64 read = d->fileEngine->read(data, len);
    if (read < 0) {
        QFileDevice::FileError err = d->fileEngine->error();
        if (err == QFileDevice::UnspecifiedError)
            err = QFileDevice::ReadError;
        d->setError(err, d->fileEngine->errorString());
    }

    if (read < len) {
        // failed to read all requested, may be at the end of file, stop caching size so that it's rechecked
        d->cachedSize = 0;
    }

    return read;
}

/*!
    \internal
*/
bool QFileDevicePrivate::putCharHelper(char c)
{
#ifdef QT_NO_QOBJECT
    return QIODevicePrivate::putCharHelper(c);
#else

    // Cutoff for code that doesn't only touch the buffer.
    qint64 writeBufferSize = writeBuffer.size();
    if ((openMode & QIODevice::Unbuffered) || writeBufferSize + 1 >= writeBufferChunkSize
#ifdef Q_OS_WIN
        || ((openMode & QIODevice::Text) && c == '\n'
            && writeBufferSize + 2 >= writeBufferChunkSize)
#endif
        ) {
        return QIODevicePrivate::putCharHelper(c);
    }

    if (!(openMode & QIODevice::WriteOnly)) {
        if (openMode == QIODevice::NotOpen)
            qWarning("QIODevice::putChar: Closed device");
        else
            qWarning("QIODevice::putChar: ReadOnly device");
        return false;
    }

    // Make sure the device is positioned correctly.
    const bool sequential = isSequential();
    if (pos != devicePos && !sequential && !q_func()->seek(pos))
        return false;

    lastWasWrite = true;

    int len = 1;
#ifdef Q_OS_WIN
    if ((openMode & QIODevice::Text) && c == '\n') {
        ++len;
        *writeBuffer.reserve(1) = '\r';
    }
#endif

    // Write to buffer.
    *writeBuffer.reserve(1) = c;

    if (!sequential) {
        pos += len;
        devicePos += len;
        if (!buffer.isEmpty())
            buffer.skip(len);
    }

    return true;
#endif
}

/*!
  \reimp
*/
qint64 QFileDevice::writeData(const char *data, qint64 len)
{
    Q_D(QFileDevice);
    unsetError();
    d->lastWasWrite = true;
    bool buffered = !(d->openMode & Unbuffered);

    // Flush buffered data if this read will overflow.
    if (buffered && (d->writeBuffer.size() + len) > d->writeBufferChunkSize) {
        if (!flush())
            return -1;
    }

    // Write directly to the engine if the block size is larger than
    // the write buffer size.
    if (!buffered || len > d->writeBufferChunkSize) {
        const qint64 ret = d->fileEngine->write(data, len);
        if (ret < 0) {
            QFileDevice::FileError err = d->fileEngine->error();
            if (err == QFileDevice::UnspecifiedError)
                err = QFileDevice::WriteError;
            d->setError(err, d->fileEngine->errorString());
        }
        return ret;
    }

    // Write to the buffer.
    d->writeBuffer.append(data, len);
    return len;
}

/*!
    Returns the file error status.

    The I/O device status returns an error code. For example, if open()
    returns \c false, or a read/write operation returns -1, this function can
    be called to find out the reason why the operation failed.

    \sa unsetError()
*/
QFileDevice::FileError QFileDevice::error() const
{
    Q_D(const QFileDevice);
    return d->error;
}

/*!
    Sets the file's error to QFileDevice::NoError.

    \sa error()
*/
void QFileDevice::unsetError()
{
    Q_D(QFileDevice);
    d->setError(QFileDevice::NoError);
}

/*!
  Returns the size of the file.

  For regular empty files on Unix (e.g. those in \c /proc), this function
  returns 0; the contents of such a file are generated on demand in response
  to you calling read().
*/
qint64 QFileDevice::size() const
{
    Q_D(const QFileDevice);
    if (!d->ensureFlushed())
        return 0;
    d->cachedSize = d->engine()->size();
    return d->cachedSize;
}

/*!
    Sets the file size (in bytes) \a sz. Returns \c true if the
    resize succeeds; false otherwise. If \a sz is larger than the file
    currently is, the new bytes will be set to 0; if \a sz is smaller, the
    file is simply truncated.

    \warning This function can fail if the file doesn't exist.

    \sa size()
*/
bool QFileDevice::resize(qint64 sz)
{
    Q_D(QFileDevice);
    if (!d->ensureFlushed())
        return false;
    d->engine();
    if (isOpen() && d->fileEngine->pos() > sz)
        seek(sz);
    if (d->fileEngine->setSize(sz)) {
        unsetError();
        d->cachedSize = sz;
        return true;
    }
    d->cachedSize = 0;
    d->setError(QFile::ResizeError, d->fileEngine->errorString());
    return false;
}

/*!
    Returns the complete OR-ed together combination of
    QFile::Permission for the file.

    \sa setPermissions()
*/
QFile::Permissions QFileDevice::permissions() const
{
    Q_D(const QFileDevice);
    QAbstractFileEngine::FileFlags perms = d->engine()->fileFlags(QAbstractFileEngine::PermsMask) & QAbstractFileEngine::PermsMask;
    return QFile::Permissions((int)perms); //ewww
}

/*!
    Sets the permissions for the file to the \a permissions specified.
    Returns \c true if successful, or \c false if the permissions cannot be
    modified.

    \warning This function does not manipulate ACLs, which may limit its
    effectiveness.

    \sa permissions()
*/
bool QFileDevice::setPermissions(Permissions permissions)
{
    Q_D(QFileDevice);
    if (d->engine()->setPermissions(permissions)) {
        unsetError();
        return true;
    }
    d->setError(QFile::PermissionsError, d->fileEngine->errorString());
    return false;
}

/*!
    \enum QFileDevice::MemoryMapFlags
    \since 4.4

    This enum describes special options that may be used by the map()
    function.

    \value NoOptions        No options.
    \value MapPrivateOption The mapped memory will be private, so any
    modifications will not be visible to other processes and will not
    be written to disk.  Any such modifications will be lost when the
    memory is unmapped.  It is unspecified whether modifications made
    to the file made after the mapping is created will be visible through
    the mapped memory. This enum value was introduced in Qt 5.4.
*/

/*!
    Maps \a size bytes of the file into memory starting at \a offset.  A file
    should be open for a map to succeed but the file does not need to stay
    open after the memory has been mapped.  When the QFile is destroyed
    or a new file is opened with this object, any maps that have not been
    unmapped will automatically be unmapped.

    The mapping will have the same open mode as the file (read and/or write),
    except when using MapPrivateOption, in which case it is always possible
    to write to the mapped memory.

    Any mapping options can be passed through \a flags.

    Returns a pointer to the memory or \nullptr if there is an error.

    \sa unmap()
 */
uchar *QFileDevice::map(qint64 offset, qint64 size, MemoryMapFlags flags)
{
    Q_D(QFileDevice);
    if (d->engine()
            && d->fileEngine->supportsExtension(QAbstractFileEngine::MapExtension)) {
        unsetError();
        uchar *address = d->fileEngine->map(offset, size, flags);
        if (address == nullptr)
            d->setError(d->fileEngine->error(), d->fileEngine->errorString());
        return address;
    }
    return nullptr;
}

/*!
    Unmaps the memory \a address.

    Returns \c true if the unmap succeeds; false otherwise.

    \sa map()
 */
bool QFileDevice::unmap(uchar *address)
{
    Q_D(QFileDevice);
    if (d->engine()
        && d->fileEngine->supportsExtension(QAbstractFileEngine::UnMapExtension)) {
        unsetError();
        bool success = d->fileEngine->unmap(address);
        if (!success)
            d->setError(d->fileEngine->error(), d->fileEngine->errorString());
        return success;
    }
    d->setError(PermissionsError, tr("No file engine available or engine does not support UnMapExtension"));
    return false;
}

/*!
    \enum QFileDevice::FileTime
    \since 5.10

    This enum is used by the fileTime() and setFileTime() functions.

    \value FileAccessTime           When the file was most recently accessed
                                    (e.g. read or written to).
    \value FileBirthTime            When the file was created (may not be not
                                    supported on UNIX).
    \value FileMetadataChangeTime   When the file's metadata was last changed.
    \value FileModificationTime     When the file was most recently modified.

    \sa setFileTime(), fileTime(), QFileInfo::fileTime()
*/

static inline QAbstractFileEngine::FileTime FileDeviceTimeToAbstractFileEngineTime(QFileDevice::FileTime time)
{
    Q_STATIC_ASSERT(int(QFileDevice::FileAccessTime) == int(QAbstractFileEngine::AccessTime));
    Q_STATIC_ASSERT(int(QFileDevice::FileBirthTime) == int(QAbstractFileEngine::BirthTime));
    Q_STATIC_ASSERT(int(QFileDevice::FileMetadataChangeTime) == int(QAbstractFileEngine::MetadataChangeTime));
    Q_STATIC_ASSERT(int(QFileDevice::FileModificationTime) == int(QAbstractFileEngine::ModificationTime));
    return QAbstractFileEngine::FileTime(time);
}

/*!
    \since 5.10
    Returns the file time specified by \a time.
    If the time cannot be determined return QDateTime() (an invalid
    date time).

    \sa setFileTime(), FileTime, QDateTime::isValid()
*/
QDateTime QFileDevice::fileTime(QFileDevice::FileTime time) const
{
    Q_D(const QFileDevice);

    if (d->engine())
        return d->engine()->fileTime(FileDeviceTimeToAbstractFileEngineTime(time));

    return QDateTime();
}

/*!
    \since 5.10
    Sets the file time specified by \a fileTime to \a newDate, returning true
    if successful; otherwise returns false.

    \note The file must be open to use this function.

    \sa fileTime(), FileTime
*/
bool QFileDevice::setFileTime(const QDateTime &newDate, QFileDevice::FileTime fileTime)
{
    Q_D(QFileDevice);

    if (!d->engine()) {
        d->setError(QFileDevice::UnspecifiedError, tr("No file engine available"));
        return false;
    }

    if (!d->fileEngine->setFileTime(newDate, FileDeviceTimeToAbstractFileEngineTime(fileTime))) {
        d->setError(d->fileEngine->error(), d->fileEngine->errorString());
        return false;
    }

    unsetError();
    return true;
}

QT_END_NAMESPACE

#ifndef QT_NO_QOBJECT
#include "moc_qfiledevice.cpp"
#endif
