/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */

#include <config_features.h>
#include <o3tl/safeint.hxx>
#include <o3tl/typed_flags_set.hxx>
#include <sal/log.hxx>
#include <osl/detail/file.h>
#include <rtl/byteseq.h>
#include <rtl/string.hxx>
#include <o3tl/string_view.hxx>

#include <setallowedpaths.hxx>

#include "system.hxx"
#include "createfilehandlefromfd.hxx"
#include "file_error_transl.hxx"
#include "file_impl.hxx"
#include "file_url.hxx"
#include "uunxapi.hxx"
#include "unixerrnostring.hxx"

#include <algorithm>
#include <atomic>
#include <cassert>
#include <fcntl.h>
#include <limits>
#include <limits.h>
#include <utility>

#include <string.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

#if defined(MACOSX)

#include <sys/param.h>
#include <sys/mount.h>
#define HAVE_O_EXLOCK

#include <CoreFoundation/CoreFoundation.h>

#endif /* MACOSX */

#ifdef ANDROID
#include <osl/detail/android-bootstrap.h>
#include <android/log.h>
#include <android/asset_manager.h>
#endif

#include <vector>

#ifdef LINUX
#include <sys/vfs.h>
// As documented by the kernel
constexpr decltype(std::declval<struct statfs>().f_type) SMB_SUPER_MAGIC = 0x517B;
constexpr decltype(std::declval<struct statfs>().f_type) CIFS_SUPER_MAGIC = 0xFF534D42;
constexpr decltype(std::declval<struct statfs>().f_type) SMB2_SUPER_MAGIC = 0xFE534D42;
#endif

namespace {

enum class State
{
    Seekable  = 1, /*< default */
    Readable  = 2, /*< default */
    Writeable = 4, /*< open() sets, write() requires, else osl_File_E_BADF */
    Modified  = 8  /*< write() sets, flush() resets  */
};

}

template<> struct o3tl::typed_flags<State>: o3tl::is_typed_flags<State, 15> {};

namespace {

struct FileHandle_Impl
{
    pthread_mutex_t m_mutex;
    OString         m_strFilePath; /*< holds native file path */
    int             m_fd;

    enum Kind
    {
        KIND_FD = 1,
        KIND_MEM = 2
    };
    int          m_kind;
    /** State
     */
    State m_state;

    sal_uInt64   m_size;    /*< file size */
    off_t        m_offset;  /*< physical offset from begin of file */
    // m_fileptr is hit hard in some situations, where the overhead of a mutex starts to show up, so use an atomic
    std::atomic<off_t> m_fileptr; /*< logical offset from begin of file */

    off_t        m_bufptr;  /*< buffer offset from begin of file */
    size_t       m_buflen;  /*< buffer filled [0, m_bufsiz - 1] */

    size_t       m_bufsiz;
    sal_uInt8 *  m_buffer;
#ifdef ANDROID
    rtl_String*  m_memstreambuf; /*< used for in-memory streams */
#endif

    explicit FileHandle_Impl(int fd, Kind kind = KIND_FD, OString path = "<anon>"_ostr);
    ~FileHandle_Impl();

    static size_t getpagesize();

    sal_uInt64   getPos() const;
    void setPos(sal_uInt64 uPos);

    sal_uInt64   getSize() const;
    oslFileError setSize(sal_uInt64 uSize);

    oslFileError readAt(
        off_t        nOffset,
        void*        pBuffer,
        size_t       nBytesRequested,
        sal_uInt64*  pBytesRead);

    oslFileError writeAt(
        off_t        nOffset,
        void const*  pBuffer,
        size_t       nBytesToWrite,
        sal_uInt64*  pBytesWritten);

    oslFileError readFileAt(
        off_t        nOffset,
        void*        pBuffer,
        size_t       nBytesRequested,
        sal_uInt64*  pBytesRead);

    oslFileError writeFileAt(
        off_t        nOffset,
        void const*  pBuffer,
        size_t       nBytesToWrite,
        sal_uInt64*  pBytesWritten);

    oslFileError readLineAt(
        off_t           nOffset,
        sal_Sequence**  ppSequence,
        sal_uInt64*     pBytesRead);

    static oslFileError writeSequence_Impl(
        sal_Sequence**  ppSequence,
        size_t*         pnOffset,
        const void*     pBuffer,
        size_t          nBytes);

    oslFileError syncFile();

    class Guard
    {
        pthread_mutex_t *m_mutex;

    public:
        explicit Guard(pthread_mutex_t *pMutex);
        ~Guard();
    };
};

}

FileHandle_Impl::Guard::Guard(pthread_mutex_t * pMutex)
    : m_mutex(pMutex)
{
    assert(m_mutex);
    (void) pthread_mutex_lock(m_mutex); // ignoring EINVAL if a null mutex is passed ...
}

FileHandle_Impl::Guard::~Guard()
{
    assert(m_mutex);
    (void) pthread_mutex_unlock(m_mutex);
}

FileHandle_Impl::FileHandle_Impl(int fd, enum Kind kind, OString path)
    : m_strFilePath(std::move(path)),
      m_fd      (fd),
      m_kind    (kind),
      m_state   (State::Seekable | State::Readable),
      m_size    (0),
      m_offset  (0),
      m_fileptr (0),
      m_bufptr  (-1),
      m_buflen  (0),
      m_bufsiz  (0),
      m_buffer  (nullptr)
{
    (void) pthread_mutex_init(&m_mutex, nullptr);
    if (m_kind == KIND_FD)
    {
        size_t const pagesize = getpagesize();
        if (pagesize != size_t(-1))
        {
            m_bufsiz = pagesize;
            m_buffer = static_cast<sal_uInt8 *>(calloc(1, m_bufsiz));
        }
    }
}

FileHandle_Impl::~FileHandle_Impl()
{
    if (m_kind == KIND_FD)
    {
        free(m_buffer);
        m_buffer = nullptr;
    }

    (void) pthread_mutex_destroy(&m_mutex); // ignoring EBUSY ...
}

size_t FileHandle_Impl::getpagesize()
{
    return sal::static_int_cast< size_t >(::sysconf(_SC_PAGESIZE));
}

sal_uInt64 FileHandle_Impl::getPos() const
{
    return sal::static_int_cast< sal_uInt64 >(m_fileptr.load());
}

void FileHandle_Impl::setPos(sal_uInt64 uPos)
{
    m_fileptr = sal::static_int_cast< off_t >(uPos);
}

sal_uInt64 FileHandle_Impl::getSize() const
{
    off_t const bufend = std::max(off_t(0), m_bufptr) + m_buflen;
    return std::max(m_size, sal::static_int_cast< sal_uInt64 >(bufend));
}

oslFileError FileHandle_Impl::setSize(sal_uInt64 uSize)
{
    off_t const nSize = sal::static_int_cast< off_t >(uSize);
    if (ftruncate_with_name(m_fd, nSize, m_strFilePath) == -1)
    {
        /* Failure. Save original result. Try fallback algorithm */
        oslFileError result = oslTranslateFileError(errno);

        /* Check against current size. Fail upon 'shrink' */
        if (uSize <= getSize())
        {
            /* Failure upon 'shrink'. Return original result */
            return result;
        }

        /* Save current position */
        off_t const nCurPos = lseek(m_fd, off_t(0), SEEK_CUR);
        if (nCurPos == off_t(-1))
        {
            int e = errno;
            SAL_INFO("sal.file", "lseek(" << m_fd << ",0,SEEK_CUR): " << UnixErrnoString(e));
            return result;
        }
        else
            SAL_INFO("sal.file", "lseek(" << m_fd << ",0,SEEK_CUR): OK");

        /* Try 'expand' via 'lseek()' and 'write()' */
        if (lseek(m_fd, static_cast<off_t>(nSize - 1), SEEK_SET) == -1)
        {
            int e = errno;
            SAL_INFO("sal.file", "lseek(" << m_fd << "," << nSize - 1 << ",SEEK_SET): " << UnixErrnoString(e));
            return result;
        }
        else
            SAL_INFO("sal.file", "lseek(" << m_fd << "," << nSize - 1 << ",SEEK_SET): OK");

        if (write(m_fd, "", size_t(1)) == -1)
        {
            /* Failure. Restore saved position */
            int e = errno;
            SAL_INFO("sal.file", "write(" << m_fd << ",\"\",1): " << UnixErrnoString(e));
            (void) lseek(m_fd, nCurPos, SEEK_SET);
            return result;
        }
        else
            SAL_INFO("sal.file", "write(" << m_fd << ",\"\",1): OK");

        /* Success. Restore saved position */
        if (lseek(m_fd, nCurPos, SEEK_SET) == -1)
            return result;
    }

    m_size = sal::static_int_cast< sal_uInt64 >(nSize);
    return osl_File_E_None;
}

oslFileError FileHandle_Impl::readAt(
    off_t        nOffset,
    void *       pBuffer,
    size_t       nBytesRequested,
    sal_uInt64 * pBytesRead)
{
    SAL_WARN_IF(!(m_state & State::Seekable), "sal.osl", "FileHandle_Impl::readAt(): not seekable");
    if (!(m_state & State::Seekable))
        return osl_File_E_SPIPE;

    SAL_WARN_IF(!(m_state & State::Readable), "sal.osl", "FileHandle_Impl::readAt(): not readable");
    if (!(m_state & State::Readable))
        return osl_File_E_BADF;

    if (m_kind == KIND_MEM)
    {
        ssize_t nBytes;

        m_offset = nOffset;

        if (o3tl::make_unsigned(m_offset) >= m_size)
        {
            nBytes = 0;
        }
        else
        {
            nBytes = std::min(nBytesRequested, static_cast<size_t>(m_size - m_offset));
            memmove(pBuffer, m_buffer + m_offset, nBytes);
            m_offset += nBytes;
        }
        *pBytesRead = nBytes;
        return osl_File_E_None;
    }

    ssize_t nBytes = ::pread(m_fd, pBuffer, nBytesRequested, nOffset);
    if ((nBytes == -1) && (errno == EOVERFLOW))
    {
        /* Some 'pread()'s fail with EOVERFLOW when reading at (or past)
         * end-of-file, different from 'lseek() + read()' behaviour.
         * Returning '0 bytes read' and 'osl_File_E_None' instead.
         */
        nBytes = 0;
    }

    if (nBytes == -1)
        return oslTranslateFileError(errno);

    *pBytesRead = nBytes;

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::writeAt(
    off_t        nOffset,
    void const * pBuffer,
    size_t       nBytesToWrite,
    sal_uInt64 * pBytesWritten)
{
    SAL_WARN_IF(!(m_state & State::Seekable), "sal.osl", "FileHandle_Impl::writeAt(): not seekable");
    if (!(m_state & State::Seekable))
        return osl_File_E_SPIPE;

    SAL_WARN_IF(!(m_state & State::Writeable), "sal.osl", "FileHandle_Impl::writeAt(): not writeable");
    if (!(m_state & State::Writeable))
        return osl_File_E_BADF;

    ssize_t nBytes = ::pwrite(m_fd, pBuffer, nBytesToWrite, nOffset);
    if (nBytes == -1)
        return oslTranslateFileError(errno);

    m_size = std::max(m_size, sal::static_int_cast< sal_uInt64 >(nOffset + nBytes));

    *pBytesWritten = nBytes;

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::readFileAt(
    off_t        nOffset,
    void*        pBuffer,
    size_t       nBytesRequested,
    sal_uInt64*  pBytesRead)
{
    if (!(m_state & State::Seekable))
    {
        // not seekable (pipe)
        ssize_t nBytes = ::read(m_fd, pBuffer, nBytesRequested);
        if (nBytes == -1)
            return oslTranslateFileError(errno);

        *pBytesRead = nBytes;

        return osl_File_E_None;
    }

    if (m_kind == KIND_MEM || !m_buffer)
    {
        // not buffered
        return readAt(nOffset, pBuffer, nBytesRequested, pBytesRead);
    }

    sal_uInt8 *buffer = static_cast<sal_uInt8*>(pBuffer);
    for (*pBytesRead = 0; nBytesRequested > 0; )
    {
        off_t  const bufptr = (nOffset / m_bufsiz) * m_bufsiz;
        size_t const bufpos = nOffset % m_bufsiz;

        if (bufptr != m_bufptr)
        {
            // flush current buffer
            oslFileError result = syncFile();
            if (result != osl_File_E_None)
                return result;

            m_bufptr = -1;
            m_buflen = 0;

            if (nBytesRequested >= m_bufsiz)
            {
                // buffer too small, read through from file
                sal_uInt64 uDone = 0;
                result = readAt(nOffset, &(buffer[*pBytesRead]), nBytesRequested, &uDone);
                if (result != osl_File_E_None)
                    return result;

                *pBytesRead += uDone;

                return osl_File_E_None;
            }

            // update buffer (pointer)
            sal_uInt64 uDone = 0;
            result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
            if (result != osl_File_E_None)
                return result;

            m_bufptr = bufptr;
            m_buflen = uDone;
        }

        if (bufpos >= m_buflen)
        {
            // end of file
            return osl_File_E_None;
        }

        size_t const bytes = std::min(m_buflen - bufpos, nBytesRequested);
        SAL_INFO("sal.fileio", "FileHandle_Impl::readFileAt(" << m_fd << ", " << nOffset << ", " << bytes << ")");

        memcpy(&(buffer[*pBytesRead]), &(m_buffer[bufpos]), bytes);
        nBytesRequested -= bytes;
        *pBytesRead += bytes;
        nOffset += bytes;
    }

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::writeFileAt(
    off_t        nOffset,
    void const * pBuffer,
    size_t       nBytesToWrite,
    sal_uInt64 * pBytesWritten)
{
    if (!(m_state & State::Seekable))
    {
        // not seekable (pipe)
        ssize_t nBytes = ::write(m_fd, pBuffer, nBytesToWrite);
        if (nBytes == -1)
            return oslTranslateFileError(errno);

        *pBytesWritten = nBytes;

        return osl_File_E_None;
    }
    if (!m_buffer)
    {
        // not buffered
        return writeAt(nOffset, pBuffer, nBytesToWrite, pBytesWritten);
    }

    sal_uInt8 const * buffer = static_cast<sal_uInt8 const *>(pBuffer);
    for (*pBytesWritten = 0; nBytesToWrite > 0;)
    {
        off_t const bufptr = (nOffset / m_bufsiz) * m_bufsiz;
        size_t const bufpos = nOffset % m_bufsiz;
        if (bufptr != m_bufptr)
        {
            // flush current buffer
            oslFileError result = syncFile();
            if (result != osl_File_E_None)
                return result;
            m_bufptr = -1;
            m_buflen = 0;

            if (nBytesToWrite >= m_bufsiz)
            {
                // buffer too small, write through to file
                sal_uInt64 uDone = 0;
                result = writeAt(nOffset, &(buffer[*pBytesWritten]), nBytesToWrite, &uDone);
                if (result != osl_File_E_None)
                    return result;

                if (uDone != nBytesToWrite)
                    return osl_File_E_IO;

                *pBytesWritten += uDone;

                return osl_File_E_None;
            }

            // update buffer (pointer)
            sal_uInt64 uDone = 0;
            result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
            if (result != osl_File_E_None)
                return result;

            m_bufptr = bufptr;
            m_buflen = uDone;
        }

        size_t const bytes = std::min(m_bufsiz - bufpos, nBytesToWrite);
        SAL_INFO("sal.fileio", "FileHandle_Impl::writeFileAt(" << m_fd << ", " << nOffset << ", " << bytes << ")");

        memcpy(&(m_buffer[bufpos]), &(buffer[*pBytesWritten]), bytes);
        nBytesToWrite -= bytes;
        *pBytesWritten += bytes;
        nOffset += bytes;

        m_buflen = std::max(m_buflen, bufpos + bytes);
        m_state |= State::Modified;
    }

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::readLineAt(
    off_t           nOffset,
    sal_Sequence ** ppSequence,
    sal_uInt64 *    pBytesRead)
{
    oslFileError result = osl_File_E_None;

    off_t bufptr = nOffset / m_bufsiz * m_bufsiz;
    if (bufptr != m_bufptr)
    {
        /* flush current buffer */
        result = syncFile();
        if (result != osl_File_E_None)
            return result;

        /* update buffer (pointer) */
        sal_uInt64 uDone = 0;
        result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
        if (result != osl_File_E_None)
            return result;

        m_bufptr = bufptr;
        m_buflen = uDone;
    }

    static int const LINE_STATE_BEGIN = 0;
    static int const LINE_STATE_CR    = 1;
    static int const LINE_STATE_LF    = 2;

    size_t bufpos = nOffset - m_bufptr, curpos = bufpos, dstpos = 0;
    int state = (bufpos >= m_buflen) ? LINE_STATE_LF : LINE_STATE_BEGIN;

    while (state != LINE_STATE_LF)
    {
        if (curpos >= m_buflen)
        {
            /* buffer examined */
            if (curpos > bufpos)
            {
                /* flush buffer to sequence */
                result = writeSequence_Impl(
                    ppSequence, &dstpos, &(m_buffer[bufpos]), curpos - bufpos);
                if (result != osl_File_E_None)
                    return result;

                *pBytesRead += curpos - bufpos;
                nOffset += curpos - bufpos;
            }

            bufptr = nOffset / m_bufsiz * m_bufsiz;
            if (bufptr != m_bufptr)
            {
                /* update buffer (pointer) */
                sal_uInt64 uDone = 0;
                result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
                if (result != osl_File_E_None)
                    return result;

                m_bufptr = bufptr;
                m_buflen = uDone;
            }

            bufpos = nOffset - m_bufptr;
            curpos = bufpos;
            if (bufpos >= m_buflen)
                break;
        }

        switch (state)
        {
        case LINE_STATE_CR:
            state = LINE_STATE_LF;
            switch (m_buffer[curpos])
            {
            case 0x0A: /* CRLF */
                /* eat current char */
                curpos++;
                break;
            default: /* single CR */
                /* keep current char */
                break;
            }
            break;
        default:
            /* determine next state */
            switch (m_buffer[curpos])
            {
            case 0x0A: /* single LF */
                state = LINE_STATE_LF;
                break;
            case 0x0D: /* CR */
                state = LINE_STATE_CR;
                break;
            default: /* advance to next char */
                curpos++;
                break;
            }
            if (state != LINE_STATE_BEGIN)
            {
                /* skip the newline char */
                curpos++;

                /* flush buffer to sequence */
                result = writeSequence_Impl(
                    ppSequence, &dstpos, &(m_buffer[bufpos]), curpos - bufpos - 1);
                if (result != osl_File_E_None)
                    return result;

                *pBytesRead += curpos - bufpos;
                nOffset += curpos - bufpos;
            }
            break;
        }
    }

    result = writeSequence_Impl(ppSequence, &dstpos, nullptr, 0);
    if (result != osl_File_E_None)
        return result;

    if (dstpos > 0)
        return osl_File_E_None;

    if (bufpos >= m_buflen)
        return osl_File_E_AGAIN;

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::writeSequence_Impl(
    sal_Sequence ** ppSequence,
    size_t *        pnOffset,
    const void *    pBuffer,
    size_t          nBytes)
{
    sal_Int32 nElements = *pnOffset + nBytes;
    if (!*ppSequence)
    {
        /* construct sequence */
        rtl_byte_sequence_constructNoDefault(ppSequence, nElements);
    }
    else if (nElements != (*ppSequence)->nElements)
    {
        /* resize sequence */
        rtl_byte_sequence_realloc(ppSequence, nElements);
    }

    if (*ppSequence && nBytes != 0)
    {
        /* fill sequence */
        memcpy(&((*ppSequence)->elements[*pnOffset]), pBuffer, nBytes);
        *pnOffset += nBytes;
    }

    return (*ppSequence) ? osl_File_E_None : osl_File_E_NOMEM;
}

oslFileError FileHandle_Impl::syncFile()
{
    oslFileError result = osl_File_E_None;
    if (m_state & State::Modified)
    {
        sal_uInt64 uDone = 0;
        result = writeAt(m_bufptr, m_buffer, m_buflen, &uDone);
        if (result != osl_File_E_None)
            return result;

        if (uDone != m_buflen)
            return osl_File_E_IO;

        m_state &= ~State::Modified;
    }

    return result;
}

oslFileHandle osl::detail::createFileHandleFromFD(int fd)
{
    if (fd == -1)
        return nullptr; // EINVAL

    struct stat aFileStat;
    if (fstat(fd, &aFileStat) == -1)
        return nullptr; // EBADF

    FileHandle_Impl *pImpl = new FileHandle_Impl(fd);

    // assume writeable
    pImpl->m_state |= State::Writeable;
    if (!S_ISREG(aFileStat.st_mode))
    {
        /* not a regular file, mark not seekable */
        pImpl->m_state &= ~State::Seekable;
    }
    else
    {
        /* regular file, init current size */
        pImpl->m_size = sal::static_int_cast< sal_uInt64 >(aFileStat.st_size);
    }

    SAL_INFO("sal.file", "osl::detail::createFileHandleFromFD(" << pImpl->m_fd << ", writeable) => " << pImpl->m_strFilePath);

    return static_cast<oslFileHandle>(pImpl);
}

static void osl_file_adjustLockFlags(const OString& path, int *flags, sal_uInt32 *uFlags)
{
#ifdef MACOSX
    (void) uFlags;

    /*
     * The AFP implementation of MacOS X 10.4 treats O_EXLOCK in a way
     * that makes it impossible for OOo to create a backup copy of the
     * file it keeps opened. OTOH O_SHLOCK for AFP behaves as desired by
     * the OOo file handling, so we need to check the path of the file
     * for the filesystem name.
     */
    struct statfs s;
    if(statfs(path.getStr(), &s) >= 0)
    {
        if(strncmp("afpfs", s.f_fstypename, 5) == 0)
        {
            *flags &= ~O_EXLOCK;
            *flags |=  O_SHLOCK;
        }
        else
        {
            /* Needed flags to allow opening a webdav file */
            *flags &= ~(O_EXLOCK | O_SHLOCK | O_NONBLOCK);
        }
    }
#elif defined(LINUX)
    (void) flags;

    /* get filesystem info */
    struct statfs aFileStatFs;
    if (statfs(path.getStr(), &aFileStatFs) < 0)
    {
        int e = errno;
        SAL_INFO("sal.file", "statfs(" << path << "): " << UnixErrnoString(e));
    }
    else
    {
        SAL_INFO("sal.file", "statfs(" << path << "): OK");

        // We avoid locking if on a Linux CIFS mount otherwise this
        // fill fail later on when opening the file for reading
        // during backup creation at save time (even though this is a
        // write lock and not a read lock).
        // Fixes the following bug:
        // https://bugs.documentfoundation.org/show_bug.cgi?id=55004
        switch (aFileStatFs.f_type) {
        case SMB_SUPER_MAGIC:
        case CIFS_SUPER_MAGIC:
        case SMB2_SUPER_MAGIC:
            *uFlags |= osl_File_OpenFlag_NoLock;
            break;
        default:
            break;
        }
    }
#else
    (void) path;
    (void) flags;
    (void) uFlags;
#endif
}

static bool osl_file_queryLocking(sal_uInt32 uFlags)
{
#if !defined HAVE_O_EXLOCK
    if (!(uFlags & osl_File_OpenFlag_NoLock)
        && ((uFlags & osl_File_OpenFlag_Write)
            || (uFlags & osl_File_OpenFlag_Create)))
    {
        static bool enabled = getenv("SAL_ENABLE_FILE_LOCKING") != nullptr;
            // getenv is not thread safe, so minimize use of result
        return enabled;
    }
#else
    (void) uFlags;
#endif
    return false;
}

static bool abortOnForbidden = false;
static std::vector<OString> allowedPathsRead;
static std::vector<OString> allowedPathsReadWrite;
static std::vector<OString> allowedPathsExecute;

static OString getParentFolder(std::string_view rFilePath)
{
    sal_Int32 n = rFilePath.find_last_of('/');
    OString folderPath;
    if (n < 1)
        folderPath = "."_ostr;
    else
        folderPath = OString(rFilePath.substr(0, n));

    return folderPath;
}

void setAllowedPaths(
        std::u16string_view aPaths
    )
{
    allowedPathsRead.clear();
    allowedPathsReadWrite.clear();
    allowedPathsExecute.clear();

    char eType = 'r';
    sal_Int32 nIndex = 0;
    do
    {
        OString aPath = rtl::OUStringToOString(
            o3tl::getToken(aPaths, 0, ':', nIndex),
            RTL_TEXTENCODING_UTF8);

        if (aPath.getLength() == 0)
            continue;

        if (aPath.getLength() == 1)
        {
            eType = aPath[0];
            continue;
        }

        char resolvedPath[PATH_MAX];
        bool isResolved = !!realpath(aPath.getStr(), resolvedPath);
        bool notExists = !isResolved && errno == ENOENT;

        if (notExists)
        {
            sal_Int32 n = aPath.lastIndexOf('/');
            OString folderPath = getParentFolder(aPath);
            isResolved = !!realpath(folderPath.getStr(), resolvedPath);
            notExists = !isResolved && errno == ENOENT;

            if (notExists || !isResolved || strlen(resolvedPath) + aPath.getLength() - n + 1 >= PATH_MAX)
                return; // too bad
            else
            {
                strcat(resolvedPath, aPath.getStr() + n);
            }
        }

        if (isResolved)
        {
            OString aPushPath(resolvedPath, strlen(resolvedPath));
            if (eType == 'r')
                allowedPathsRead.push_back(aPushPath);
            else if (eType == 'w')
            {
                allowedPathsRead.push_back(aPushPath);
                allowedPathsReadWrite.push_back(aPushPath);
            }
            else if (eType == 'x')
                allowedPathsExecute.push_back(aPushPath);
        }
    }
    while (nIndex != -1);

    abortOnForbidden = !!getenv("SAL_ABORT_ON_FORBIDDEN");
}

bool isForbidden(const OString &filePath, sal_uInt32 nFlags)
{
    // avoid realpath cost unless configured
    if (allowedPathsRead.size() == 0)
        return false;

    char resolvedPath[PATH_MAX];
    if (!realpath(filePath.getStr(), resolvedPath))
    {
        // write calls path a non-existent path that realpath will
        // fail to resolve. Thankfully our I/O APIs don't allow
        // symlink creation to race here.
        sal_Int32 n = filePath.lastIndexOf('/');
        OString folderPath = getParentFolder(filePath);

        bool isResolved = !!realpath(folderPath.getStr(), resolvedPath);
        bool notExists = !isResolved && errno == ENOENT;
        if (notExists) // folder doesn't exist, check parent, in the end of chain checks "."
            return isForbidden(folderPath, nFlags);
        else if (!isResolved || strlen(resolvedPath) + filePath.getLength() - n + 1 >= PATH_MAX)
            return true; // too bad
        else
        {
            strcat(resolvedPath, filePath.getStr() + n);
        }
    }

    const std::vector<OString> *pCheckPaths = &allowedPathsRead;
    if (nFlags & osl_File_OpenFlag_Write ||
        nFlags & osl_File_OpenFlag_Create)
        pCheckPaths = &allowedPathsReadWrite;
    else if (nFlags & 0x80)
        pCheckPaths = &allowedPathsExecute;

    bool allowed = false;
    for (const auto &it : *pCheckPaths) {
        if (!strncmp(resolvedPath, it.getStr(), it.getLength()))
        {
            allowed = true;
            break;
        }
    }

    if (!allowed)
        SAL_WARN("sal.osl", "access outside sandbox to " <<
                 ((nFlags & osl_File_OpenFlag_Write ||
                   nFlags & osl_File_OpenFlag_Create) ? "w" :
                  (nFlags & 0x80) ? "x" : "r") << ":" <<
                 filePath << " which is really " << resolvedPath <<
                 (allowed ? " allowed " : " forbidden") <<
                 " check list: " << pCheckPaths->size());

    if (abortOnForbidden && !allowed)
        abort(); // a bit abrupt - but don't try to escape.

    return !allowed;
}

#ifdef HAVE_O_EXLOCK
#define OPEN_WRITE_FLAGS ( O_RDWR | O_EXLOCK | O_NONBLOCK )
#define OPEN_CREATE_FLAGS ( O_CREAT | O_RDWR | O_EXLOCK | O_NONBLOCK )
#else
#define OPEN_WRITE_FLAGS ( O_RDWR )
#define OPEN_CREATE_FLAGS ( O_CREAT | O_RDWR )
#endif

#if defined ANDROID

namespace {

static oslFileError openMemoryAsFile(const OString &rData,
                                     oslFileHandle *pHandle,
                                     const OString& path)
{
    const char *address = rData.getStr();
    size_t size = rData.getLength();

    FileHandle_Impl *pImpl = new FileHandle_Impl(-1, FileHandle_Impl::KIND_MEM, path);
    pImpl->m_size = sal::static_int_cast< sal_uInt64 >(size);

    *pHandle = (oslFileHandle)(pImpl);

    pImpl->m_bufptr = 0;
    pImpl->m_buflen = size;
    pImpl->m_memstreambuf = rData.pData;
    rtl_string_acquire(pImpl->m_memstreambuf);

    pImpl->m_bufsiz = size;
    pImpl->m_buffer = reinterpret_cast<sal_uInt8*>(const_cast<char *>(address));

    return osl_File_E_None;
}

/*
 * Reading files from /assets/ on Android via a transition into the VM
 * shows on profiles and is rather slow; so we cache small files as
 * used by UNO, UI-builder etc.
 */
class AndroidFileCache {
public:
    struct Entry {
        OString maFilePath;
        OString maData;
    };
    AndroidFileCache(size_t nElements)
        : mnCur(0)
    {
        maEntries.resize(nElements);
        assert (maEntries.size() == nElements);
    }
    Entry *find(const char *cpFilePath)
    {
        for (auto &it : maEntries)
        {
            if (!strcmp(it.maFilePath.getStr(), cpFilePath))
                return &it;
        }
        return nullptr;
    }
    // no clever LRU - but - good enough for now.
    void insert(const char *cpFilePath, OString &rData)
    {
        assert (maEntries.size() > 0);
        if (++mnCur >= maEntries.size())
            mnCur = 0;
        maEntries[mnCur].maFilePath = OString(cpFilePath, strlen(cpFilePath));
        maEntries[mnCur].maData = rData;
    }
    static AndroidFileCache &getHitCache()
    {
        static AndroidFileCache *pCache = new AndroidFileCache(16);
        return *pCache;
    }
    static AndroidFileCache &getMissCache()
    {
        static AndroidFileCache *pCache = new AndroidFileCache(32);
        return *pCache;
    }
private:
    size_t             mnCur;
    std::vector<Entry> maEntries;
};

} // namespace

#endif

oslFileError openFilePath(const OString& filePath, oslFileHandle* pHandle,
                          sal_uInt32 uFlags, mode_t mode)
{
    oslFileError eRet;

#ifdef ANDROID
    /* Opening a file from /assets read-only means
     * we should mmap it from the .apk file
     */
    if (o3tl::starts_with(filePath, "/assets/"))
    {
        OString aData;
        bool bCache = true;

        const char *cpAssetsPath = filePath.getStr() + sizeof("/assets/") - 1;
        // some requests are /assets//foo...
        if (cpAssetsPath[0] == '/')
        {
            __android_log_print(ANDROID_LOG_DEBUG,"libo:sal/osl/unx/file", "double-slash in path: %s", filePath.getStr());
            cpAssetsPath++;
        }

        AndroidFileCache::Entry *pHit = AndroidFileCache::getHitCache().find(cpAssetsPath);
        if (pHit)
            aData = pHit->maData;

        else
        {
            bCache = false;
            AndroidFileCache::Entry *pMiss = AndroidFileCache::getMissCache().find(cpAssetsPath);
            if (pMiss)
            {
                errno = ENOENT;
                __android_log_print(ANDROID_LOG_ERROR,"libo:sal/osl/unx/file", "miss cache: failed to open %s", filePath.getStr());
                return osl_File_E_NOENT;
            }
            AAssetManager* mgr = lo_get_native_assetmgr();
            AAsset* asset = AAssetManager_open(mgr, cpAssetsPath, AASSET_MODE_BUFFER);
            if (!asset)
            {
                AndroidFileCache::getMissCache().insert(cpAssetsPath, aData);
                errno = ENOENT;
                __android_log_print(ANDROID_LOG_ERROR,"libo:sal/osl/unx/file", "failed to open %s", filePath.getStr());
                return osl_File_E_NOENT;
            }
            else
            {
                rtl_String *pData = nullptr;
                size_t size = AAsset_getLength(asset);
                rtl_string_new_WithLength(&pData, size);
                pData->length = size;
                AAsset_read(asset, pData->buffer, size);
                AAsset_close(asset);

                aData = OString(pData, SAL_NO_ACQUIRE);

                if (pData->length < 50 * 1024)
                    AndroidFileCache::getHitCache().insert(cpAssetsPath, aData);
            }
        }

        if (uFlags & osl_File_OpenFlag_Write)
        {
            // It seems to work better to silently "open" it read-only
            // and let write attempts, if any, fail later. Otherwise
            // loading a document from /assets fails with that idiotic
            // "General Error" dialog...
        }
        SAL_INFO("sal.file", "osl_openFile(" << filePath << ") => '" << cpAssetsPath << "'"
                 << aData.getLength() << " bytes from file " << (bCache ? "cache" : "system"));
        return openMemoryAsFile(aData, pHandle, filePath);
    }
#endif

    /* set mode and flags */
    int defmode = (uFlags & osl_File_OpenFlag_Private) ? S_IRUSR : S_IRUSR | S_IRGRP | S_IROTH;
    int flags = O_RDONLY;

    if (uFlags & osl_File_OpenFlag_Write)
    {
        defmode |= (uFlags & osl_File_OpenFlag_Private) ? S_IWUSR : S_IWUSR | S_IWGRP | S_IWOTH;
        flags = OPEN_WRITE_FLAGS;
    }

    if (uFlags & osl_File_OpenFlag_Create)
    {
        defmode |= (uFlags & osl_File_OpenFlag_Private) ? S_IWUSR : S_IWUSR | S_IWGRP | S_IWOTH;
        flags = OPEN_CREATE_FLAGS;
    }

    if (mode == mode_t(-1))
        mode = defmode;

    /* Check for flags passed in from SvFileStream::Open() */
    if (uFlags & osl_File_OpenFlag_Trunc)
        flags |= O_TRUNC;

    if (!(uFlags & osl_File_OpenFlag_NoExcl))
        flags |= O_EXCL;

    if (uFlags & osl_File_OpenFlag_NoLock)
    {
#ifdef HAVE_O_EXLOCK
        flags &= ~(O_EXLOCK | O_SHLOCK | O_NONBLOCK);
#endif /* HAVE_O_EXLOCK */
    }
    else
    {
        osl_file_adjustLockFlags (filePath, &flags, &uFlags);
    }

    // O_EXCL can be set only when O_CREAT is set
    if (flags & O_EXCL && !(flags & O_CREAT))
        flags &= ~O_EXCL;

    // set close-on-exec by default
    flags |= O_CLOEXEC;

    // Sandboxing hook
    if (isForbidden( filePath, uFlags ))
        return osl_File_E_ACCES;

    /* open the file */
    int fd = open_c( filePath, flags, mode );
    if (fd == -1)
    {
        return oslTranslateFileError(errno);
    }

#if !HAVE_FEATURE_MACOSX_SANDBOX
    /* reset O_NONBLOCK flag */
    if (flags & O_NONBLOCK)
    {
        int f = fcntl(fd, F_GETFL, 0);
        if (f == -1)
        {
            int e = errno;
            SAL_INFO("sal.file", "fcntl(" << fd << ",F_GETFL,0): " << UnixErrnoString(e));
            eRet = oslTranslateFileError(e);
            (void) close(fd);
            SAL_INFO("sal.file", "close(" << fd << ")");
            return eRet;
        }
        else
            SAL_INFO("sal.file", "fcntl(" << fd << ",F_GETFL,0): OK");

        if (fcntl(fd, F_SETFL, (f & ~O_NONBLOCK)) == -1)
        {
            int e = errno;
            SAL_INFO("sal.file", "fcntl(" << fd << ",F_SETFL,(f & ~O_NONBLOCK)): " << UnixErrnoString(e));
            eRet = oslTranslateFileError(e);
            (void) close(fd);
            SAL_INFO("sal.file", "close(" << fd << ")");
            return eRet;
        }
        else
            SAL_INFO("sal.file", "fcntl(" << fd << ",F_SETFL,(f & ~O_NONBLOCK)): OK");
    }
#endif

    /* get file status (mode, size) */
    struct stat aFileStat;
    if (fstat(fd, &aFileStat) == -1)
    {
        int e = errno;
        SAL_INFO("sal.file", "fstat(" << fd << "): " << UnixErrnoString(e));
        eRet = oslTranslateFileError(e);
        (void) close(fd);
        SAL_INFO("sal.file", "close(" << fd << ")");
        return eRet;
    }
    else
        SAL_INFO("sal.file", "fstat(" << fd << "): OK");

    if (!S_ISREG(aFileStat.st_mode))
    {
        /* we only open regular files here */
        SAL_INFO("sal.file", "osl_openFile(" << filePath << "): not a regular file");
        (void) close(fd);
        SAL_INFO("sal.file", "close(" << fd << ")");
        return osl_File_E_INVAL;
    }

    if (osl_file_queryLocking(uFlags))
    {
#ifdef MACOSX
        if (flock(fd, LOCK_EX | LOCK_NB) == -1)
        {
            int e = errno;
            SAL_INFO("sal.file", "flock(" << fd << ",LOCK_EX|LOCK_NB): " << UnixErrnoString(e));
            /* Mac OSX returns ENOTSUP for webdav drives. We should try read lock */

            // Restore errno after possibly having been overwritten by the SAL_INFO above...
            errno = e;
            if ((errno != ENOTSUP) || ((flock(fd, LOCK_SH | LOCK_NB) == 1) && (errno != ENOTSUP)))
            {
                eRet = oslTranslateFileError(errno);
                (void) close(fd);
                SAL_INFO("sal.file", "close(" << fd << ")");
                return eRet;
            }
        }
        else
            SAL_INFO("sal.file", "flock(" << fd << ",LOCK_EX|LOCK_NB): OK");
#else   /* F_SETLK */
        struct flock aflock;

        aflock.l_type = F_WRLCK;
        aflock.l_whence = SEEK_SET;
        aflock.l_start = 0;
        aflock.l_len = 0;

        if (fcntl(fd, F_SETLK, &aflock) == -1)
        {
            int e = errno;
            SAL_INFO("sal.file", "fcntl(" << fd << ",F_SETLK): " << UnixErrnoString(e));
            eRet = oslTranslateFileError(e);
            (void) close(fd);
            SAL_INFO("sal.file", "close(" << fd << ")");
            return eRet;
        }
#endif  /* F_SETLK */
    }

    /* allocate memory for impl structure */
    FileHandle_Impl *pImpl = new FileHandle_Impl(fd, FileHandle_Impl::KIND_FD, filePath);
    if (flags & O_RDWR)
        pImpl->m_state |= State::Writeable;

    pImpl->m_size = sal::static_int_cast< sal_uInt64 >(aFileStat.st_size);

    *pHandle = static_cast<oslFileHandle>(pImpl);

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_openFile(rtl_uString* ustrFileURL, oslFileHandle* pHandle, sal_uInt32 uFlags)
{
    return openFile(ustrFileURL, pHandle, uFlags, mode_t(-1));
}

oslFileError openFile(rtl_uString* ustrFileURL, oslFileHandle* pHandle, sal_uInt32 uFlags, mode_t mode)
{
    oslFileError eRet;

    if ((!ustrFileURL) || (ustrFileURL->length == 0) || (!pHandle))
        return osl_File_E_INVAL;

    /* convert file URL to system path */
    char buffer[PATH_MAX];
    eRet = FileURLToPath(buffer, sizeof(buffer), ustrFileURL);
    if (eRet != osl_File_E_None)
        return eRet;

#ifdef MACOSX
    if (macxp_resolveAlias(buffer, sizeof(buffer)) != 0)
        return oslTranslateFileError(errno);
#endif /* MACOSX */

    return openFilePath(buffer, pHandle, uFlags, mode);
}

oslFileError SAL_CALL osl_closeFile(oslFileHandle Handle)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if (!pImpl)
        return osl_File_E_INVAL;

    if (pImpl->m_kind == FileHandle_Impl::KIND_MEM)
    {
#ifdef ANDROID
        rtl_string_release(pImpl->m_memstreambuf);
        pImpl->m_memstreambuf = nullptr;

        pImpl->m_buffer = NULL;
#endif
        delete pImpl;
        return osl_File_E_None;
    }

    if (pImpl->m_fd < 0)
        return osl_File_E_INVAL;

    (void) pthread_mutex_lock(&(pImpl->m_mutex));

    /* close(2) implicitly (and unconditionally) unlocks */
    oslFileError result = pImpl->syncFile();
    if (result != osl_File_E_None)
    {
        /* close, ignoring double failure */
        (void) close(pImpl->m_fd);
        SAL_INFO("sal.file", "close(" << pImpl->m_fd << ")");
    }
    else if (close(pImpl->m_fd) == -1)
    {
        int e = errno;
        SAL_INFO("sal.file", "close(" << pImpl->m_fd << "): " << UnixErrnoString(e));
        /* translate error code */
        result = oslTranslateFileError(e);
    }
    else
        SAL_INFO("sal.file", "close(" << pImpl->m_fd << "): OK");

    (void) pthread_mutex_unlock(&(pImpl->m_mutex));
    delete pImpl;
    return result;
}

oslFileError SAL_CALL osl_syncFile(oslFileHandle Handle)
{
    FileHandle_Impl* pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)))
        return osl_File_E_INVAL;

    if (pImpl->m_kind == FileHandle_Impl::KIND_MEM)
        return osl_File_E_None;

    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));

    oslFileError result = pImpl->syncFile();

    if (result != osl_File_E_None)
        return result;

    static bool disabled = getenv("SAL_DISABLE_FSYNC") != nullptr;

    if (disabled)
        SAL_INFO("sal.file", "fsync(" << pImpl->m_fd << "): Disabled");
    else if (fsync(pImpl->m_fd) == -1)
    {
        int e = errno;
        SAL_INFO("sal.file", "fsync(" << pImpl->m_fd << "): " << UnixErrnoString(e));
        return oslTranslateFileError(e);
    }
    else
        SAL_INFO("sal.file", "fsync(" << pImpl->m_fd << "): OK");

    return osl_File_E_None;
}

const off_t MAX_OFF_T = std::numeric_limits< off_t >::max();

namespace {

template<typename T> bool exceedsMaxOffT(T n) { return n > MAX_OFF_T; }

template<typename T> bool exceedsMinOffT(T n)
{ return n < std::numeric_limits<off_t>::min(); }

}

oslFileError SAL_CALL osl_mapFile(
    oslFileHandle Handle,
    void**        ppAddr,
    sal_uInt64    uLength,
    sal_uInt64    uOffset,
    sal_uInt32    uFlags
)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!ppAddr))
        return osl_File_E_INVAL;

    *ppAddr = nullptr;

    if (uLength > SAL_MAX_SIZE)
    {
        // coverity[dead_error_line] 2024.6.1 - crossplatform requirement
        return osl_File_E_OVERFLOW;
    }

    size_t const nLength = sal::static_int_cast< size_t >(uLength);

    if (exceedsMaxOffT(uOffset))
        return osl_File_E_OVERFLOW;

    if (pImpl->m_kind == FileHandle_Impl::KIND_MEM)
    {
        *ppAddr = pImpl->m_buffer + uOffset;
        return osl_File_E_None;
    }

    off_t const nOffset = sal::static_int_cast< off_t >(uOffset);

    void* p = mmap(nullptr, nLength, PROT_READ, MAP_SHARED, pImpl->m_fd, nOffset);

    if (p == MAP_FAILED)
        return oslTranslateFileError(errno);

    *ppAddr = p;

    if (uFlags & osl_File_MapFlag_RandomAccess)
    {
        // Determine memory pagesize.
        size_t const nPageSize = FileHandle_Impl::getpagesize();
        if (nPageSize != size_t(-1))
        {
            /*
             * Pagein, touching first byte of every memory page.
             * Note: volatile disables optimizing the loop away.
             */
            sal_uInt8 volatile *pData(static_cast<sal_uInt8*>(*ppAddr));
            size_t nSize(nLength);

            while (nSize > nPageSize)
            {
                pData[0];
                pData += nPageSize;
                nSize -= nPageSize;
            }

            if (nSize > 0)
                pData[0];
        }
    }

    if (uFlags & osl_File_MapFlag_WillNeed)
    {
        // On Linux, madvise(..., MADV_WILLNEED) appears to have the undesirable
        // effect of not returning until the data has actually been paged in, so
        // that its net effect would typically be to slow down the process
        // (which could start processing at the beginning of the data while the
        // OS simultaneously pages in the rest); on other platforms, it remains
        // to be evaluated whether madvise or equivalent is available and
        // actually useful:
#if defined MACOSX || (defined(__sun) && (!defined(__XOPEN_OR_POSIX) || defined(_XPG6) || defined(__EXTENSIONS__)))
        int e = posix_madvise(p, nLength, POSIX_MADV_WILLNEED);
        if (e != 0)
            SAL_INFO("sal.file", "posix_madvise(..., POSIX_MADV_WILLNEED) failed with " << e);

#elif defined __sun
        if (madvise(static_cast< caddr_t >(p), nLength, MADV_WILLNEED) != 0)
            SAL_INFO("sal.file", "madvise(..., MADV_WILLNEED) failed with " << UnixErrnoString(errno));
#endif
    }

    return osl_File_E_None;
}

static oslFileError unmapFile(void* pAddr, sal_uInt64 uLength)
{
    if (!pAddr)
        return osl_File_E_INVAL;

    if (uLength > SAL_MAX_SIZE)
    {
        // coverity[dead_error_line] 2024.6.1 - crossplatform requirement
        return osl_File_E_OVERFLOW;
    }

    size_t const nLength = sal::static_int_cast< size_t >(uLength);

    if (munmap(pAddr, nLength) == -1)
        return oslTranslateFileError(errno);

    return osl_File_E_None;
}

#ifndef ANDROID

// Note that osl_unmapFile() just won't work on Android in general
// where for (uncompressed) files inside the .apk, in the /assets
// folder osl_mapFile just returns a pointer to the file inside the
// already mmapped .apk archive.

oslFileError SAL_CALL osl_unmapFile(void* pAddr, sal_uInt64 uLength)
{
    return unmapFile(pAddr, uLength);
}

#endif

oslFileError SAL_CALL osl_unmapMappedFile(oslFileHandle Handle, void* pAddr, sal_uInt64 uLength)
{
    FileHandle_Impl *pImpl = static_cast<FileHandle_Impl*>(Handle);

    if (!pImpl)
        return osl_File_E_INVAL;

    if (pImpl->m_kind == FileHandle_Impl::KIND_FD)
        return unmapFile(pAddr, uLength);

    // For parts of already mmapped "parent" files, whose mapping we
    // can't change, not much we can or should do...
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_readLine(
    oslFileHandle   Handle,
    sal_Sequence ** ppSequence)
{
    FileHandle_Impl *pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!ppSequence))
        return osl_File_E_INVAL;

    sal_uInt64 uBytesRead = 0;

    // read at current fileptr; fileptr += uBytesRead;
    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    oslFileError result = pImpl->readLineAt(pImpl->m_fileptr, ppSequence, &uBytesRead);

    if (result == osl_File_E_None)
        pImpl->m_fileptr += uBytesRead;

    return result;
}

oslFileError SAL_CALL osl_readFile(
    oslFileHandle Handle,
    void *        pBuffer,
    sal_uInt64    uBytesRequested,
    sal_uInt64 *  pBytesRead)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!pBuffer) || (!pBytesRead))
        return osl_File_E_INVAL;

    static sal_uInt64 const g_limit_ssize_t = std::numeric_limits< ssize_t >::max();
    if (g_limit_ssize_t < uBytesRequested)
        return osl_File_E_OVERFLOW;

    size_t const nBytesRequested = sal::static_int_cast< size_t >(uBytesRequested);

    // read at current fileptr; fileptr += *pBytesRead;
    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    oslFileError result = pImpl->readFileAt(pImpl->m_fileptr, pBuffer, nBytesRequested, pBytesRead);

    if (result == osl_File_E_None)
        pImpl->m_fileptr += *pBytesRead;

    return result;
}

oslFileError SAL_CALL osl_writeFile(
    oslFileHandle Handle,
    const void *  pBuffer,
    sal_uInt64    uBytesToWrite,
    sal_uInt64 *  pBytesWritten)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || (pImpl->m_fd == -1) || (!pBuffer) || (!pBytesWritten))
        return osl_File_E_INVAL;

    if (!(pImpl->m_state & State::Writeable))
        return osl_File_E_BADF;

    static sal_uInt64 const g_limit_ssize_t = std::numeric_limits< ssize_t >::max();
    if (g_limit_ssize_t < uBytesToWrite)
        return osl_File_E_OVERFLOW;

    size_t const nBytesToWrite = sal::static_int_cast< size_t >(uBytesToWrite);

    // write at current fileptr; fileptr += *pBytesWritten;
    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    oslFileError result = pImpl->writeFileAt(pImpl->m_fileptr, pBuffer, nBytesToWrite, pBytesWritten);
    if (result == osl_File_E_None)
        pImpl->m_fileptr += *pBytesWritten;

    return result;
}

oslFileError SAL_CALL osl_readFileAt(
    oslFileHandle Handle,
    sal_uInt64    uOffset,
    void*         pBuffer,
    sal_uInt64    uBytesRequested,
    sal_uInt64*   pBytesRead)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!pBuffer) || (!pBytesRead))
        return osl_File_E_INVAL;

    if (!(pImpl->m_state & State::Seekable))
        return osl_File_E_SPIPE;

    if (exceedsMaxOffT(uOffset))
        return osl_File_E_OVERFLOW;

    off_t const nOffset = sal::static_int_cast< off_t >(uOffset);

    static sal_uInt64 const g_limit_ssize_t = std::numeric_limits< ssize_t >::max();
    if (g_limit_ssize_t < uBytesRequested)
        return osl_File_E_OVERFLOW;

    size_t const nBytesRequested = sal::static_int_cast< size_t >(uBytesRequested);

    // read at specified fileptr
    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));

    return pImpl->readFileAt(nOffset, pBuffer, nBytesRequested, pBytesRead);
}

oslFileError SAL_CALL osl_writeFileAt(
    oslFileHandle Handle,
    sal_uInt64    uOffset,
    const void*   pBuffer,
    sal_uInt64    uBytesToWrite,
    sal_uInt64*   pBytesWritten)
{
    FileHandle_Impl* pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || (pImpl->m_fd == -1) || (!pBuffer) || (!pBytesWritten))
        return osl_File_E_INVAL;

    if (!(pImpl->m_state & State::Seekable))
        return osl_File_E_SPIPE;

    if (!(pImpl->m_state & State::Writeable))
        return osl_File_E_BADF;

    if (exceedsMaxOffT(uOffset))
        return osl_File_E_OVERFLOW;

    off_t const nOffset = sal::static_int_cast< off_t >(uOffset);

    static sal_uInt64 const g_limit_ssize_t = std::numeric_limits< ssize_t >::max();
    if (g_limit_ssize_t < uBytesToWrite)
        return osl_File_E_OVERFLOW;

    size_t const nBytesToWrite = sal::static_int_cast< size_t >(uBytesToWrite);

    // write at specified fileptr
    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));

    return pImpl->writeFileAt(nOffset, pBuffer, nBytesToWrite, pBytesWritten);
}

oslFileError SAL_CALL osl_isEndOfFile(oslFileHandle Handle, sal_Bool *pIsEOF)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!pIsEOF))
        return osl_File_E_INVAL;

    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    *pIsEOF = (pImpl->getPos() == pImpl->getSize());

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_getFilePos(oslFileHandle Handle, sal_uInt64* pPos)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!pPos))
        return osl_File_E_INVAL;

    // no need to lock because pos is atomic
    *pPos = pImpl->getPos();

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFilePos(oslFileHandle Handle, sal_uInt32 uHow, sal_Int64 uOffset)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)))
        return osl_File_E_INVAL;

    if (exceedsMaxOffT(uOffset) || exceedsMinOffT(uOffset))
        return osl_File_E_OVERFLOW;

    off_t nPos = 0, nOffset = sal::static_int_cast< off_t >(uOffset);

    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    switch (uHow)
    {
        case osl_Pos_Absolut:
            if (nOffset < 0)
                return osl_File_E_INVAL;
            break;

        case osl_Pos_Current:
            nPos = sal::static_int_cast< off_t >(pImpl->getPos());
            if ((nOffset < 0) && (nPos < -1*nOffset))
                return osl_File_E_INVAL;

            assert(nPos >= 0);
            if (nOffset > MAX_OFF_T - nPos)
                return osl_File_E_OVERFLOW;
            break;

        case osl_Pos_End:
            nPos = sal::static_int_cast< off_t >(pImpl->getSize());
            if ((nOffset < 0) && (nPos < -1*nOffset))
                return osl_File_E_INVAL;

            assert(nPos >= 0);
            if (nOffset > MAX_OFF_T - nPos)
                return osl_File_E_OVERFLOW;
            break;

        default:
            return osl_File_E_INVAL;
    }

    pImpl->setPos(nPos + nOffset);
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_getFileSize(oslFileHandle Handle, sal_uInt64* pSize)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || ((pImpl->m_kind == FileHandle_Impl::KIND_FD) && (pImpl->m_fd == -1)) || (!pSize))
        return osl_File_E_INVAL;

    FileHandle_Impl::Guard lock(&(pImpl->m_mutex));
    *pSize = pImpl->getSize();

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFileSize(oslFileHandle Handle, sal_uInt64 uSize)
{
    FileHandle_Impl* pImpl = static_cast< FileHandle_Impl* >(Handle);

    if ((!pImpl) || (pImpl->m_fd == -1))
        return osl_File_E_INVAL;

    if (!(pImpl->m_state & State::Writeable))
        return osl_File_E_BADF;

    if (exceedsMaxOffT(uSize))
        return osl_File_E_OVERFLOW;

    oslFileError result = pImpl->syncFile();
    if (result != osl_File_E_None)
        return result;

    pImpl->m_bufptr = -1;
    pImpl->m_buflen = 0;

    return pImpl->setSize(uSize);
}


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
