/* -*- 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 <osl/file.h>

#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <utime.h>

#include <osl/diagnose.h>
#include <osl/thread.h>

#include "system.hxx"
#include "file_impl.hxx"
#include "file_error_transl.hxx"
#include "file_path_helper.hxx"
#include "file_url.hxx"
#include "uunxapi.hxx"

namespace
{
    void set_file_type(const struct stat& file_stat, oslFileStatus* pStat)
    {
        /* links to directories state also to be a directory */
       if (S_ISLNK(file_stat.st_mode))
           pStat->eType = osl_File_Type_Link;
       else if (S_ISDIR(file_stat.st_mode))
           pStat->eType = osl_File_Type_Directory;
       else if (S_ISREG(file_stat.st_mode))
           pStat->eType = osl_File_Type_Regular;
       else if (S_ISFIFO(file_stat.st_mode))
           pStat->eType = osl_File_Type_Fifo;
       else if (S_ISSOCK(file_stat.st_mode))
           pStat->eType = osl_File_Type_Socket;
       else if (S_ISCHR(file_stat.st_mode) || S_ISBLK(file_stat.st_mode))
           pStat->eType = osl_File_Type_Special;
       else
           pStat->eType = osl_File_Type_Unknown;

       pStat->uValidFields |= osl_FileStatus_Mask_Type;
    }

    void set_file_access_mask(const struct stat& file_stat, oslFileStatus* pStat)
    {
        // user permissions
        if (S_IRUSR & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OwnRead;

        if (S_IWUSR & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OwnWrite;

        if (S_IXUSR & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OwnExe;

        // group permissions
        if (S_IRGRP & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_GrpRead;

        if (S_IWGRP & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_GrpWrite;

        if (S_IXGRP & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_GrpExe;

        // others permissions
        if (S_IROTH & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OthRead;

        if (S_IWOTH & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OthWrite;

        if (S_IXOTH & file_stat.st_mode)
            pStat->uAttributes |= osl_File_Attribute_OthExe;

        pStat->uValidFields |= osl_FileStatus_Mask_Attributes;
    }

    /* This code used not to use access(...) because access follows links which
       may cause performance problems see #97133.  (That apparently references a
       no-longer accessible Hamburg-internal bug-tracking system.)
       However, contrary to what is stated above the use of access calls is
       required on network file systems not using unix semantics (AFS, see
       fdo#43095).
    */
    void set_file_access_rights(const OString& file_path, oslFileStatus* pStat)
    {
        pStat->uValidFields |= osl_FileStatus_Mask_Attributes;

        if (osl::access(file_path, W_OK) < 0)
            pStat->uAttributes |= osl_File_Attribute_ReadOnly;

        if (osl::access(file_path, X_OK) == 0)
            pStat->uAttributes |= osl_File_Attribute_Executable;
    }

    void set_file_hidden_status(const OString& file_path, oslFileStatus* pStat)
    {
        pStat->uAttributes   = osl::systemPathIsHiddenFileOrDirectoryEntry(file_path) ? osl_File_Attribute_Hidden : 0;
        pStat->uValidFields |= osl_FileStatus_Mask_Attributes;
    }

    /* the set_file_access_rights must be called after set_file_hidden_status(...) and
       set_file_access_mask(...) because of the hack in set_file_access_rights(...) */
    void set_file_attributes(
        const OString& file_path, const struct stat& file_stat, const sal_uInt32 uFieldMask, oslFileStatus* pStat)
    {
        set_file_hidden_status(file_path, pStat);
        set_file_access_mask(file_stat, pStat);

        // we set the file access rights only on demand
        // because it's potentially expensive
        if (uFieldMask & osl_FileStatus_Mask_Attributes)
            set_file_access_rights(file_path, pStat);
    }

    void set_file_access_time(const struct stat& file_stat, oslFileStatus* pStat)
    {
        pStat->aAccessTime.Seconds  = file_stat.st_atime;
        pStat->aAccessTime.Nanosec  = 0;
        pStat->uValidFields        |= osl_FileStatus_Mask_AccessTime;
    }

    void set_file_modify_time(const struct stat& file_stat, oslFileStatus* pStat)
    {
        pStat->aModifyTime.Seconds  = file_stat.st_mtime;
        pStat->aModifyTime.Nanosec  = 0;
        pStat->uValidFields        |= osl_FileStatus_Mask_ModifyTime;
    }

    void set_file_size(const struct stat& file_stat, oslFileStatus* pStat)
    {
        if (S_ISREG(file_stat.st_mode))
        {
            pStat->uFileSize     = file_stat.st_size;
            pStat->uValidFields |= osl_FileStatus_Mask_FileSize;
        }
    }

    /* we only need to call stat or lstat if one of the
       following flags is set */
    bool is_stat_call_necessary(sal_uInt32 field_mask, oslFileType file_type)
    {
        return (
                ((field_mask & osl_FileStatus_Mask_Type) && (file_type == osl_File_Type_Unknown)) ||
                (field_mask & osl_FileStatus_Mask_Attributes) ||
                (field_mask & osl_FileStatus_Mask_CreationTime) ||
                (field_mask & osl_FileStatus_Mask_AccessTime) ||
                (field_mask & osl_FileStatus_Mask_ModifyTime) ||
                (field_mask & osl_FileStatus_Mask_FileSize) ||
                (field_mask & osl_FileStatus_Mask_LinkTargetURL) ||
                (field_mask & osl_FileStatus_Mask_Validate));
    }

    oslFileError set_link_target_url(const OString& file_path, oslFileStatus* pStat)
    {
        OString link_target;
        if (!osl::realpath(file_path, link_target))
            return oslTranslateFileError(errno);

        OUString url;
        oslFileError osl_error = osl::detail::convertPathnameToUrl(link_target, &url);
        if (osl_error != osl_File_E_None)
            return osl_error;
        rtl_uString_assign(&pStat->ustrLinkTargetURL, url.pData);

        pStat->uValidFields |= osl_FileStatus_Mask_LinkTargetURL;
        return osl_File_E_None;
    }

    oslFileError setup_osl_getFileStatus(
        DirectoryItem_Impl * pImpl, oslFileStatus* pStat, OString& file_path)
    {
        if ((pImpl == nullptr) || (pStat == nullptr))
            return osl_File_E_INVAL;

        file_path = pImpl->m_strFilePath;
        OSL_ASSERT(!file_path.isEmpty());
        if (file_path.isEmpty())
            return osl_File_E_INVAL;

        pStat->uValidFields = 0;
        return osl_File_E_None;
    }

}

oslFileError SAL_CALL osl_getFileStatus(oslDirectoryItem Item, oslFileStatus* pStat, sal_uInt32 uFieldMask)
{
    DirectoryItem_Impl * pImpl = static_cast< DirectoryItem_Impl* >(Item);

    OString file_path;
    oslFileError  osl_error = setup_osl_getFileStatus(pImpl, pStat, file_path);
    if (osl_error != osl_File_E_None)
        return osl_error;

    struct stat file_stat;

    bool bStatNeeded = is_stat_call_necessary(uFieldMask, pImpl->getFileType());
    if (bStatNeeded && (osl::lstat(file_path, file_stat) != 0))
        return oslTranslateFileError(errno);

    if (bStatNeeded)
    {
        // we set all these attributes because it's cheap
        set_file_type(file_stat, pStat);
        set_file_access_time(file_stat, pStat);
        set_file_modify_time(file_stat, pStat);
        set_file_size(file_stat, pStat);
        set_file_attributes(file_path, file_stat, uFieldMask, pStat);

        // file exists semantic of osl_FileStatus_Mask_Validate
        if ((uFieldMask & osl_FileStatus_Mask_LinkTargetURL) && S_ISLNK(file_stat.st_mode))
        {
            osl_error = set_link_target_url(file_path, pStat);
            if (osl_error != osl_File_E_None)
                return osl_error;
        }
    }
#ifdef _DIRENT_HAVE_D_TYPE
    else if (uFieldMask & osl_FileStatus_Mask_Type)
    {
        pStat->eType = pImpl->getFileType();
        pStat->uValidFields |= osl_FileStatus_Mask_Type;
    }
#endif /* _DIRENT_HAVE_D_TYPE */

    if (uFieldMask & osl_FileStatus_Mask_FileURL)
    {
        OUString url;
        if ((osl_error = osl::detail::convertPathnameToUrl(file_path, &url)) != osl_File_E_None)
            return osl_error;
        rtl_uString_assign(&pStat->ustrFileURL, url.pData);

        pStat->uValidFields |= osl_FileStatus_Mask_FileURL;
    }

    if (uFieldMask & osl_FileStatus_Mask_FileName)
    {
        OString name;
        osl_systemPathGetFileNameOrLastDirectoryPart(file_path.pData, &name.pData);
        bool ok = rtl_convertStringToUString(
            &pStat->ustrFileName, name.getStr(), name.getLength(), osl_getThreadTextEncoding(),
            (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT
             | RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT));
        assert(ok); (void)ok;
        pStat->uValidFields |= osl_FileStatus_Mask_FileName;
    }
    return osl_File_E_None;
}

static oslFileError osl_psz_setFileAttributes( const char* pszFilePath, sal_uInt64 uAttributes )
{
    oslFileError osl_error = osl_File_E_None;
    mode_t       nNewMode  = 0;

    OSL_ENSURE(!(osl_File_Attribute_Hidden & uAttributes), "osl_File_Attribute_Hidden doesn't work under Unix");

    if (isForbidden(pszFilePath, osl_File_OpenFlag_Write))
        return osl_File_E_ACCES;

    if (uAttributes & osl_File_Attribute_OwnRead)
        nNewMode |= S_IRUSR;

    if (uAttributes & osl_File_Attribute_OwnWrite)
        nNewMode|=S_IWUSR;

    if  (uAttributes & osl_File_Attribute_OwnExe)
        nNewMode|=S_IXUSR;

    if (uAttributes & osl_File_Attribute_GrpRead)
        nNewMode|=S_IRGRP;

    if (uAttributes & osl_File_Attribute_GrpWrite)
        nNewMode|=S_IWGRP;

    if (uAttributes & osl_File_Attribute_GrpExe)
        nNewMode|=S_IXGRP;

    if (uAttributes & osl_File_Attribute_OthRead)
        nNewMode|=S_IROTH;

    if (uAttributes & osl_File_Attribute_OthWrite)
        nNewMode|=S_IWOTH;

    if (uAttributes & osl_File_Attribute_OthExe)
        nNewMode|=S_IXOTH;

    if (chmod(pszFilePath, nNewMode) < 0)
        osl_error = oslTranslateFileError(errno);

    return osl_error;
}

oslFileError SAL_CALL osl_setFileAttributes( rtl_uString* ustrFileURL, sal_uInt64 uAttributes )
{
    char path[PATH_MAX];
    oslFileError eRet;

    OSL_ASSERT( ustrFileURL );

    /* convert file url to system path */
    eRet = FileURLToPath( path, PATH_MAX, ustrFileURL );
    if( eRet != osl_File_E_None )
        return eRet;

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

    return osl_psz_setFileAttributes( path, uAttributes );
}

static oslFileError osl_psz_setFileTime (
    const char* pszFilePath,
    const TimeValue* pLastAccessTime,
    const TimeValue* pLastWriteTime )
{
    int nRet=0;
    struct utimbuf aTimeBuffer;
    struct stat aFileStat;
#ifdef DEBUG_OSL_FILE
    struct tm* pTM=0;
#endif

    if (isForbidden(pszFilePath, osl_File_OpenFlag_Write))
        return osl_File_E_ACCES;

    nRet = lstat_c(pszFilePath,&aFileStat);

    if ( nRet < 0 )
    {
        nRet=errno;
        return oslTranslateFileError(nRet);
    }

#ifdef DEBUG_OSL_FILE
    fprintf(stderr,"File Times are (in localtime):\n");
    pTM=localtime(&aFileStat.st_ctime);
    fprintf(stderr,"CreationTime is '%s'\n",asctime(pTM));
    pTM=localtime(&aFileStat.st_atime);
    fprintf(stderr,"AccessTime   is '%s'\n",asctime(pTM));
    pTM=localtime(&aFileStat.st_mtime);
    fprintf(stderr,"Modification is '%s'\n",asctime(pTM));

    fprintf(stderr,"File Times are (in UTC):\n");
    fprintf(stderr,"CreationTime is '%s'\n",ctime(&aFileStat.st_ctime));
    fprintf(stderr,"AccessTime   is '%s'\n",ctime(&aTimeBuffer.actime));
    fprintf(stderr,"Modification is '%s'\n",ctime(&aTimeBuffer.modtime));
#endif

    if ( pLastAccessTime != nullptr )
    {
        aTimeBuffer.actime=pLastAccessTime->Seconds;
    }
    else
    {
        aTimeBuffer.actime=aFileStat.st_atime;
    }

    if ( pLastWriteTime != nullptr )
    {
        aTimeBuffer.modtime=pLastWriteTime->Seconds;
    }
    else
    {
        aTimeBuffer.modtime=aFileStat.st_mtime;
    }

    /* mfe: Creation time not used here! */

#ifdef DEBUG_OSL_FILE
    fprintf(stderr,"File Times are (in localtime):\n");
    pTM=localtime(&aFileStat.st_ctime);
    fprintf(stderr,"CreationTime now '%s'\n",asctime(pTM));
    pTM=localtime(&aTimeBuffer.actime);
    fprintf(stderr,"AccessTime   now '%s'\n",asctime(pTM));
    pTM=localtime(&aTimeBuffer.modtime);
    fprintf(stderr,"Modification now '%s'\n",asctime(pTM));

    fprintf(stderr,"File Times are (in UTC):\n");
    fprintf(stderr,"CreationTime now '%s'\n",ctime(&aFileStat.st_ctime));
    fprintf(stderr,"AccessTime   now '%s'\n",ctime(&aTimeBuffer.actime));
    fprintf(stderr,"Modification now '%s'\n",ctime(&aTimeBuffer.modtime));
#endif

    nRet = utime_c(pszFilePath,&aTimeBuffer);
    if ( nRet < 0 )
    {
        nRet=errno;
        return oslTranslateFileError(nRet);
    }

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFileTime (
    rtl_uString* ustrFileURL,
    SAL_UNUSED_PARAMETER const TimeValue* /* pCreationTime */,
    const TimeValue* pLastAccessTime,
    const TimeValue* pLastWriteTime )
{
    char path[PATH_MAX];
    oslFileError eRet;

    OSL_ASSERT( ustrFileURL );

    /* convert file url to system path */
    eRet = FileURLToPath( path, PATH_MAX, ustrFileURL );
    if( eRet != osl_File_E_None )
        return eRet;

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

    return osl_psz_setFileTime( path, pLastAccessTime, pLastWriteTime );
}

sal_Bool
SAL_CALL osl_identicalDirectoryItem( oslDirectoryItem a, oslDirectoryItem b)
{
    DirectoryItem_Impl *pA = static_cast<DirectoryItem_Impl *>(a);
    DirectoryItem_Impl *pB = static_cast<DirectoryItem_Impl *>(b);
    if (a == b)
        return true;
    /* same name => same item, unless renaming / moving madness has occurred */
    if (pA->m_strFilePath == pB->m_strFilePath)
        return true;

    struct stat a_stat, b_stat;

    if (osl::lstat(pA->m_strFilePath, a_stat) != 0 ||
        osl::lstat(pB->m_strFilePath, b_stat) != 0)
        return false;

    return (a_stat.st_ino == b_stat.st_ino);
}

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