/* -*- 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 <sal/config.h>

#include <osl/file.h>

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

#include "file_error_transl.hxx"
#include "file_url.hxx"
#include "system.hxx"

#include <errno.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_STATFS_H
#undef HAVE_STATFS_H
#endif

#if defined(LINUX) && defined(__FreeBSD_kernel__)
#undef LINUX
#define FREEBSD 1
#endif

#if defined(__sun)

#include <sys/mnttab.h>
#include <sys/statvfs.h>
#define  HAVE_STATFS_H

#elif defined(LINUX)
#include <sys/statfs.h>
#define  HAVE_STATFS_H

#elif defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD) || defined(DRAGONFLY)

#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#define  HAVE_STATFS_H

#elif defined(MACOSX)

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

#endif /* HAVE_STATFS_H */

/************************************************************************
 *   ToDo
 *
 *   - Fix: check for corresponding struct sizes in exported functions
 *   - check size/use of oslVolumeInfo
 ***********************************************************************/

/******************************************************************************
 *
 *                  C-String Function Declarations
 *
 *****************************************************************************/

static oslFileError osl_psz_getVolumeInformation(const char* , oslVolumeInfo* pInfo, sal_uInt32 uFieldMask);

/****************************************************************************/
/*  osl_getVolumeInformation */
/****************************************************************************/

oslFileError osl_getVolumeInformation( rtl_uString* ustrDirectoryURL, oslVolumeInfo* pInfo, sal_uInt32 uFieldMask )
{
    char path[PATH_MAX];
    oslFileError eRet;

    OSL_ASSERT( ustrDirectoryURL );
    OSL_ASSERT( pInfo );

    /* convert directory url to system path */
    eRet = FileURLToPath( path, PATH_MAX, ustrDirectoryURL );
    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_getVolumeInformation( path, pInfo, uFieldMask);
}

/******************************************************************************
 *
 *                  C-String Versions of Exported Module Functions
 *
 *****************************************************************************/

#ifdef HAVE_STATFS_H

#if defined(FREEBSD) || defined(MACOSX) || defined(OPENBSD) || defined(DRAGONFLY)
#   define OSL_detail_STATFS_STRUCT                  struct statfs
#   define OSL_detail_STATFS(dir, sfs)               statfs((dir), (sfs))
#   define OSL_detail_STATFS_BLKSIZ(a)               (static_cast<sal_uInt64>((a).f_bsize))
#   define OSL_detail_STATFS_TYPENAME(a)             ((a).f_fstypename)
#if defined(OPENBSD)
#   define OSL_detail_STATFS_ISREMOTE(a)             (rtl_str_compare((a).f_fstypename, "nfs") == 0)
#else
#   define OSL_detail_STATFS_ISREMOTE(a)             (((a).f_type & MNT_LOCAL) == 0)
#endif

/* always return true if queried for the properties of
   the file system. If you think this is wrong under any
   of the target platforms fix it!!!! */
#   define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a)  (true)
#   define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true)
#endif /* FREEBSD || MACOSX || OPENBSD */

#if defined(NETBSD)

#   define OSL_detail_STATFS_STRUCT              struct statvfs
#   define OSL_detail_STATFS(dir, sfs)           statvfs((dir), (sfs))
#   define OSL_detail_STATFS_ISREMOTE(a)         (((a).f_flag & ST_LOCAL) == 0)

#   define OSL_detail_STATFS_BLKSIZ(a)           ((sal_uInt64)((a).f_bsize))
#   define OSL_detail_STATFS_TYPENAME(a)         ((a).f_fstypename)

#   define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) (strcmp((a).f_fstypename, "msdos") != 0 && strcmp((a).f_fstypename, "ntfs") != 0 && strcmp((a).f_fstypename, "smbfs") != 0)
#   define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a)    (strcmp((a).f_fstypename, "msdos") != 0)
#endif /* NETBSD */

#if defined(LINUX)
#   define OSL_detail_NFS_SUPER_MAGIC                 0x6969
#   define OSL_detail_SMB_SUPER_MAGIC                 0x517B
#   define OSL_detail_MSDOS_SUPER_MAGIC               0x4d44
#   define OSL_detail_NTFS_SUPER_MAGIC                0x5346544e
#   define OSL_detail_STATFS_STRUCT                   struct statfs
#   define OSL_detail_STATFS(dir, sfs)                statfs((dir), (sfs))
#   define OSL_detail_STATFS_BLKSIZ(a)                (static_cast<sal_uInt64>((a).f_bsize))
#   define OSL_detail_STATFS_IS_NFS(a)                (OSL_detail_NFS_SUPER_MAGIC == (a).f_type)
#   define OSL_detail_STATFS_IS_SMB(a)                (OSL_detail_SMB_SUPER_MAGIC == (a).f_type)
#   define OSL_detail_STATFS_ISREMOTE(a)              (OSL_detail_STATFS_IS_NFS((a)) || OSL_detail_STATFS_IS_SMB((a)))
#   define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a)  ((OSL_detail_MSDOS_SUPER_MAGIC != (a).f_type) && (OSL_detail_NTFS_SUPER_MAGIC != (a).f_type))
#   define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) ((OSL_detail_MSDOS_SUPER_MAGIC != (a).f_type))
#endif /* LINUX */

#if defined(__sun)
#   define OSL_detail_STATFS_STRUCT                   struct statvfs
#   define OSL_detail_STATFS(dir, sfs)                statvfs((dir), (sfs))
#   define OSL_detail_STATFS_BLKSIZ(a)                ((sal_uInt64)((a).f_frsize))
#   define OSL_detail_STATFS_TYPENAME(a)              ((a).f_basetype)
#   define OSL_detail_STATFS_ISREMOTE(a)              (rtl_str_compare((a).f_basetype, "nfs") == 0)

/* always return true if queried for the properties of
   the file system. If you think this is wrong under any
   of the target platforms fix it!!!! */
#   define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a)  (true)
#   define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true)
#endif /* __sun */

#   define OSL_detail_STATFS_INIT(a)         (memset(&(a), 0, sizeof(OSL_detail_STATFS_STRUCT)))

#else /* no statfs available */

#   define OSL_detail_STATFS_STRUCT                   struct dummy {int i;}
#   define OSL_detail_STATFS_INIT(a)                  ((void)a)
#   define OSL_detail_STATFS(dir, sfs)                (1)
#   define OSL_detail_STATFS_ISREMOTE(sfs)            (false)
#   define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a)  (true)
#   define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true)
#endif /* HAVE_STATFS_H */

static oslFileError osl_psz_getVolumeInformation (
    const char* pszDirectory, oslVolumeInfo* pInfo, sal_uInt32 uFieldMask)
{
    if (!pInfo)
        return osl_File_E_INVAL;

    pInfo->uValidFields = 0;
    pInfo->uAttributes  = 0;
    pInfo->uTotalSpace = 0;
    pInfo->uFreeSpace  = 0;
    pInfo->uUsedSpace  = 0;

    if ((uFieldMask
         & (osl_VolumeInfo_Mask_Attributes | osl_VolumeInfo_Mask_TotalSpace
            | osl_VolumeInfo_Mask_UsedSpace | osl_VolumeInfo_Mask_FreeSpace
            | osl_VolumeInfo_Mask_FileSystemName
            | osl_VolumeInfo_Mask_FileSystemCaseHandling))
        != 0)
    {
        OSL_detail_STATFS_STRUCT sfs;
        OSL_detail_STATFS_INIT(sfs);
        // coverity[fs_check_call : FALSE]
        if ((OSL_detail_STATFS(pszDirectory, &sfs)) < (0))
        {
            oslFileError result = oslTranslateFileError(errno);
            return result;
        }

        /* FIXME: how to detect the kind of storage (fixed, cdrom, ...) */
        if (uFieldMask & osl_VolumeInfo_Mask_Attributes)
        {
            bool const remote = OSL_detail_STATFS_ISREMOTE(sfs);
                // extracted from the 'if' to avoid Clang -Wunreachable-code
            if (remote)
                pInfo->uAttributes  |= osl_Volume_Attribute_Remote;

            pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes;
        }

        if (uFieldMask & osl_VolumeInfo_Mask_FileSystemCaseHandling)
        {
            if (OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(sfs))
                pInfo->uAttributes |= osl_Volume_Attribute_Case_Sensitive;

            if (OSL_detail_STATFS_IS_CASE_PRESERVING_FS(sfs))
                pInfo->uAttributes |= osl_Volume_Attribute_Case_Is_Preserved;

            pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes;
        }

#if defined(OSL_detail_STATFS_BLKSIZ)

        if ((uFieldMask & osl_VolumeInfo_Mask_TotalSpace) ||
            (uFieldMask & osl_VolumeInfo_Mask_UsedSpace))
        {
            pInfo->uTotalSpace   = OSL_detail_STATFS_BLKSIZ(sfs);
            pInfo->uTotalSpace  *= static_cast<sal_uInt64>(sfs.f_blocks);
            pInfo->uValidFields |= osl_VolumeInfo_Mask_TotalSpace;
        }

        if ((uFieldMask & osl_VolumeInfo_Mask_FreeSpace) ||
            (uFieldMask & osl_VolumeInfo_Mask_UsedSpace))
        {
            pInfo->uFreeSpace = OSL_detail_STATFS_BLKSIZ(sfs);

            if (getuid() == 0)
                pInfo->uFreeSpace *= static_cast<sal_uInt64>(sfs.f_bfree);
            else
                pInfo->uFreeSpace *= static_cast<sal_uInt64>(sfs.f_bavail);

            pInfo->uValidFields |= osl_VolumeInfo_Mask_FreeSpace;
        }

        if ((pInfo->uValidFields & osl_VolumeInfo_Mask_TotalSpace) &&
            (pInfo->uValidFields & osl_VolumeInfo_Mask_FreeSpace ))
        {
            pInfo->uUsedSpace    = pInfo->uTotalSpace - pInfo->uFreeSpace;
            pInfo->uValidFields |= osl_VolumeInfo_Mask_UsedSpace;
        }

#endif  /* OSL_detail_STATFS_BLKSIZ */

#if defined(OSL_detail_STATFS_TYPENAME)

        if (uFieldMask & osl_VolumeInfo_Mask_FileSystemName)
        {
            rtl_string2UString(
                &(pInfo->ustrFileSystemName),
                OSL_detail_STATFS_TYPENAME(sfs),
                rtl_str_getLength(OSL_detail_STATFS_TYPENAME(sfs)),
                osl_getThreadTextEncoding(),
                OUSTRING_TO_OSTRING_CVTFLAGS);
            OSL_ASSERT(pInfo->ustrFileSystemName != nullptr);

            pInfo->uValidFields |= osl_VolumeInfo_Mask_FileSystemName;
        }

#endif /* OSL_detail_STATFS_TYPENAME */
    }

    pInfo->uMaxNameLength = 0;
    if (uFieldMask & osl_VolumeInfo_Mask_MaxNameLength)
    {
        long nLen = pathconf(pszDirectory, _PC_NAME_MAX);
        if (nLen > 0)
        {
            pInfo->uMaxNameLength = static_cast<sal_uInt32>(nLen);
            pInfo->uValidFields |= osl_VolumeInfo_Mask_MaxNameLength;
        }
    }

    pInfo->uMaxPathLength = 0;
    if (uFieldMask & osl_VolumeInfo_Mask_MaxPathLength)
    {
        long nLen = pathconf (pszDirectory, _PC_PATH_MAX);
        if (nLen > 0)
        {
            pInfo->uMaxPathLength  = static_cast<sal_uInt32>(nLen);
            pInfo->uValidFields   |= osl_VolumeInfo_Mask_MaxPathLength;
        }
    }

    return osl_File_E_None;
}

oslFileError osl_getVolumeDeviceMountPath( oslVolumeDeviceHandle, rtl_uString ** )
{
    return osl_File_E_INVAL;
}

oslFileError osl_acquireVolumeDeviceHandle( oslVolumeDeviceHandle )
{
    return osl_File_E_INVAL;
}

oslFileError osl_releaseVolumeDeviceHandle( oslVolumeDeviceHandle )
{
    return osl_File_E_INVAL;
}

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