diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sal/osl | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
95 files changed, 32911 insertions, 0 deletions
diff --git a/sal/osl/all/compat.cxx b/sal/osl/all/compat.cxx new file mode 100644 index 000000000..8fafc628b --- /dev/null +++ b/sal/osl/all/compat.cxx @@ -0,0 +1,197 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cstdlib> + +#include <osl/module.h> +#include <osl/pipe.h> +#include <osl/socket.h> +#include <osl/time.h> +#include <sal/types.h> + +// Stubs for removed functionality, to be killed when we bump sal SONAME + +extern "C" { + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_acquireSemaphore(void *) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC void SAL_CALL osl_addToSocketSet(void *, oslSocket) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT int SAL_CALL osl_areCommandArgsSet() { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_assertFailedLine( + char const *, sal_Int32, char const *) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL osl_breakDebug() { + std::abort(); +} + +SAL_DLLPUBLIC void SAL_CALL osl_clearSocketSet(void *) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void * SAL_CALL osl_createSemaphore(sal_uInt32) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC void * SAL_CALL osl_createSocketSet() { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC sal_Int32 SAL_CALL osl_demultiplexSocketEvents( + void *, void *, void *, TimeValue const *) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL osl_destroySemaphore(void *) { + std::abort(); +} + +SAL_DLLPUBLIC void SAL_CALL osl_destroySocketSet(void *) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_getEthernetAddress(sal_uInt8 *) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC sal_Bool SAL_CALL osl_isInSocketSet(void *, oslSocket) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT oslSocket SAL_CALL osl_receiveResourcePipe(oslPipe) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_releaseSemaphore(void *) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT sal_Int32 SAL_CALL osl_reportError( + sal_uInt32, char const *) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC void SAL_CALL osl_removeFromSocketSet(void *, oslSocket) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_sendResourcePipe(oslPipe, oslSocket) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +namespace { +typedef void (* pfunc_osl_printDebugMessage)(char const *); +} +SAL_DLLPUBLIC_EXPORT pfunc_osl_printDebugMessage SAL_CALL +osl_setDebugMessageFunc(pfunc_osl_printDebugMessage) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +namespace { +typedef void (* pfunc_osl_printDetailedDebugMessage)( + char const *, sal_Int32, char const *); +} +SAL_DLLPUBLIC_EXPORT pfunc_osl_printDetailedDebugMessage SAL_CALL +osl_setDetailedDebugMessageFunc(pfunc_osl_printDetailedDebugMessage) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL osl_trace(char const *, ...) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL osl_tryToAcquireSemaphore(void *) { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT sal_Int32 SAL_CALL rtl_addUnloadingListener( + void (SAL_CALL *)(void *), void *) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT sal_Int32 SAL_CALL rtl_compareMemory( + void const *, void const *, sal_Size) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_copyMemory( + void *, void const *, sal_Size) +{ + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_fillMemory(void *, sal_Size, sal_uInt8) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void * SAL_CALL rtl_findInMemory( + void const *, sal_uInt8, sal_Size) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_moveMemory( + void *, void const *, sal_Size) +{ + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL rtl_registerModuleForUnloading(oslModule) +{ + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_removeUnloadingListener(sal_Int32) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_unloadUnusedModules(TimeValue *) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_unregisterModuleForUnloading(oslModule) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_zeroMemory(void *, sal_Size) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_logfile_trace( const char*, ... ) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT void SAL_CALL rtl_logfile_longTrace(char const *, ...) { + std::abort(); +} + +SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL rtl_logfile_hasLogFile() { + for (;;) { std::abort(); } // avoid "must return a value" warnings +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/debugbase.cxx b/sal/osl/all/debugbase.cxx new file mode 100644 index 000000000..59848a883 --- /dev/null +++ b/sal/osl/all/debugbase.cxx @@ -0,0 +1,145 @@ +/* -*- 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 <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <osl/process.h> +#include <osl/diagnose.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <algorithm> +#include <vector> + +namespace { + +struct StaticDebugBaseAddressFilter + : rtl::StaticWithInit<std::vector<OString>, StaticDebugBaseAddressFilter> { + std::vector<OString> operator()() const { + std::vector<OString> vec; + rtl_uString * pStr = nullptr; + OUString const name( + "OSL_DEBUGBASE_STORE_ADDRESSES" ); + if (osl_getEnvironment( name.pData, &pStr ) == osl_Process_E_None) { + OUString const str(pStr); + rtl_uString_release(pStr); + sal_Int32 nIndex = 0; + do { + vec.push_back( OUStringToOString( + o3tl::getToken(str, 0, ';', nIndex ), + RTL_TEXTENCODING_ASCII_US ) ); + } + while (nIndex >= 0); + } + return vec; + } +}; + +bool isSubStr( char const* pStr, OString const& subStr ) +{ + return rtl_str_indexOfStr( pStr, subStr.getStr() ) >= 0; +} + +} // anon namespace + +extern "C" { + +// These functions presumably should not be extern "C", but changing +// that would break binary compatibility. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +osl::Mutex & SAL_CALL osl_detail_ObjectRegistry_getMutex() + SAL_THROW_EXTERN_C() +{ + static osl::Mutex aMutex; + return aMutex; +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +bool SAL_CALL osl_detail_ObjectRegistry_storeAddresses( char const* pName ) + SAL_THROW_EXTERN_C() +{ + std::vector<OString> const& rVec = StaticDebugBaseAddressFilter::get(); + if (rVec.empty()) + return false; + // check for "all": + OString const& rFirst = rVec[0]; + if ( rFirst == "all" ) + return true; + auto const iEnd( rVec.cend() ); + return std::any_of( rVec.begin(), iEnd, + [pName] (OString const& it) { return isSubStr(pName, it); }); +} + +bool SAL_CALL osl_detail_ObjectRegistry_checkObjectCount( + osl::detail::ObjectRegistryData const& rData, std::size_t nExpected ) + SAL_THROW_EXTERN_C() +{ + std::size_t nSize; + if (rData.m_bStoreAddresses) + nSize = rData.m_addresses.size(); + else + nSize = static_cast<std::size_t>(rData.m_nCount); + + bool const bRet = (nSize == nExpected); + SAL_WARN_IF( + !bRet, "sal.osl", + "unexpected number of " << rData.m_pName << ": " << nSize + << "; Expected: " << nExpected); + return bRet; +} + +void SAL_CALL osl_detail_ObjectRegistry_registerObject( + osl::detail::ObjectRegistryData & rData, void const* pObj ) + SAL_THROW_EXTERN_C() +{ + if (rData.m_bStoreAddresses) { + osl::MutexGuard const guard( osl_detail_ObjectRegistry_getMutex() ); + std::pair<osl::detail::VoidPointerSet::iterator, bool> const insertion( + rData.m_addresses.insert(pObj) ); + SAL_WARN_IF(!insertion.second, "sal.osl", "insertion failed!?"); + } + else { + osl_atomic_increment(&rData.m_nCount); + } +} + +void SAL_CALL osl_detail_ObjectRegistry_revokeObject( + osl::detail::ObjectRegistryData & rData, void const* pObj ) + SAL_THROW_EXTERN_C() +{ + if (rData.m_bStoreAddresses) { + osl::MutexGuard const guard( osl_detail_ObjectRegistry_getMutex() ); + std::size_t const n = rData.m_addresses.erase(pObj); + SAL_WARN_IF(n != 1, "sal.osl", "erased more than 1 entry!?"); + } + else { + osl_atomic_decrement(&rData.m_nCount); + } +} + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/filepath.cxx b/sal/osl/all/filepath.cxx new file mode 100644 index 000000000..afba01739 --- /dev/null +++ b/sal/osl/all/filepath.cxx @@ -0,0 +1,113 @@ +/* -*- 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 <rtl/ustring.h> +#include <cassert> + +static sal_uInt32 osl_defCalcTextWidth( rtl_uString *ustrText ) +{ + return ustrText ? ustrText->length : 0; +} + +oslFileError SAL_CALL osl_abbreviateSystemPath( rtl_uString *ustrSystemPath, rtl_uString **pustrCompacted, sal_uInt32 uMaxWidth, oslCalcTextWidthFunc pfnCalcWidth ) +{ + rtl_uString *ustrPath = nullptr; + rtl_uString *ustrFile = nullptr; + sal_uInt32 uPathWidth, uFileWidth; + + if ( !pfnCalcWidth ) + pfnCalcWidth = osl_defCalcTextWidth; + + { + sal_Int32 iLastSlash = rtl_ustr_lastIndexOfChar_WithLength( ustrSystemPath->buffer, ustrSystemPath->length, SAL_PATHDELIMITER ); + + if ( iLastSlash >= 0 ) + { + rtl_uString_newFromStr_WithLength( &ustrPath, ustrSystemPath->buffer, iLastSlash ); + rtl_uString_newFromStr_WithLength( &ustrFile, &ustrSystemPath->buffer[iLastSlash], ustrSystemPath->length - iLastSlash ); + } + else + { + rtl_uString_new( &ustrPath ); + rtl_uString_newFromString( &ustrFile, ustrSystemPath ); + } + } + + assert(ustrPath && ustrFile); + + uPathWidth = pfnCalcWidth( ustrPath ); + uFileWidth = pfnCalcWidth( ustrFile ); + + /* First abbreviate the directory component of the path */ + + while ( uPathWidth + uFileWidth > uMaxWidth ) + { + if ( ustrPath->length > 3 ) + { + ustrPath->length--; + ustrPath->buffer[ustrPath->length-3] = '.'; + ustrPath->buffer[ustrPath->length-2] = '.'; + ustrPath->buffer[ustrPath->length-1] = '.'; + ustrPath->buffer[ustrPath->length] = 0; + + uPathWidth = pfnCalcWidth( ustrPath ); + } + else + break; + } + + /* Now abbreviate file component */ + + while ( uPathWidth + uFileWidth > uMaxWidth ) + { + if ( ustrFile->length > 4 ) + { + ustrFile->length--; + ustrFile->buffer[ustrFile->length-3] = '.'; + ustrFile->buffer[ustrFile->length-2] = '.'; + ustrFile->buffer[ustrFile->length-1] = '.'; + ustrFile->buffer[ustrFile->length] = 0; + + uFileWidth = pfnCalcWidth( ustrFile ); + } + else + break; + } + + rtl_uString_newConcat( pustrCompacted, ustrPath, ustrFile ); + + /* Event now if path was compacted to ".../..." it can be too large */ + + uPathWidth += uFileWidth; + + while ( uPathWidth > uMaxWidth ) + { + (*pustrCompacted)->length--; + (*pustrCompacted)->buffer[(*pustrCompacted)->length] = 0; + uPathWidth = pfnCalcWidth( *pustrCompacted ); + } + + rtl_uString_release(ustrPath); + rtl_uString_release(ustrFile); + + return osl_File_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/loadmodulerelative.cxx b/sal/osl/all/loadmodulerelative.cxx new file mode 100644 index 000000000..242bb6210 --- /dev/null +++ b/sal/osl/all/loadmodulerelative.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <cstddef> + +#include <sal/log.hxx> +#include <osl/module.h> +#include <osl/module.hxx> +#include <rtl/malformeduriexception.hxx> +#include <rtl/uri.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +extern "C" { + +#ifndef DISABLE_DYNLOADING + +oslModule SAL_CALL osl_loadModuleRelative( + oslGenericFunction const baseModule, rtl_uString * const relativePath, + sal_Int32 const mode) +{ + OUString base; + if (!osl::Module::getUrlFromAddress(baseModule, base)) { + SAL_INFO("sal.osl","osl::Module::getUrlFromAddress failed"); + return nullptr; + } + OUString abs; + try { + abs = rtl::Uri::convertRelToAbs(base, relativePath); + } catch (const rtl::MalformedUriException & e) { + SAL_INFO("sal.osl", "rtl::MalformedUriException <" << e.getMessage() << ">"); + return nullptr; + } + return osl_loadModule(abs.pData, mode); +} + +#endif // !DISABLE_DYNLOADING + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/log.cxx b/sal/osl/all/log.cxx new file mode 100644 index 000000000..405d5599c --- /dev/null +++ b/sal/osl/all/log.cxx @@ -0,0 +1,477 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstdarg> +#include <cstddef> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <sstream> + +#include <stdio.h> +#include <string.h> +#include <fstream> + +#include <config_global.h> +#include <osl/thread.hxx> +#include <rtl/string.h> +#include <sal/detail/log.h> +#include <sal/log.hxx> +#include <sal/types.h> +#include <backtraceasstring.hxx> +#include <salusesyslog.hxx> + +#if defined ANDROID +#include <android/log.h> +#elif defined _WIN32 +#include <process.h> +#include <windows.h> +#define OSL_DETAIL_GETPID _getpid() +#else +#include <unistd.h> +#define OSL_DETAIL_GETPID getpid() +#endif + +#if HAVE_SYSLOG_H +#include <syslog.h> +// sal/osl/unx/salinit.cxx::sal_detail_initialize updates this: +bool sal_use_syslog; +#else +bool const sal_use_syslog = false; +#endif + +// Avoid the use of other sal code in this file as much as possible, so that +// this code can be called from other sal code without causing endless +// recursion. + +namespace { + +struct TimeContainer +{ + TimeValue aTime; + TimeContainer() + { + osl_getSystemTime(&aTime); + } +}; + +TimeContainer aStartTime; + +bool equalStrings( + char const * string1, std::size_t length1, char const * string2, + std::size_t length2) +{ + return length1 == length2 && std::memcmp(string1, string2, length1) == 0; +} + +#if !defined ANDROID +char const * toString(sal_detail_LogLevel level) { + switch (level) { + case SAL_DETAIL_LOG_LEVEL_INFO: + return "info"; + case SAL_DETAIL_LOG_LEVEL_WARN: + return "warn"; + case SAL_DETAIL_LOG_LEVEL_DEBUG: + return "debug"; + default: + assert(false); // this cannot happen + return "broken"; + } +} +#endif + +#ifdef _WIN32 + +char const* setEnvFromLoggingIniFile(const char* env, const char* key) +{ + char const* sResult = nullptr; + wchar_t buffer[MAX_PATH]; + GetModuleFileNameW(nullptr, buffer, MAX_PATH); + std::wstring sProgramDirectory(buffer); + std::wstring::size_type pos = sProgramDirectory.find_last_of(L"\\/"); + sProgramDirectory = sProgramDirectory.substr(0, pos+1); + sProgramDirectory += L"logging.ini"; + + std::ifstream logFileStream(sProgramDirectory); + if (!logFileStream.good()) + return sResult; + + std::size_t n; + std::string aKey; + std::string sWantedKey(key); + std::string sLine; + while (std::getline(logFileStream, sLine)) { + if (sLine.find('#') == 0) + continue; + if ( ( n = sLine.find('=') ) != std::string::npos) { + aKey = sLine.substr(0, n); + if (aKey != sWantedKey) + continue; + _putenv_s(env, sLine.substr(n+1, sLine.length()).c_str()); + sResult = std::getenv(env); + break; + } + } + return sResult; +} +#endif + +char const* pLogSelector = nullptr; + +char const* getLogLevelEnvVar() { + static char const* const pLevel = [] { + char const* pResult = nullptr; + + // First check the environment variable, then the setting in logging.ini + char const* env = std::getenv("SAL_LOG"); + +#ifdef _WIN32 + if (!env) + env = setEnvFromLoggingIniFile("SAL_LOG", "LogLevel"); +#endif + + if (env) + { + // Make a copy from the string in environment block + static std::string sLevel(env); + pResult = sLevel.c_str(); + } + return pResult; + }(); + + return pLevel; +} + +#if !defined ANDROID + +std::ofstream * getLogFile() { + static std::ofstream* const pFile = [] { + std::ofstream* pResult = nullptr; + + // First check the environment variable, then the setting in logging.ini + char const* logFile = std::getenv("SAL_LOG_FILE"); + +#ifdef _WIN32 + if (!logFile) + logFile = setEnvFromLoggingIniFile("SAL_LOG_FILE", "LogFilePath"); +#endif + + if (logFile) + { + // stays until process exits + static std::ofstream file(logFile, std::ios::app | std::ios::out); + pResult = &file; + } + + return pResult; + }(); + + return pFile; +} + + +std::pair<bool, bool> getTimestampFlags(char const *selector) +{ + bool outputTimestamp = false; + bool outputRelativeTimer = false; + for (char const* p = selector; p && *p;) + { + if (*p++ == '+') + { + char const * p1 = p; + while (*p1 != '.' && *p1 != '+' && *p1 != '-' && *p1 != '\0') { + ++p1; + } + if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP"))) + outputTimestamp = true; + else if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER"))) + outputRelativeTimer = true; + char const * p2 = p1; + while (*p2 != '+' && *p2 != '-' && *p2 != '\0') { + ++p2; + } + p = p2; + } + } + return std::pair(outputTimestamp, outputRelativeTimer); +} + +void maybeOutputTimestamp(std::ostringstream &s) { + static const std::pair<bool, bool> aEnvFlags = getTimestampFlags(getLogLevelEnvVar()); + const auto& [outputTimestamp, outputRelativeTimer] = (pLogSelector == nullptr ? aEnvFlags : getTimestampFlags(pLogSelector)); + + if (outputTimestamp) + { + char ts[100]; + TimeValue systemTime; + osl_getSystemTime(&systemTime); + TimeValue localTime; + osl_getLocalTimeFromSystemTime(&systemTime, &localTime); + oslDateTime dateTime; + osl_getDateTimeFromTimeValue(&localTime, &dateTime); + struct tm tm; + tm.tm_sec = dateTime.Seconds; + tm.tm_min = dateTime.Minutes; + tm.tm_hour = dateTime.Hours; + tm.tm_mday = dateTime.Day; + tm.tm_wday = dateTime.DayOfWeek; + tm.tm_mon = dateTime.Month - 1; + tm.tm_year = dateTime.Year - 1900; + tm.tm_yday = 0; + strftime(ts, sizeof(ts), "%Y-%m-%d:%H:%M:%S", &tm); + char milliSecs[11]; + snprintf(milliSecs, sizeof(milliSecs), "%03u", + static_cast<unsigned>(dateTime.NanoSeconds / 1000000)); + s << ts << '.' << milliSecs << ':'; + } + if (!outputRelativeTimer) + return; + + TimeValue now; + osl_getSystemTime(&now); + int seconds = now.Seconds - aStartTime.aTime.Seconds; + int milliSeconds; + if (now.Nanosec < aStartTime.aTime.Nanosec) + { + seconds--; + milliSeconds = 1000 - (aStartTime.aTime.Nanosec - now.Nanosec) / 1000000; + } + else + milliSeconds = (now.Nanosec - aStartTime.aTime.Nanosec) / 1000000; + char relativeTimestamp[100]; + snprintf(relativeTimestamp, sizeof(relativeTimestamp), "%d.%03d", seconds, milliSeconds); + s << relativeTimestamp << ':'; +} + +#endif + +} + +void sal_detail_log( + sal_detail_LogLevel level, char const * area, char const * where, + char const * message, sal_uInt32 backtraceDepth) +{ + std::ostringstream s; +#if !defined ANDROID + // On Android, the area will be used as the "tag," and log info already + // contains timestamp and PID. + if (!sal_use_syslog) { + maybeOutputTimestamp(s); + s << toString(level) << ':'; + } + if (level != SAL_DETAIL_LOG_LEVEL_DEBUG) { + s << area << ':'; + } + s << OSL_DETAIL_GETPID << ':'; +#endif + s << osl::Thread::getCurrentIdentifier() << ':'; + if (level == SAL_DETAIL_LOG_LEVEL_DEBUG) { + s << ' '; + } else { + const size_t nStrLen(std::strlen(SRCDIR "/")); + s << (where + + (std::strncmp(where, SRCDIR "/", nStrLen) == 0 + ? nStrLen : 0)); + } + s << message; + if (backtraceDepth != 0) { + s << " at:\n" << osl::detail::backtraceAsString(backtraceDepth); + } + +#if defined ANDROID + int android_log_level; + switch (level) { + case SAL_DETAIL_LOG_LEVEL_INFO: + android_log_level = ANDROID_LOG_INFO; + break; + case SAL_DETAIL_LOG_LEVEL_WARN: + android_log_level = ANDROID_LOG_WARN; + break; + case SAL_DETAIL_LOG_LEVEL_DEBUG: + android_log_level = ANDROID_LOG_DEBUG; + break; + default: + android_log_level = ANDROID_LOG_INFO; + break; + } + __android_log_print( + android_log_level, area == 0 ? "LibreOffice" : area, "%s", + s.str().c_str()); +#else + if (sal_use_syslog) { +#if HAVE_SYSLOG_H + int prio; + switch (level) { + case SAL_DETAIL_LOG_LEVEL_INFO: + prio = LOG_INFO; + break; + case SAL_DETAIL_LOG_LEVEL_WARN: + prio = LOG_WARNING; + break; + case SAL_DETAIL_LOG_LEVEL_DEBUG: + prio = LOG_DEBUG; + break; + default: + assert(false); // this cannot happen + prio = LOG_WARNING; + } + syslog(prio, "%s", s.str().c_str()); +#endif + } else { + // avoid calling getLogFile() more than once + static std::ofstream * logFile = getLogFile(); + if (logFile) { + *logFile << s.str() << std::endl; + } + else { + s << '\n'; +#ifdef _WIN32 + // write to Windows debugger console, too + OutputDebugStringA(s.str().c_str()); +#endif + std::fputs(s.str().c_str(), stderr); + std::fflush(stderr); + } + } +#endif +} + +void sal_detail_set_log_selector(char const *logSelector) +{ + pLogSelector = logSelector; +} + +void sal_detail_logFormat( + sal_detail_LogLevel level, char const * area, char const * where, + char const * format, ...) +{ + const sal_detail_LogAction eAction + = static_cast<sal_detail_LogAction>(sal_detail_log_report(level, area)); + if (eAction == SAL_DETAIL_LOG_ACTION_IGNORE) + return; + + std::va_list args; + va_start(args, format); + char buf[1024]; + int const len = sizeof buf - RTL_CONSTASCII_LENGTH("..."); + int n = vsnprintf(buf, len, format, args); + if (n < 0) { + std::strcpy(buf, "???"); + } else if (n >= len) { + std::strcpy(buf + len - 1, "..."); + } + sal_detail_log(level, area, where, buf, 0); + va_end(args); + + if (eAction == SAL_DETAIL_LOG_ACTION_FATAL) + std::abort(); +} + +unsigned char sal_detail_log_report(sal_detail_LogLevel level, char const * area) +{ + if (level == SAL_DETAIL_LOG_LEVEL_DEBUG) { + return SAL_DETAIL_LOG_ACTION_LOG; + } + assert(area != nullptr); + static char const* const envEnv = [] { + char const* pResult = getLogLevelEnvVar(); + if (!pResult) + pResult = "+WARN"; + return pResult; + }(); + char const* const env = (pLogSelector == nullptr ? envEnv : pLogSelector); + std::size_t areaLen = std::strlen(area); + enum Sense { POSITIVE = 0, NEGATIVE = 1 }; + std::size_t senseLen[2] = { 0, 1 }; + // initial senseLen[POSITIVE] < senseLen[NEGATIVE], so that if there are + // no matching switches at all, the result will be negative (and + // initializing with 1 is safe as the length of a valid switch, even + // without the "+"/"-" prefix, will always be > 1) + bool senseFatal[2] = { false, false }; + bool seenWarn = false; + bool bFlagFatal = false; + for (char const * p = env;;) { + Sense sense; + switch (*p++) { + case '\0': + { + if (level == SAL_DETAIL_LOG_LEVEL_WARN && !seenWarn) + return sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, area); + + sal_detail_LogAction eAction = SAL_DETAIL_LOG_ACTION_IGNORE; + // if a specific item is positive and negative (==), default to positive + if (senseLen[POSITIVE] >= senseLen[NEGATIVE]) + { + if (senseFatal[POSITIVE]) eAction = SAL_DETAIL_LOG_ACTION_FATAL; + else eAction = SAL_DETAIL_LOG_ACTION_LOG; + } + return eAction; + } + case '+': + sense = POSITIVE; + break; + case '-': + sense = NEGATIVE; + break; + default: + return SAL_DETAIL_LOG_ACTION_LOG; // upon an illegal SAL_LOG value, enable everything + } + char const * p1 = p; + while (*p1 != '.' && *p1 != '+' && *p1 != '-' && *p1 != '\0') { + ++p1; + } + bool match; + if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("INFO"))) { + match = level == SAL_DETAIL_LOG_LEVEL_INFO; + } else if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("WARN"))) + { + match = level == SAL_DETAIL_LOG_LEVEL_WARN; + seenWarn = true; + } else if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("FATAL"))) + { + bFlagFatal = (sense == POSITIVE); + match = false; + } else if (equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")) || + equalStrings(p, p1 - p, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER"))) + { + // handled later + match = false; + } else { + return SAL_DETAIL_LOG_ACTION_LOG; + // upon an illegal SAL_LOG value, everything is considered + // positive + } + char const * p2 = p1; + while (*p2 != '+' && *p2 != '-' && *p2 != '\0') { + ++p2; + } + if (match) { + if (*p1 == '.') { + ++p1; + std::size_t n = p2 - p1; + if ((n == areaLen && equalStrings(p1, n, area, areaLen)) + || (n < areaLen && area[n] == '.' + && equalStrings(p1, n, area, n))) + { + senseLen[sense] = p2 - p; + senseFatal[sense] = bFlagFatal; + } + } else { + senseLen[sense] = p1 - p; + senseFatal[sense] = bFlagFatal; + } + } + p = p2; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/mutexshared.cxx b/sal/osl/all/mutexshared.cxx new file mode 100644 index 000000000..3de7f9930 --- /dev/null +++ b/sal/osl/all/mutexshared.cxx @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <osl/mutex.hxx> + +oslMutex* SAL_CALL osl_getGlobalMutex() +{ + static osl::Mutex g_Mutex; + return &g_Mutex.mutex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/all/signalshared.cxx b/sal/osl/all/signalshared.cxx new file mode 100644 index 000000000..530413f26 --- /dev/null +++ b/sal/osl/all/signalshared.cxx @@ -0,0 +1,141 @@ +/* -*- 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 <stdlib.h> + +#include <signalshared.hxx> + +#include <osl/diagnose.h> +#include <mutex> + +namespace +{ +oslSignalHandlerImpl* SignalList; +bool bInitSignal = false; + +std::mutex& getSignalMutex() +{ + static std::mutex aMutex; + return aMutex; +} +} + +oslSignalAction callSignalHandler(oslSignalInfo* pInfo) +{ + oslSignalHandlerImpl* pHandler = SignalList; + oslSignalAction Action = osl_Signal_ActCallNextHdl; + + while (pHandler) + { + if ((Action = pHandler->Handler(pHandler->pData, pInfo)) != osl_Signal_ActCallNextHdl) + break; + + pHandler = pHandler->pNext; + } + + return Action; +} + +oslSignalHandler SAL_CALL osl_addSignalHandler(oslSignalHandlerFunction handler, void* pData) +{ + if (!handler) + return nullptr; + + oslSignalHandlerImpl* pHandler + = static_cast<oslSignalHandlerImpl*>(calloc(1, sizeof(oslSignalHandlerImpl))); + + std::scoped_lock aGuard(getSignalMutex()); + + if (!bInitSignal) + bInitSignal = onInitSignal(); + + if (pHandler) + { + pHandler->Handler = handler; + pHandler->pData = pData; + + pHandler->pNext = SignalList; + SignalList = pHandler; + + return pHandler; + } + + return nullptr; +} + +sal_Bool SAL_CALL osl_removeSignalHandler(oslSignalHandler handler) +{ + std::scoped_lock aGuard(getSignalMutex()); + + if (!bInitSignal) + bInitSignal = onInitSignal(); + + oslSignalHandlerImpl* pHandler = SignalList; + oslSignalHandlerImpl* pPrevious = nullptr; + + while (pHandler) + { + if (pHandler == handler) + { + if (pPrevious) + pPrevious->pNext = pHandler->pNext; + else + SignalList = pHandler->pNext; + + if (SignalList == nullptr) + bInitSignal = onDeInitSignal(); + + free(pHandler); + + return true; + } + + pPrevious = pHandler; + pHandler = pHandler->pNext; + } + + return false; +} + +oslSignalAction SAL_CALL osl_raiseSignal(sal_Int32 userSignal, void* userData) +{ + std::scoped_lock aGuard(getSignalMutex()); + + if (!bInitSignal) + bInitSignal = onInitSignal(); + + oslSignalInfo info; + info.Signal = osl_Signal_User; + info.UserSignal = userSignal; + info.UserData = userData; + + oslSignalAction action = callSignalHandler(&info); + + return action; +} + +sal_Bool SAL_CALL osl_setErrorReporting(sal_Bool /*bEnable*/) +{ + // this is part of the stable API + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/all/utility.cxx b/sal/osl/all/utility.cxx new file mode 100644 index 000000000..e22bdd3de --- /dev/null +++ b/sal/osl/all/utility.cxx @@ -0,0 +1,49 @@ +/* -*- 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/time.h> + +/* + * mfe : maybe it would be wishful to include initialization + * of the global timer in dllmain or _init directly. + * But nonetheless this (should) work too. + */ +namespace osl +{ + +namespace { + +class OGlobalTimer +{ + +public: + + OGlobalTimer() { + osl_getGlobalTimer(); + } + +}; + +} + +static OGlobalTimer aGlobalTimer; + +} // namespace osl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/asm/interlck_sparc.s b/sal/osl/unx/asm/interlck_sparc.s new file mode 100644 index 000000000..7b971a50a --- /dev/null +++ b/sal/osl/unx/asm/interlck_sparc.s @@ -0,0 +1,79 @@ +/* + * 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 . + */ + + +/* + * Implements osl_[increment|decrement]InterlockedCount: + * sparcv9/sparcv8plus architecture: use the "cas" instruction + * + * 32 bit mode with v8plus support or 64 bit mode: + * sparcv9 mode is implied. Assemble with -xarch=v8plus (32 bit) or + * -xarch=v9 (64 bit). + * + */ + +#if !defined(__sparcv8plus) && !defined(__sparcv9) && !defined(__sparc_v9__) + +#error LibreOffice requires SPARCv8plus or SPARCv9 CPU with "cas" instruction + +#endif + +.section ".text" + .global osl_incrementInterlockedCount + .align 8 + +! Implements osl_[increment|decrement]InterlockedCount with sparcv9(sparcv8plus) "cas" +! instruction. + +osl_incrementInterlockedCount: + +1: ld [%o0], %o1 + add %o1, 1, %o2 +! allow linux to build for v8 + .word 0xD5E21009 +! cas [%o0], %o1, %o2 + cmp %o1, %o2 + bne 1b + nop ! delay slot + retl + add %o2, 1, %o0 ! delay slot + + .type osl_incrementInterlockedCount,#function + .size osl_incrementInterlockedCount,.-osl_incrementInterlockedCount + + +.section ".text" + .global osl_decrementInterlockedCount + .align 8 + +osl_decrementInterlockedCount: + +1: ld [%o0], %o1 + sub %o1, 1, %o2 +! allow linux to build for v8 + .word 0xD5E21009 +! cas [%o0], %o1, %o2 + cmp %o1, %o2 + bne 1b + nop ! delay slot + retl + sub %o2, 1, %o0 ! delay slot + + .type osl_decrementInterlockedCount,#function + .size osl_decrementInterlockedCount,.-osl_decrementInterlockedCount + diff --git a/sal/osl/unx/backtrace.c b/sal/osl/unx/backtrace.c new file mode 100644 index 000000000..f7bd01c33 --- /dev/null +++ b/sal/osl/unx/backtrace.c @@ -0,0 +1,246 @@ +/* -*- 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 "backtrace.h" + +#if ! HAVE_FEATURE_BACKTRACE /* no GNU backtrace implementation available */ + +#include <sal/types.h> + +#ifdef __sun /* Solaris */ + +#include <dlfcn.h> +#include <pthread.h> +#include <setjmp.h> +#include <stdio.h> +#include <sys/frame.h> + +#if defined(SPARC) + +#if defined IS_LP64 + +#define FRAME_PTR_OFFSET 1 +#define FRAME_OFFSET 0 +#define STACK_BIAS 0x7ff + +#else + +#define FRAME_PTR_OFFSET 1 +#define FRAME_OFFSET 0 +#define STACK_BIAS 0 + +#endif + +#elif defined( INTEL ) + +#define FRAME_PTR_OFFSET 3 +#define FRAME_OFFSET 0 +#define STACK_BIAS 0 + +#else + +#error Unknown Solaris target platform. + +#endif /* defined SPARC or INTEL */ + +int backtrace( void **buffer, int max_frames ) +{ + jmp_buf ctx; + long fpval; + struct frame *fp; + int i; + + /* flush register windows */ +#ifdef SPARC + asm("ta 3"); +#endif + + /* get stack- and framepointer */ + setjmp(ctx); + + fpval = ((long*)(ctx))[FRAME_PTR_OFFSET]; + fp = (struct frame*)((char*)(fpval) + STACK_BIAS); + + for (i = 0; (i < FRAME_OFFSET) && (fp != 0); i++) + fp = (struct frame*)((char*)(fp->fr_savfp) + STACK_BIAS); + + /* iterate through backtrace */ + for (i = 0; (fp != 0) && (fp->fr_savpc != 0) && (i < max_frames); i++) + { + /* saved (prev) frame */ + struct frame * prev = (struct frame*)((char*)(fp->fr_savfp) + STACK_BIAS); + + /* store frame */ + *(buffer++) = (void*)(fp->fr_savpc); + + /* prev frame (w/ stack growing top down) */ + fp = (prev > fp) ? prev : 0; + } + + /* return number of frames stored */ + return i; +} + +char ** backtrace_symbols(void * const * buffer, int size) +{ + (void)buffer; (void)size; + return NULL; /*TODO*/ +} + +void backtrace_symbols_fd( void **buffer, int size, int fd ) +{ + FILE *fp = fdopen( fd, "w" ); + + if ( fp ) + { + void **pFramePtr; + + for ( pFramePtr = buffer; size > 0 && pFramePtr && *pFramePtr; pFramePtr++, size-- ) + { + Dl_info dli; + ptrdiff_t offset; + + if ( 0 != dladdr( *pFramePtr, &dli ) ) + { + if ( dli.dli_fname && dli.dli_fbase ) + { + offset = (ptrdiff_t)*pFramePtr - (ptrdiff_t)dli.dli_fbase; + fprintf( fp, "%s+0x%" SAL_PRI_PTRDIFFT "x", dli.dli_fname, offset ); + } + if ( dli.dli_sname && dli.dli_saddr ) + { + offset = (ptrdiff_t)*pFramePtr - (ptrdiff_t)dli.dli_saddr; + fprintf( fp, "(%s+0x%" SAL_PRI_PTRDIFFT "x)", dli.dli_sname, offset ); + } + } + fprintf( fp, "[%p]\n", *pFramePtr ); + } + + fclose( fp ); + } +} + +#elif defined FREEBSD || defined NETBSD || defined OPENBSD || defined(DRAGONFLY) + +#include <dlfcn.h> +#include <pthread.h> +#include <setjmp.h> +#include <stddef.h> +#include <stdio.h> + +/* no frame.h on FreeBSD */ +struct frame { + struct frame *fr_savfp; + long fr_savpc; +}; + +#if defined(POWERPC) || defined(POWERPC64) + +#define FRAME_PTR_OFFSET 1 +#define FRAME_OFFSET 0 + +#else + +#define FRAME_PTR_OFFSET 3 +#define FRAME_OFFSET 0 + +#endif + +int backtrace( void **buffer, int max_frames ) +{ + struct frame *fp; + jmp_buf ctx; + int i; + /* get stack- and framepointer */ + setjmp(ctx); + fp = (struct frame*)(((size_t*)(ctx))[FRAME_PTR_OFFSET]); + for ( i=0; (i<FRAME_OFFSET) && (fp!=0); i++) + fp = fp->fr_savfp; + /* iterate through backtrace */ + for (i=0; fp && fp->fr_savpc && i<max_frames; i++) + { + /* store frame */ + *(buffer++) = (void *)fp->fr_savpc; + /* next frame */ + fp=fp->fr_savfp; + } + return i; +} + +char ** backtrace_symbols(void * const * buffer, int size) +{ + (void)buffer; (void)size; + return NULL; /*TODO*/ +} + +void backtrace_symbols_fd( void **buffer, int size, int fd ) +{ + FILE *fp = fdopen( fd, "w" ); + + if ( fp ) + { + void **pFramePtr; + for ( pFramePtr = buffer; size > 0 && pFramePtr && *pFramePtr; pFramePtr++, size-- ) + { + Dl_info dli; + ptrdiff_t offset; + + if ( 0 != dladdr( *pFramePtr, &dli ) ) + { + if ( dli.dli_fname && dli.dli_fbase ) + { + offset = (ptrdiff_t)*pFramePtr - (ptrdiff_t)dli.dli_fbase; + fprintf( fp, "%s+0x%" SAL_PRI_PTRDIFFT "x", dli.dli_fname, offset ); + } + if ( dli.dli_sname && dli.dli_saddr ) + { + offset = (ptrdiff_t)*pFramePtr - (ptrdiff_t)dli.dli_saddr; + fprintf( fp, "(%s+0x%" SAL_PRI_PTRDIFFT "x)", dli.dli_sname, offset ); + } + } + fprintf( fp, "[%p]\n", *pFramePtr ); + } + fclose( fp ); + } +} + +#else /* not GNU/BSD/Solaris */ + +int backtrace( void **buffer, int max_frames ) +{ + (void)buffer; (void)max_frames; + return 0; +} + +char ** backtrace_symbols(void * const * buffer, int size) +{ + (void)buffer; (void)size; + return NULL; /*TODO*/ +} + +void backtrace_symbols_fd( void **buffer, int size, int fd ) +{ + (void)buffer; (void)size; (void)fd; +} + +#endif /* not GNU/BSD/Solaris */ + +#endif /* ! HAVE_FEATURE_BACKTRACE */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/backtrace.h b/sal/osl/unx/backtrace.h new file mode 100644 index 000000000..11a9da52e --- /dev/null +++ b/sal/osl/unx/backtrace.h @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_BACKTRACE_H +#define INCLUDED_SAL_OSL_UNX_BACKTRACE_H + +#include <config_features.h> + +#if HAVE_FEATURE_BACKTRACE /* GNU backtrace implementation available */ + +#include <execinfo.h> + +#else + +#ifdef __cplusplus +extern "C" { +#endif + +/* backtrace function with same behaviour as defined in GNU libc */ + +int backtrace( void **buffer, int max_frames ); + +char ** backtrace_symbols(void * const * buffer, int size); + +void backtrace_symbols_fd( void **buffer, int size, int fd ); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/backtraceapi.cxx b/sal/osl/unx/backtraceapi.cxx new file mode 100644 index 000000000..a4cec46ca --- /dev/null +++ b/sal/osl/unx/backtraceapi.cxx @@ -0,0 +1,279 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstdlib> +#include <limits> +#include <memory> +#include <mutex> + +#include <o3tl/runtimetooustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <sal/log.hxx> +#include <sal/backtrace.hxx> + +#include "backtrace.h" +#include <backtraceasstring.hxx> + +OUString osl::detail::backtraceAsString(sal_uInt32 maxDepth) { + std::unique_ptr<sal::BacktraceState> backtrace = sal::backtrace_get( maxDepth ); + return sal::backtrace_to_string( backtrace.get()); +} + +std::unique_ptr<sal::BacktraceState> sal::backtrace_get(sal_uInt32 maxDepth) +{ + assert(maxDepth != 0); + auto const maxInt = static_cast<unsigned int>( + std::numeric_limits<int>::max()); + if (maxDepth > maxInt) { + maxDepth = static_cast<sal_uInt32>(maxInt); + } + auto b1 = new void *[maxDepth]; + int n = backtrace(b1, static_cast<int>(maxDepth)); + return std::unique_ptr<BacktraceState>(new BacktraceState{ b1, n }); +} + +#if OSL_DEBUG_LEVEL > 0 && (defined LINUX || defined MACOSX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined(DRAGONFLY)) +// The backtrace_symbols() function is unreliable, it requires -rdynamic and even then it cannot resolve names +// of many functions, such as those with hidden ELF visibility. Libunwind doesn't resolve names for me either, +// boost::stacktrace doesn't work properly, the best result I've found is addr2line. Using addr2line is relatively +// slow, but I don't find that to be a big problem for printing of backtraces. Feel free to improve if needed +// (e.g. the calls could be grouped by the binary). +#include <dlfcn.h> +#include <unistd.h> +#include <vector> +#include <osl/process.h> +#include <rtl/strbuf.hxx> +#include <osl/mutex.hxx> +#include <o3tl/lru_map.hxx> +#include "file_url.hxx" + +namespace +{ +struct FrameData +{ + const char* file = nullptr; + void* addr; + ptrdiff_t offset; + OString info; + bool handled = false; +}; + +typedef o3tl::lru_map<void*, OString> FrameCache; +std::mutex frameCacheMutex; +FrameCache frameCache( 256 ); + +void process_file_addr2line( const char* file, std::vector<FrameData>& frameData ) +{ + if(access( file, R_OK ) != 0) + return; // cannot read info from the binary file anyway + OUString binary("addr2line"); + OUString dummy; +#if defined __clang__ + // llvm-addr2line is faster than addr2line + if(osl::detail::find_in_PATH("llvm-addr2line", dummy)) + binary = "llvm-addr2line"; +#endif + if(!osl::detail::find_in_PATH(binary, dummy)) + return; // Will not work, avoid warnings from osl process code. + OUString arg1("-Cfe"); + OUString arg2 = OUString::fromUtf8(file); + std::vector<OUString> addrs; + std::vector<rtl_uString*> args; + args.reserve(frameData.size() + 2); + args.push_back( arg1.pData ); + args.push_back( arg2.pData ); + for( FrameData& frame : frameData ) + { + if( frame.file != nullptr && strcmp( file, frame.file ) == 0 ) + { + addrs.push_back("0x" + OUString::number(frame.offset, 16)); + args.push_back(addrs.back().pData); + frame.handled = true; + } + } + + oslProcess aProcess; + oslFileHandle pOut = nullptr; + oslFileHandle pErr = nullptr; + oslSecurity pSecurity = osl_getCurrentSecurity(); + oslProcessError eErr = osl_executeProcess_WithRedirectedIO( + binary.pData, args.data(), args.size(), osl_Process_SEARCHPATH | osl_Process_HIDDEN, pSecurity, nullptr, + nullptr, 0, &aProcess, nullptr, &pOut, &pErr); + osl_freeSecurityHandle(pSecurity); + + if (eErr != osl_Process_E_None) + { + SAL_WARN("sal.osl", binary << " call to resolve " << file << " symbols failed"); + return; + } + + OStringBuffer outputBuffer; + if (pOut) + { + const sal_uInt64 BUF_SIZE = 1024; + char buffer[BUF_SIZE]; + while (true) + { + sal_uInt64 bytesRead = 0; + while(osl_readFile(pErr, buffer, BUF_SIZE, &bytesRead) == osl_File_E_None + && bytesRead != 0) + ; // discard possible stderr output + oslFileError err = osl_readFile(pOut, buffer, BUF_SIZE, &bytesRead); + if(bytesRead == 0 && err == osl_File_E_None) + break; + outputBuffer.append(buffer, bytesRead); + if (err != osl_File_E_None && err != osl_File_E_AGAIN) + break; + } + osl_closeFile(pOut); + } + if(pErr) + osl_closeFile(pErr); + eErr = osl_joinProcess(aProcess); + osl_freeProcessHandle(aProcess); + + OString output = outputBuffer.makeStringAndClear(); + std::vector<OString> lines; + sal_Int32 outputPos = 0; + while(outputPos < output.getLength()) + { + sal_Int32 end1 = output.indexOf('\n', outputPos); + if(end1 < 0) + break; + sal_Int32 end2 = output.indexOf('\n', end1 + 1); + if(end2 < 0) + end2 = output.getLength(); + lines.push_back(output.copy( outputPos, end1 - outputPos )); + lines.push_back(output.copy( end1 + 1, end2 - end1 - 1 )); + outputPos = end2 + 1; + } + if(lines.size() != addrs.size() * 2) + { + SAL_WARN("sal.osl", "failed to parse " << binary << " call output to resolve " << file << " symbols "); + return; // addr2line problem? + } + size_t linesPos = 0; + for( FrameData& frame : frameData ) + { + if( frame.file != nullptr && strcmp( file, frame.file ) == 0 ) + { + // There should be two lines, first function name and second source file information. + // If each of them starts with ??, it is invalid/unknown. + OString function = lines[linesPos]; + OString source = lines[linesPos+1]; + linesPos += 2; + if(function.isEmpty() || function.startsWith("??")) + { + // Cache that the address cannot be resolved. + std::lock_guard guard(frameCacheMutex); + frameCache.insert( { frame.addr, "" } ); + } + else + { + if( source.startsWith("??")) + frame.info = function + " in " + file; + else + frame.info = function + " at " + source; + std::lock_guard guard(frameCacheMutex); + frameCache.insert( { frame.addr, frame.info } ); + } + } + } +} + +} // namespace + +OUString sal::backtrace_to_string(BacktraceState* backtraceState) +{ + // Collect frames for each binary and process each binary in one addr2line + // call for better performance. + std::vector< FrameData > frameData; + frameData.resize(backtraceState->nDepth); + for (int i = 0; i != backtraceState->nDepth; ++i) + { + Dl_info dli; + void* addr = backtraceState->buffer[i]; + std::unique_lock guard(frameCacheMutex); + auto it = frameCache.find(addr); + bool found = it != frameCache.end(); + guard.unlock(); + if( found ) + { + frameData[ i ].info = it->second; + frameData[ i ].handled = true; + } + else if (dladdr(addr, &dli) != 0) + { + if (dli.dli_fname && dli.dli_fbase) + { + frameData[ i ].file = dli.dli_fname; + frameData[ i ].addr = addr; + frameData[ i ].offset = reinterpret_cast<ptrdiff_t>(addr) - reinterpret_cast<ptrdiff_t>(dli.dli_fbase); + } + } + } + for (int i = 0; i != backtraceState->nDepth; ++i) + { + if(frameData[ i ].file != nullptr && !frameData[ i ].handled) + process_file_addr2line( frameData[ i ].file, frameData ); + } + OUStringBuffer b3; + std::unique_ptr<char*, decltype(free)*> b2{ nullptr, free }; + bool fallbackInitDone = false; + for (int i = 0; i != backtraceState->nDepth; ++i) + { + if (i != 0) + b3.append("\n"); + b3.append( "#" + OUString::number( i ) + " " ); + if(!frameData[i].info.isEmpty()) + b3.append(o3tl::runtimeToOUString(frameData[i].info.getStr())); + else + { + if(!fallbackInitDone) + { + b2 = std::unique_ptr<char*, decltype(free)*> + {backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free}; + fallbackInitDone = true; + } + if(b2) + b3.append(o3tl::runtimeToOUString(b2.get()[i])); + else + b3.append("??"); + } + } + return b3.makeStringAndClear(); +} + +#else + +OUString sal::backtrace_to_string(BacktraceState* backtraceState) +{ + std::unique_ptr<char*, decltype(free)*> b2{backtrace_symbols(backtraceState->buffer, backtraceState->nDepth), free}; + if (!b2) { + return OUString(); + } + OUStringBuffer b3; + for (int i = 0; i != backtraceState->nDepth; ++i) { + if (i != 0) { + b3.append("\n"); + } + b3.append( "#" + OUString::number( i ) + " " ); + b3.append(o3tl::runtimeToOUString(b2.get()[i])); + } + return b3.makeStringAndClear(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/conditn.cxx b/sal/osl/unx/conditn.cxx new file mode 100644 index 000000000..602240922 --- /dev/null +++ b/sal/osl/unx/conditn.cxx @@ -0,0 +1,270 @@ +/* -*- 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 <assert.h> + +#include "system.hxx" +#include "unixerrnostring.hxx" +#include <sal/log.hxx> +#include <sal/types.h> + +#include <osl/conditn.h> +#include <osl/time.h> + +namespace { + +struct oslConditionImpl +{ + pthread_cond_t m_Condition; + pthread_mutex_t m_Lock; + bool m_State; +}; + +} + +oslCondition SAL_CALL osl_createCondition() +{ + oslConditionImpl* pCond; + int nRet=0; + + pCond = static_cast<oslConditionImpl*>(malloc(sizeof(oslConditionImpl))); + + if ( pCond == nullptr ) + { + return nullptr; + } + + pCond->m_State = false; + + /* init condition variable with default attr. (PTHREAD_PROCESS_PRIVAT) */ + nRet = pthread_cond_init(&pCond->m_Condition, PTHREAD_CONDATTR_DEFAULT); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "pthread_cond_init failed: " << UnixErrnoString(nRet) ); + + free(pCond); + return nullptr; + } + + nRet = pthread_mutex_init(&pCond->m_Lock, PTHREAD_MUTEXATTR_DEFAULT); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "pthread_mutex_init failed: " << UnixErrnoString(nRet) ); + + nRet = pthread_cond_destroy(&pCond->m_Condition); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "pthread_cond_destroy failed: " << UnixErrnoString(nRet) ); + + free(pCond); + pCond = nullptr; + } + + SAL_INFO( "sal.osl.condition", "osl_createCondition(): " << pCond ); + + return static_cast<oslCondition>(pCond); +} + +void SAL_CALL osl_destroyCondition(oslCondition Condition) +{ + oslConditionImpl* pCond; + + pCond = static_cast<oslConditionImpl*>(Condition); + + SAL_INFO( "sal.osl.condition", "osl_destroyCondition(" << pCond << ")" ); + + if ( pCond ) + { + int nRet = pthread_cond_destroy(&pCond->m_Condition); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "pthread_cond_destroy failed: " << UnixErrnoString(nRet) ); + nRet = pthread_mutex_destroy(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "pthread_mutex_destroy failed: " << UnixErrnoString(nRet) ); + + free(Condition); + } +} + +sal_Bool SAL_CALL osl_setCondition(oslCondition Condition) +{ + oslConditionImpl* pCond; + int nRet=0; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + nRet = pthread_mutex_lock(&pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_setCondition(" << pCond << "): pthread_mutex_lock failed: " << UnixErrnoString(nRet) ); + return false; + } + + pCond->m_State = true; + nRet = pthread_cond_broadcast(&pCond->m_Condition); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_setCondition(" << pCond << "): pthread_cond_broadcast failed: " << UnixErrnoString(nRet) ); + // try to unlock the mutex + pthread_mutex_unlock(&pCond->m_Lock); + return false; + } + + nRet = pthread_mutex_unlock(&pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_setCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + return false; + } + + SAL_INFO( "sal.osl.condition", "osl_setCondition(" << pCond << ")" ); + + return true; + +} + +sal_Bool SAL_CALL osl_resetCondition(oslCondition Condition) +{ + oslConditionImpl* pCond; + int nRet=0; + + assert(Condition); + + pCond = static_cast<oslConditionImpl*>(Condition); + + nRet = pthread_mutex_lock(&pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_resetCondition(" << pCond << "): pthread_mutex_lock failed: " << UnixErrnoString(nRet) ); + return false; + } + + pCond->m_State = false; + + nRet = pthread_mutex_unlock(&pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_resetCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + return false; + } + + SAL_INFO( "sal.osl.condition", "osl_resetCondition(" << pCond << ")" ); + + return true; +} + +oslConditionResult SAL_CALL osl_waitCondition(oslCondition Condition, const TimeValue* pTimeout) +{ + oslConditionImpl* pCond; + int nRet=0; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + SAL_INFO( "sal.osl.condition", "osl_waitCondition(" << pCond << ")" ); + + nRet = pthread_mutex_lock(&pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_mutex_lock failed: " << UnixErrnoString(nRet) ); + return osl_cond_result_error; + } + + if ( pTimeout ) + { + if ( ! pCond->m_State ) + { + struct timeval tp; + struct timespec to; + + gettimeofday(&tp, nullptr); + + SET_TIMESPEC( to, tp.tv_sec + pTimeout->Seconds, + tp.tv_usec * 1000 + pTimeout->Nanosec ); + + /* spurious wake up prevention */ + do + { + const int ret = pthread_cond_timedwait(&pCond->m_Condition, &pCond->m_Lock, &to); + if ( ret != 0 ) + { + if ( ret == ETIME || ret == ETIMEDOUT ) + { + nRet = pthread_mutex_unlock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + + return osl_cond_result_timeout; + } + if ( ret != EINTR ) + { + nRet = pthread_mutex_unlock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + return osl_cond_result_error; + } + } + } + while ( !pCond->m_State ); + } + } + else + { + while ( !pCond->m_State ) + { + nRet = pthread_cond_wait(&pCond->m_Condition, &pCond->m_Lock); + if ( nRet != 0 ) + { + SAL_WARN( "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_cond_wait failed: " << UnixErrnoString(nRet) ); + nRet = pthread_mutex_unlock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + + return osl_cond_result_error; + } + } + } + + nRet = pthread_mutex_unlock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_waitCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + + SAL_INFO( "sal.osl.condition", "osl_waitCondition(" << pCond << "): OK" ); + + return osl_cond_result_ok; +} + +sal_Bool SAL_CALL osl_checkCondition(oslCondition Condition) +{ + bool State; + oslConditionImpl* pCond; + int nRet=0; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + nRet = pthread_mutex_lock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_checkCondition(" << pCond << "): pthread_mutex_lock failed: " << UnixErrnoString(nRet) ); + + State = pCond->m_State; + + nRet = pthread_mutex_unlock(&pCond->m_Lock); + SAL_WARN_IF( nRet != 0, "sal.osl.condition", "osl_checkCondition(" << pCond << "): pthread_mutex_unlock failed: " << UnixErrnoString(nRet) ); + + SAL_INFO( "sal.osl.condition", "osl_checkCondition(" << pCond << "): " << (State ? "YES" : "NO") ); + + return State; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/createfilehandlefromfd.hxx b/sal/osl/unx/createfilehandlefromfd.hxx new file mode 100644 index 000000000..bb8a768ef --- /dev/null +++ b/sal/osl/unx/createfilehandlefromfd.hxx @@ -0,0 +1,24 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_UNX_CREATEFILEHANDLEFROMFD_HXX +#define INCLUDED_SAL_OSL_UNX_CREATEFILEHANDLEFROMFD_HXX + +#include <sal/config.h> + +#include <osl/file.h> + +namespace osl::detail +{ +oslFileHandle createFileHandleFromFD(int fd); // defined in file.cxx +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file.cxx b/sal/osl/unx/file.cxx new file mode 100644 index 000000000..ac93ffacd --- /dev/null +++ b/sal/osl/unx/file.cxx @@ -0,0 +1,1606 @@ +/* -*- 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/string_view.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/detail/file.h> +#include <rtl/alloc.h> +#include <rtl/byteseq.hxx> +#include <rtl/string.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 <vector> +#include <cassert> +#include <limits> + +#include <string.h> +#include <pthread.h> +#include <sys/mman.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 + +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>"); + ~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 *>(malloc(m_bufsiz)); + if (m_buffer) + memset(m_buffer, 0, 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) > 0) + { + /* 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 int osl_file_adjustLockFlags(const OString& path, int flags) +{ +#ifdef MACOSX + /* + * 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); + } + } +#else + (void) path; +#endif + + return flags; +} + +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; +} + +#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 ⁢ + } + 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 + { + flags = osl_file_adjustLockFlags (filePath, flags); + } + + // O_EXCL can be set only when O_CREAT is set + if (flags & O_EXCL && !(flags & O_CREAT)) + flags &= ~O_EXCL; + + /* 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; + + 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 { + +// coverity[result_independent_of_operands] - crossplatform requirement +template<typename T> bool exceedsMaxOffT(T n) { return n > MAX_OFF_T; } + +// coverity[result_independent_of_operands] - crossplatform requirement +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) + 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) + 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: */ diff --git a/sal/osl/unx/file_error_transl.cxx b/sal/osl/unx/file_error_transl.cxx new file mode 100644 index 000000000..ad6e00558 --- /dev/null +++ b/sal/osl/unx/file_error_transl.cxx @@ -0,0 +1,181 @@ +/* -*- 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 <cassert> +#include <cerrno> + +#include "file_error_transl.hxx" + +oslFileError oslTranslateFileError(int Errno) +{ + switch (Errno) + { + case EPERM: + return osl_File_E_PERM; + + case ENOENT: + return osl_File_E_NOENT; + + case ESRCH: + return osl_File_E_SRCH; + + case EINTR: + return osl_File_E_INTR; + + case EIO: + return osl_File_E_IO; + + case ENXIO: + return osl_File_E_IO; + + case E2BIG: + return osl_File_E_2BIG; + + case ENOEXEC: + return osl_File_E_NOEXEC; + + case EBADF: + return osl_File_E_BADF; + + case ECHILD: + return osl_File_E_CHILD; + + case EAGAIN: + return osl_File_E_AGAIN; + + case ENOMEM: + return osl_File_E_NOMEM; + + case EACCES: + return osl_File_E_ACCES; + + case EFAULT: + return osl_File_E_FAULT; + + case EBUSY: + return osl_File_E_BUSY; + + case EEXIST: + return osl_File_E_EXIST; + + case EXDEV: + return osl_File_E_XDEV; + + case ENODEV: + return osl_File_E_NODEV; + + case ENOTDIR: + return osl_File_E_NOTDIR; + + case EISDIR: + return osl_File_E_ISDIR; + + case EINVAL: + return osl_File_E_INVAL; + + case ENFILE: + return osl_File_E_NFILE; + + case EMFILE: + return osl_File_E_MFILE; + + case ENOTTY: + return osl_File_E_NOTTY; + + case EFBIG: + return osl_File_E_FBIG; + + case ENOSPC: + return osl_File_E_NOSPC; + + case ESPIPE: + return osl_File_E_SPIPE; + + case EROFS: + return osl_File_E_ROFS; + + case EMLINK: + return osl_File_E_MLINK; + + case EPIPE: + return osl_File_E_PIPE; + + case EDOM: + return osl_File_E_DOM; + + case ERANGE: + return osl_File_E_RANGE; + + case EDEADLK: + return osl_File_E_DEADLK; + + case ENAMETOOLONG: + return osl_File_E_NAMETOOLONG; + + case ENOLCK: + return osl_File_E_NOLCK; + + case ENOSYS: + case ENOTSUP: +#if EOPNOTSUPP != ENOTSUP + case EOPNOTSUPP: +#endif + return osl_File_E_NOSYS; + +#if !defined(AIX) || !(defined(_ALL_SOURCE) && !defined(_LINUX_SOURCE_COMPAT)) + case ENOTEMPTY: + return osl_File_E_NOTEMPTY; +#endif + + case ELOOP: + return osl_File_E_LOOP; + +#if !(defined(MACOSX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD) \ + || defined(DRAGONFLY)) + case EILSEQ: + return osl_File_E_ILSEQ; + + case ENOLINK: + return osl_File_E_NOLINK; + + case EMULTIHOP: + return osl_File_E_MULTIHOP; +#endif /* MACOSX */ + +#if !defined(HAIKU) + case EUSERS: + return osl_File_E_USERS; +#endif + + case EOVERFLOW: + return osl_File_E_OVERFLOW; + + case ETIMEDOUT: + return osl_File_E_TIMEDOUT; + + default: + assert(Errno != 0); + /* FIXME translateFileError: is this alright? Or add a new one: osl_File_E_Unknown? */ + return osl_File_E_invalidError; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_error_transl.hxx b/sal/osl/unx/file_error_transl.hxx new file mode 100644 index 000000000..81d972032 --- /dev/null +++ b/sal/osl/unx/file_error_transl.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_FILE_ERROR_TRANSL_HXX +#define INCLUDED_SAL_OSL_UNX_FILE_ERROR_TRANSL_HXX + +#include <osl/file.h> +#include <sal/types.h> + +/** Translate errno's to osl file errors + + @param [in] nErrno the errno; must not be 0 + + @returns the osl error code appropriate to the errno + +*/ +oslFileError oslTranslateFileError(int Errno); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_impl.hxx b/sal/osl/unx/file_impl.hxx new file mode 100644 index 000000000..2b374cb68 --- /dev/null +++ b/sal/osl/unx/file_impl.hxx @@ -0,0 +1,55 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_FILE_IMPL_HXX +#define INCLUDED_SAL_OSL_UNX_FILE_IMPL_HXX + +#include <osl/file.h> +#include <stddef.h> +#include <sys/types.h> +#include <rtl/string.hxx> + +struct DirectoryItem_Impl +{ + OString m_strFilePath; /* holds native file name */ + sal_Int32 m_RefCount; + unsigned char m_DType; + + explicit DirectoryItem_Impl( + OString strFilePath, unsigned char DType = 0); + ~DirectoryItem_Impl(); + + void acquire(); /* @see osl_acquireDirectoryItem() */ + void release(); /* @see osl_releaseDirectoryItem() */ + + oslFileType getFileType() const; +}; + +oslFileError openFile( + rtl_uString * pustrFileURL, oslFileHandle * pHandle, sal_uInt32 uFlags, + mode_t mode); + +oslFileError openFilePath( + const OString& filePath, + oslFileHandle* pHandle, + sal_uInt32 uFlags, mode_t mode ); + +#endif // INCLUDED_SAL_OSL_UNX_FILE_IMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_misc.cxx b/sal/osl/unx/file_misc.cxx new file mode 100644 index 000000000..b2d6010fe --- /dev/null +++ b/sal/osl/unx/file_misc.cxx @@ -0,0 +1,1049 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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.hxx> +#include <osl/detail/file.h> + +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/signal.h> +#include <rtl/alloc.h> +#include <rtl/string.hxx> +#include <sal/log.hxx> + +#include "system.hxx" +#include "file_impl.hxx" +#include "file_error_transl.hxx" +#include "file_path_helper.hxx" +#include "file_url.hxx" +#include "uunxapi.hxx" +#include "readwrite_helper.hxx" +#include "unixerrnostring.hxx" + +#include <sys/types.h> +#include <errno.h> +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <memory> +#include <new> + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +/************************************************************************ + * TODO + * + * - Fix: check for corresponding struct sizes in exported functions + * - check size/use of oslDirectory + * - check size/use of oslDirectoryItem + ***********************************************************************/ + +namespace { + +struct DirectoryImpl +{ + OString strPath; /* holds native directory path */ + DIR* pDirStruct; +#ifdef ANDROID + enum Kind + { + KIND_DIRENT = 1, + KIND_ASSETS = 2 + }; + int eKind; + lo_apk_dir* pApkDirStruct; +#endif +}; + +} + +DirectoryItem_Impl::DirectoryItem_Impl( + OString strFilePath, unsigned char DType) + : m_strFilePath (std::move(strFilePath)), + m_RefCount (1), + m_DType (DType) +{ +} +DirectoryItem_Impl::~DirectoryItem_Impl() +{ +} + +void DirectoryItem_Impl::acquire() +{ + ++m_RefCount; +} +void DirectoryItem_Impl::release() +{ + if (--m_RefCount == 0) + delete this; +} + +oslFileType DirectoryItem_Impl::getFileType() const +{ + switch (m_DType) + { +#ifdef _DIRENT_HAVE_D_TYPE + case DT_LNK: + return osl_File_Type_Link; + case DT_DIR: + return osl_File_Type_Directory; + case DT_REG: + return osl_File_Type_Regular; + case DT_FIFO: + return osl_File_Type_Fifo; + case DT_SOCK: + return osl_File_Type_Socket; + case DT_CHR: + case DT_BLK: + return osl_File_Type_Special; +#endif /* _DIRENT_HAVE_D_TYPE */ + default: + break; + } + return osl_File_Type_Unknown; +} + +static oslFileError osl_psz_createDirectory( + char const * pszPath, sal_uInt32 flags); +static oslFileError osl_psz_removeDirectory(const char* pszPath); + +oslFileError SAL_CALL osl_openDirectory(rtl_uString* ustrDirectoryURL, oslDirectory* pDirectory) +{ + oslFileError eRet; + + OString path; + + if ((ustrDirectoryURL == nullptr) || (ustrDirectoryURL->length == 0) || (pDirectory == nullptr)) + return osl_File_E_INVAL; + + /* convert file URL to system path */ + eRet = osl::detail::convertUrlToPathname(OUString::unacquired(&ustrDirectoryURL), &path); + + if( eRet != osl_File_E_None ) + return eRet; + + osl_systemPathRemoveSeparator(path.pData); + +#ifdef MACOSX + { + auto const n = std::max(int(path.getLength() + 1), int(PATH_MAX)); + auto const tmp = std::make_unique<char[]>(n); + std::strcpy(tmp.get(), path.getStr()); + if (macxp_resolveAlias(tmp.get(), n) != 0) { + return oslTranslateFileError(errno); + } + path = OString(tmp.get(), std::strlen(tmp.get())); + } +#endif /* MACOSX */ + +#ifdef ANDROID + if( strncmp( path.getStr(), "/assets/", sizeof( "/assets/" ) - 1) == 0 ) + { + lo_apk_dir *pdir = lo_apk_opendir( path.getStr() ); + + if( pdir ) + { + DirectoryImpl* pDirImpl = new(std::nothrow) DirectoryImpl; + + if( pDirImpl ) + { + pDirImpl->eKind = DirectoryImpl::KIND_ASSETS; + pDirImpl->pApkDirStruct = pdir; + pDirImpl->strPath = path; + + *pDirectory = (oslDirectory) pDirImpl; + return osl_File_E_None; + } + else + { + errno = ENOMEM; + lo_apk_closedir( pdir ); + } + } + } + else +#endif + { + /* open directory */ + DIR *pdir = opendir( path.getStr() ); + + if( pdir ) + { + SAL_INFO("sal.file", "opendir(" << path << ") => " << pdir); + + /* create and initialize impl structure */ + DirectoryImpl* pDirImpl = new(std::nothrow) DirectoryImpl; + + if( pDirImpl ) + { + pDirImpl->pDirStruct = pdir; + pDirImpl->strPath = path; +#ifdef ANDROID + pDirImpl->eKind = DirectoryImpl::KIND_DIRENT; +#endif + *pDirectory = static_cast<oslDirectory>(pDirImpl); + return osl_File_E_None; + } + errno = ENOMEM; + closedir( pdir ); + } + else + { + int e = errno; + SAL_INFO("sal.file", "opendir(" << path << "): " << UnixErrnoString(e)); + // Restore errno after possible modification by SAL_INFO above + errno = e; + } + } + + return oslTranslateFileError(errno); +} + +oslFileError SAL_CALL osl_closeDirectory(oslDirectory pDirectory) +{ + SAL_WARN_IF(!pDirectory, "sal.file", "pDirectory is nullptr"); + DirectoryImpl* pDirImpl = static_cast<DirectoryImpl*>(pDirectory); + oslFileError err = osl_File_E_None; + + if (!pDirImpl) + return osl_File_E_INVAL; + +#ifdef ANDROID + if (pDirImpl->eKind == DirectoryImpl::KIND_ASSETS) + { + if (lo_apk_closedir(pDirImpl->pApkDirStruct)) + err = osl_File_E_IO; + } + else +#endif + { + if (closedir( pDirImpl->pDirStruct) != 0) + { + int e = errno; + SAL_INFO("sal.file", "closedir(" << pDirImpl->pDirStruct << "): " << UnixErrnoString(e)); + err = oslTranslateFileError(e); + } + else + SAL_INFO("sal.file", "closedir(" << pDirImpl->pDirStruct << "): OK"); + } + + delete pDirImpl; + + return err; +} + +/********************************************** + * osl_readdir_impl_ + * + * readdir wrapper, filters out "." and ".." + * on request + *********************************************/ + +static struct dirent* osl_readdir_impl_(DIR* pdir) +{ + struct dirent* pdirent; + + while ((pdirent = readdir(pdir)) != nullptr) + { + if ((strcmp(pdirent->d_name, ".") == 0) || (strcmp(pdirent->d_name, "..") == 0)) + continue; + break; + } + + return pdirent; +} + +oslFileError SAL_CALL osl_getNextDirectoryItem(oslDirectory pDirectory, + oslDirectoryItem* pItem, SAL_UNUSED_PARAMETER sal_uInt32 /*uHint*/) +{ + SAL_WARN_IF(!pDirectory, "sal.file", "pDirectory is nullptr"); + SAL_WARN_IF(!pItem, "sal.file", "pItem is nullptr"); + + DirectoryImpl* pDirImpl = static_cast<DirectoryImpl*>(pDirectory); + OString strFileName; + struct dirent* pEntry; + + if ((pDirectory == nullptr) || (pItem == nullptr)) + return osl_File_E_INVAL; + +#ifdef ANDROID + if(pDirImpl->eKind == DirectoryImpl::KIND_ASSETS) + { + pEntry = lo_apk_readdir(pDirImpl->pApkDirStruct); + } + else +#endif + { + pEntry = osl_readdir_impl_(pDirImpl->pDirStruct); + } + + if (!pEntry) + return osl_File_E_NOENT; + + char const * filename = pEntry->d_name; + +#if defined(MACOSX) + // convert decomposed filename to precomposed UTF-8 + char composed_name[BUFSIZ]; + CFMutableStringRef strRef = CFStringCreateMutable(nullptr, 0 ); + CFStringAppendCString(strRef, filename, kCFStringEncodingUTF8); // UTF8 is default on Mac OSX + CFStringNormalize(strRef, kCFStringNormalizationFormC); + CFStringGetCString(strRef, composed_name, BUFSIZ, kCFStringEncodingUTF8); + CFRelease(strRef); + filename = composed_name; +#endif + + strFileName = OString(filename, strlen(filename)); + + auto const strFilePath = osl::systemPathMakeAbsolutePath(pDirImpl->strPath, strFileName); + + DirectoryItem_Impl* pImpl = static_cast< DirectoryItem_Impl* >(*pItem); + if (pImpl) + { + pImpl->release(); + pImpl = nullptr; + } +#ifdef _DIRENT_HAVE_D_TYPE + pImpl = new DirectoryItem_Impl(std::move(strFilePath), pEntry->d_type); +#else + pImpl = new DirectoryItem_Impl(std::move(strFilePath)); +#endif /* _DIRENT_HAVE_D_TYPE */ + *pItem = pImpl; + + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_getDirectoryItem(rtl_uString* ustrFileURL, oslDirectoryItem* pItem) +{ + OString strSystemPath; + oslFileError osl_error = osl_File_E_INVAL; + + if ((!ustrFileURL) || (ustrFileURL->length == 0) || (!pItem)) + return osl_File_E_INVAL; + + osl_error = osl::detail::convertUrlToPathname(OUString::unacquired(&ustrFileURL), &strSystemPath); + if (osl_error != osl_File_E_None) + return osl_error; + + osl_systemPathRemoveSeparator(strSystemPath.pData); + + if (osl::access(strSystemPath, F_OK) == -1) + { + osl_error = oslTranslateFileError(errno); + } + else + { + *pItem = new DirectoryItem_Impl(std::move(strSystemPath)); + } + + return osl_error; +} + +oslFileError SAL_CALL osl_acquireDirectoryItem( oslDirectoryItem Item ) +{ + DirectoryItem_Impl * pImpl = static_cast< DirectoryItem_Impl* >(Item); + if (pImpl == nullptr) + return osl_File_E_INVAL; + + pImpl->acquire(); + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_releaseDirectoryItem( oslDirectoryItem Item ) +{ + DirectoryItem_Impl * pImpl = static_cast< DirectoryItem_Impl* >(Item); + if (pImpl == nullptr) + return osl_File_E_INVAL; + + pImpl->release(); + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_createDirectory( rtl_uString* ustrDirectoryURL ) +{ + return osl_createDirectoryWithFlags( + ustrDirectoryURL, osl_File_OpenFlag_Read | osl_File_OpenFlag_Write); +} + +oslFileError osl_createDirectoryWithFlags( + rtl_uString * ustrDirectoryURL, sal_uInt32 flags) +{ + char path[PATH_MAX]; + oslFileError eRet; + + SAL_WARN_IF((!ustrDirectoryURL) || (ustrDirectoryURL->length == 0), + "sal.file", "Invalid directory URL"); + + /* 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_createDirectory( path, flags ); +} + +oslFileError SAL_CALL osl_removeDirectory( rtl_uString* ustrDirectoryURL ) +{ + char path[PATH_MAX]; + oslFileError eRet; + + SAL_WARN_IF((!ustrDirectoryURL) || (ustrDirectoryURL->length == 0), + "sal.file", "Invalid directory URL"); + + /* 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_removeDirectory( path ); +} + +oslFileError osl_psz_createDirectory(char const * pszPath, sal_uInt32 flags) +{ + int nRet=0; + int mode + = (((flags & osl_File_OpenFlag_Read) == 0 + ? 0 + : ((flags & osl_File_OpenFlag_Private) == 0 + ? S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH + : S_IRUSR | S_IXUSR)) + | ((flags & osl_File_OpenFlag_Write) == 0 + ? 0 + : ((flags & osl_File_OpenFlag_Private) == 0 + ? S_IWUSR | S_IWGRP | S_IWOTH + : S_IWUSR))); + + nRet = mkdir(pszPath,mode); + + if ( nRet < 0 ) + { + nRet=errno; + SAL_INFO("sal.file", "mkdir(" << pszPath << ",0" << std::oct << mode << std::dec << "): " << UnixErrnoString(nRet)); + return oslTranslateFileError(nRet); + } + else + SAL_INFO("sal.file", "mkdir(" << pszPath << ",0" << std::oct << mode << std::dec << "): OK"); + + return osl_File_E_None; +} + +static oslFileError osl_psz_removeDirectory( const char* pszPath ) +{ + int nRet = rmdir(pszPath); + + if ( nRet < 0 ) + { + nRet=errno; + SAL_INFO("sal.file", "rmdir(" << pszPath << "): " << UnixErrnoString(nRet)); + return oslTranslateFileError(nRet); + } + else + SAL_INFO("sal.file", "rmdir(" << pszPath << "): OK"); + + return osl_File_E_None; +} + +static int path_make_parent(char* path) +{ + int i = rtl_str_lastIndexOfChar(path, '/'); + + if (i > 0) + { + *(path + i) = 0; + return i; + } + return 0; +} + +static int create_dir_with_callback( + char* directory_path, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + if (osl::mkdir(directory_path, S_IRWXU | S_IRWXG | S_IRWXO) == 0) + { + if (aDirectoryCreationCallbackFunc) + { + OUString url; + osl::detail::convertPathnameToUrl(directory_path, &url); + aDirectoryCreationCallbackFunc(pData, url.pData); + } + return 0; + } + return errno; +} + +static oslFileError create_dir_recursively_( + char* dir_path, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + OSL_PRECOND((rtl_str_getLength(dir_path) > 0) && ((dir_path + (rtl_str_getLength(dir_path) - 1)) != (dir_path + rtl_str_lastIndexOfChar(dir_path, '/'))), + "Path must not end with a slash"); + + int native_err = create_dir_with_callback( + dir_path, aDirectoryCreationCallbackFunc, pData); + + if (native_err == 0) + return osl_File_E_None; + + if (native_err != ENOENT) + return oslTranslateFileError(native_err); + + // we step back until '/a_dir' at maximum because + // we should get an error unequal ENOENT when + // we try to create 'a_dir' at '/' and would so + // return before + int pos = path_make_parent(dir_path); + + oslFileError osl_error = create_dir_recursively_( + dir_path, aDirectoryCreationCallbackFunc, pData); + + if (osl_error != osl_File_E_None && osl_error != osl_File_E_EXIST) + return osl_error; + + dir_path[pos] = '/'; + + return create_dir_recursively_(dir_path, aDirectoryCreationCallbackFunc, pData); +} + +oslFileError SAL_CALL osl_createDirectoryPath( + rtl_uString* aDirectoryUrl, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + if (aDirectoryUrl == nullptr) + return osl_File_E_INVAL; + + OString sys_path; + oslFileError osl_error = osl::detail::convertUrlToPathname( + OUString::unacquired(&aDirectoryUrl), &sys_path); + + if (osl_error != osl_File_E_None) + return osl_error; + + osl::systemPathRemoveSeparator(sys_path); + + // const_cast because sys_path is a local copy which we want to modify inplace instead of + // copy it into another buffer on the heap again + return create_dir_recursively_(sys_path.pData->buffer, aDirectoryCreationCallbackFunc, pData); +} + +static oslFileError osl_unlinkFile(const char* pszPath); +static oslFileError osl_psz_copyFile(const char* pszPath, const char* pszDestPath, bool preserveMetadata); +static oslFileError osl_psz_moveFile(const char* pszPath, const char* pszDestPath); + +static oslFileError oslDoCopy(const char* pszSourceFileName, const char* pszDestFileName, mode_t nMode, size_t nSourceSize, bool DestFileExists); +static void attemptChangeMetadata(const char* pszFileName, mode_t nMode, time_t nAcTime, time_t nModTime, uid_t nUID, gid_t nGID); +static int oslDoCopyLink(const char* pszSourceFileName, const char* pszDestFileName); +static int oslDoCopyFile(const char* pszSourceFileName, const char* pszDestFileName, size_t nSourceSize, mode_t mode); +static oslFileError oslDoMoveFile(const char* pszPath, const char* pszDestPath); + +oslFileError SAL_CALL osl_moveFile( rtl_uString* ustrFileURL, rtl_uString* ustrDestURL ) +{ + char srcPath[PATH_MAX]; + char destPath[PATH_MAX]; + oslFileError eRet; + + SAL_WARN_IF((!ustrFileURL) || (ustrFileURL->length == 0), "sal.file", "Invalid source file URL"); + SAL_WARN_IF((!ustrDestURL) || (ustrDestURL->length == 0), "sal.file", "Invalid destination file URL"); + + /* convert source url to system path */ + eRet = FileURLToPath( srcPath, PATH_MAX, ustrFileURL ); + if( eRet != osl_File_E_None ) + return eRet; + + /* convert destination url to system path */ + eRet = FileURLToPath( destPath, PATH_MAX, ustrDestURL ); + if( eRet != osl_File_E_None ) + return eRet; + +#ifdef MACOSX + if ( macxp_resolveAlias( srcPath, PATH_MAX ) != 0 || macxp_resolveAlias( destPath, PATH_MAX ) != 0 ) + return oslTranslateFileError( errno ); +#endif/* MACOSX */ + + return oslDoMoveFile( srcPath, destPath ); +} + +oslFileError SAL_CALL osl_replaceFile(rtl_uString* ustrFileURL, rtl_uString* ustrDestURL) +{ + int nGid = -1; + char destPath[PATH_MAX]; + oslFileError eRet = FileURLToPath(destPath, PATH_MAX, ustrDestURL); + if (eRet == osl_File_E_None) + { + struct stat aFileStat; + // coverity[fs_check_call] - unavoidable TOCTOU + int nRet = stat(destPath, &aFileStat); + if (nRet == -1) + { + nRet = errno; + SAL_INFO("sal.file", "stat(" << destPath << "): " << UnixErrnoString(nRet)); + } + else + { + nGid = aFileStat.st_gid; + } + } + + eRet = osl_moveFile(ustrFileURL, ustrDestURL); + + if (eRet == osl_File_E_None && nGid != -1) + { + int nRet = chown(destPath, -1, nGid); + if (nRet == -1) + { + nRet = errno; + SAL_INFO("sal.file", + "chown(" << destPath << "-1, " << nGid << "): " << UnixErrnoString(nRet)); + } + } + + return eRet; +} + +oslFileError SAL_CALL osl_copyFile( rtl_uString* ustrFileURL, rtl_uString* ustrDestURL ) +{ + char srcPath[PATH_MAX]; + char destPath[PATH_MAX]; + oslFileError eRet; + + SAL_WARN_IF((!ustrFileURL) || (ustrFileURL->length == 0), "sal.file", "Invalid source file URL"); + SAL_WARN_IF((!ustrDestURL) || (ustrDestURL->length == 0), "sal.file", "Invalid destination file URL"); + + /* convert source url to system path */ + eRet = FileURLToPath( srcPath, PATH_MAX, ustrFileURL ); + if( eRet != osl_File_E_None ) + return eRet; + + /* convert destination url to system path */ + eRet = FileURLToPath( destPath, PATH_MAX, ustrDestURL ); + if( eRet != osl_File_E_None ) + return eRet; + +#ifdef MACOSX + if ( macxp_resolveAlias( srcPath, PATH_MAX ) != 0 || macxp_resolveAlias( destPath, PATH_MAX ) != 0 ) + return oslTranslateFileError( errno ); +#endif/* MACOSX */ + + return osl_psz_copyFile( srcPath, destPath, false ); +} + +oslFileError SAL_CALL osl_removeFile(rtl_uString* ustrFileURL) +{ + char path[PATH_MAX]; + oslFileError eRet; + + SAL_WARN_IF(!ustrFileURL || ustrFileURL->length == 0, "sal.file", "Invalid file URL"); + + /* 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_unlinkFile(path); +} + +static oslFileError oslDoMoveFile(const char* pszPath, const char* pszDestPath) +{ + oslFileError tErr = osl_psz_moveFile(pszPath,pszDestPath); + if (tErr == osl_File_E_None) + return tErr; + + if (tErr != osl_File_E_XDEV) + return tErr; + + tErr = osl_psz_copyFile(pszPath,pszDestPath, true); + + if (tErr != osl_File_E_None) + { + osl_unlinkFile(pszDestPath); + return tErr; + } + + tErr = osl_unlinkFile(pszPath); + + return tErr; +} + +static oslFileError osl_unlinkFile(const char* pszPath) +{ + int nRet=0; + struct stat aStat; + + nRet = lstat_c(pszPath,&aStat); + if (nRet < 0) + { + nRet=errno; + return oslTranslateFileError(nRet); + } + + if (S_ISDIR(aStat.st_mode)) + return osl_File_E_ISDIR; + + nRet = unlink(pszPath); + if (nRet < 0) + { + nRet=errno; + SAL_INFO("sal.file", "unlink(" << pszPath << "): " << UnixErrnoString(nRet)); + return oslTranslateFileError(nRet); + } + else + SAL_INFO("sal.file", "unlink(" << pszPath << "): OK"); + + return osl_File_E_None; +} + +static oslFileError osl_psz_moveFile(const char* pszPath, const char* pszDestPath) +{ + int nRet = rename(pszPath,pszDestPath); + + if (nRet < 0) + { + nRet=errno; + SAL_INFO("sal.file", "rename(" << pszPath << "," << pszDestPath << "): " << UnixErrnoString(nRet)); + return oslTranslateFileError(nRet); + } + else + SAL_INFO("sal.file", "rename(" << pszPath << "," << pszDestPath << "): OK"); + + return osl_File_E_None; +} + +static oslFileError osl_psz_copyFile( const char* pszPath, const char* pszDestPath, bool preserveMetadata ) +{ + time_t nAcTime=0; + time_t nModTime=0; + uid_t nUID=0; + gid_t nGID=0; + int nRet=0; + mode_t nMode=0; + struct stat aFileStat; + oslFileError tErr=osl_File_E_invalidError; + size_t nSourceSize=0; + bool DestFileExists=true; + + /* mfe: does the source file really exists? */ + nRet = lstat_c(pszPath,&aFileStat); + + if (nRet < 0) + { + nRet=errno; + return oslTranslateFileError(nRet); + } + + /* we do only copy files here */ + if (S_ISDIR(aFileStat.st_mode)) + return osl_File_E_ISDIR; + + nSourceSize = static_cast< size_t >(aFileStat.st_size); + nMode = aFileStat.st_mode; + nAcTime = aFileStat.st_atime; + nModTime = aFileStat.st_mtime; + nUID = aFileStat.st_uid; + nGID = aFileStat.st_gid; + + nRet = stat_c(pszDestPath,&aFileStat); + if (nRet < 0) + { + nRet=errno; + +#ifdef IOS + // Checking for nonexistent files at least in the iCloud cache directory (like + // "/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/helloodt0.odt" fails + // with EPERM, not ENOENT. + if (nRet == EPERM) + DestFileExists=false; +#endif + + if (nRet == ENOENT) + DestFileExists=false; + } + + /* mfe: the destination file must not be a directory! */ + if (nRet == 0 && S_ISDIR(aFileStat.st_mode)) + return osl_File_E_ISDIR; + + /* mfe: file does not exists or is no dir */ + + tErr = oslDoCopy(pszPath, pszDestPath, nMode, nSourceSize, DestFileExists); + + if (tErr != osl_File_E_None) + return tErr; + + if (preserveMetadata) + attemptChangeMetadata(pszDestPath, nMode, nAcTime, nModTime, nUID, nGID); + + return tErr; +} + +static oslFileError oslDoCopy(const char* pszSourceFileName, const char* pszDestFileName, mode_t nMode, size_t nSourceSize, bool DestFileExists) +{ + int nRet=0; + + OString tmpDestFile; + if ( DestFileExists ) + { + //TODO: better pick a temp file name instead of adding .osl-tmp: + // use the destination file to avoid EXDEV /* Cross-device link */ + tmpDestFile = pszDestFileName + OString::Concat(".osl-tmp"); + if (rename(pszDestFileName, tmpDestFile.getStr()) != 0) + { + int e = errno; + SAL_INFO("sal.file", "rename(" << pszDestFileName << ", " << tmpDestFile + << "): " << UnixErrnoString(e)); + if (e == ENOENT) + { + DestFileExists = false; + } + else + { + return osl_File_E_EXIST; // for want of a better error code + } + } + else + { + SAL_INFO("sal.file", "rename(" << pszDestFileName << ", " << tmpDestFile + << "): OK"); + } + } + + /* mfe: should be S_ISREG */ + if ( !S_ISLNK(nMode) ) + { + /* copy SourceFile to DestFile */ + nRet = oslDoCopyFile(pszSourceFileName,pszDestFileName,nSourceSize, nMode); + } + /* mfe: OK redundant at the moment */ + else if ( S_ISLNK(nMode) ) + { + nRet = oslDoCopyLink(pszSourceFileName,pszDestFileName); + } + else + { + /* mfe: what to do here? */ + nRet=ENOSYS; + } + + if ( nRet > 0 && DestFileExists ) + { + if (unlink(pszDestFileName) != 0) + { + int e = errno; + SAL_INFO("sal.file", "unlink(" << pszDestFileName << "): " << UnixErrnoString(e)); + } + else + SAL_INFO("sal.file", "unlink(" << pszDestFileName << "): OK"); + + if (rename(tmpDestFile.getStr(), pszDestFileName) != 0) + { + int e = errno; + SAL_INFO("sal.file", "rename(" << tmpDestFile << ", " << pszDestFileName + << "): " << UnixErrnoString(e)); + } + else + SAL_INFO("sal.file", "rename(" << tmpDestFile << ", " << pszDestFileName << "): OK"); + } + + if ( nRet > 0 ) + { + return oslTranslateFileError(nRet); + } + + if ( DestFileExists ) + { + unlink(tmpDestFile.getStr()); + } + + return osl_File_E_None; +} + +void attemptChangeMetadata( const char* pszFileName, mode_t nMode, time_t nAcTime, time_t nModTime, uid_t nUID, gid_t nGID) +{ + struct utimbuf aTimeBuffer; + +#if !defined AT_FDCWD + if (!S_ISLNK(nMode) && chmod(pszFileName, nMode) < 0) +#else + if ( fchmodat(AT_FDCWD, pszFileName, nMode, AT_SYMLINK_NOFOLLOW) < 0 ) +#endif + { + int e = errno; + SAL_INFO("sal.file", "chmod(" << pszFileName << ",0" << std::oct << nMode << std::dec <<"): " << UnixErrnoString(e)); + } + else + SAL_INFO("sal.file", "chmod(" << pszFileName << ",0" << std::oct << nMode << std::dec <<"): OK"); + + // No way to change utime of a symlink itself: + if (!S_ISLNK(nMode)) + { + aTimeBuffer.actime=nAcTime; + aTimeBuffer.modtime=nModTime; + if ( utime(pszFileName,&aTimeBuffer) < 0 ) + { + int e = errno; + SAL_INFO("sal.file", "utime(" << pszFileName << "): errno " << e); + } + } + + if ( nUID != getuid() ) + { + nUID=getuid(); + } + if ( lchown(pszFileName,nUID,nGID) < 0 ) + { + int e = errno; + SAL_INFO("sal.file", "lchown(" << pszFileName << "): errno " << e); + } + else + SAL_INFO("sal.file", "lchown(" << pszFileName << "): OK"); +} + +static int oslDoCopyLink(const char* pszSourceFileName, const char* pszDestFileName) +{ + int nRet=0; + + /* mfe: if dest file is symbolic link remove the link and place the file instead (hro says so) */ + /* mfe: if source is a link copy the link and not the file it points to (hro says so) */ + char pszLinkContent[PATH_MAX+1]; + + pszLinkContent[0] = '\0'; + + nRet = readlink(pszSourceFileName,pszLinkContent,PATH_MAX); + + if ( nRet < 0 ) + { + nRet=errno; + return nRet; + } + + pszLinkContent[ nRet ] = 0; + + nRet = symlink(pszLinkContent,pszDestFileName); + + if ( nRet < 0 ) + { + nRet=errno; + return nRet; + } + + return 0; +} + +static int oslDoCopyFile(const char* pszSourceFileName, const char* pszDestFileName, size_t nSourceSize, mode_t mode) +{ + oslFileHandle SourceFileFH=nullptr; + int DestFileFD=0; + int nRet=0; + + if (openFilePath(pszSourceFileName, + &SourceFileFH, + osl_File_OpenFlag_Read|osl_File_OpenFlag_NoLock|osl_File_OpenFlag_NoExcl, mode_t(-1)) != osl_File_E_None) + { + // Let's hope errno is still set relevantly after openFilePath... + nRet=errno; + return nRet; + } + + DestFileFD=open(pszDestFileName, O_WRONLY | O_CREAT, mode); + + if ( DestFileFD < 0 ) + { + nRet=errno; + SAL_INFO("sal.file", "open(" << pszDestFileName << ",O_WRONLY|O_CREAT,0" << std::oct << mode << std::dec << "): " << UnixErrnoString(nRet)); + osl_closeFile(SourceFileFH); + return nRet; + } + else + SAL_INFO("sal.file", "open(" << pszDestFileName << ",O_WRONLY|O_CREAT,0" << std::oct << mode << std::dec << "): OK"); + + size_t nRemains = nSourceSize; + + if ( nRemains ) + { + /* mmap has problems, try the direct streaming */ + char pBuffer[0x7FFF]; + + do + { + size_t nToRead = std::min( sizeof(pBuffer), nRemains ); + sal_uInt64 nRead; + bool succeeded; + if ( osl_readFile( SourceFileFH, pBuffer, nToRead, &nRead ) != osl_File_E_None || nRead > nToRead || nRead == 0 ) + break; + + succeeded = safeWrite( DestFileFD, pBuffer, nRead ); + if ( !succeeded ) + break; + + // We know nRead <= nToRead, so it must fit in a size_t + nRemains -= static_cast<size_t>(nRead); + } + while( nRemains ); + } + + if ( nRemains ) + { + if ( errno ) + nRet = errno; + else + nRet = ENOSPC; + } + + osl_closeFile( SourceFileFH ); + if ( close( DestFileFD ) == -1 ) + { + int e = errno; + SAL_INFO("sal.file", "close(" << DestFileFD << "): " << UnixErrnoString(e)); + if ( nRet == 0 ) + nRet = e; + } + else + SAL_INFO("sal.file", "close(" << DestFileFD << "): OK"); + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_path_helper.cxx b/sal/osl/unx/file_path_helper.cxx new file mode 100644 index 000000000..a8ee5238f --- /dev/null +++ b/sal/osl/unx/file_path_helper.cxx @@ -0,0 +1,262 @@ +/* -*- 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 <cassert> +#include <utility> + +#include "file_path_helper.hxx" +#include "uunxapi.hxx" + +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +const sal_Unicode FPH_CHAR_PATH_SEPARATOR = '/'; +const sal_Unicode FPH_CHAR_DOT = '.'; +const sal_Unicode FPH_CHAR_COLON = ':'; + +void osl_systemPathRemoveSeparator(rtl_String* pstrPath) +{ + OSL_PRECOND(nullptr != pstrPath, "osl_systemPathRemoveSeparator: Invalid parameter"); + if (pstrPath == nullptr) + return; + + // maybe there are more than one separator at end + // so we run in a loop + while ((pstrPath->length > 1) && (pstrPath->buffer[pstrPath->length - 1] == FPH_CHAR_PATH_SEPARATOR)) + { + pstrPath->length--; + pstrPath->buffer[pstrPath->length] = '\0'; + } + + SAL_WARN_IF( !((0 == pstrPath->length) || (1 == pstrPath->length) || + (pstrPath->length > 1 && pstrPath->buffer[pstrPath->length - 1] != FPH_CHAR_PATH_SEPARATOR)), + "sal.osl", + "osl_systemPathRemoveSeparator: Post condition failed"); +} + +namespace { + +template<typename T> void systemPathEnsureSeparator(T* ppstrPath) +{ + assert(nullptr != ppstrPath); + sal_Int32 lp = ppstrPath->getLength(); + sal_Int32 i = ppstrPath->lastIndexOf(FPH_CHAR_PATH_SEPARATOR); + + if ((lp > 1 && i != (lp - 1)) || ((lp < 2) && i < 0)) + { + *ppstrPath += "/"; + } + + SAL_WARN_IF( !ppstrPath->endsWith("/"), + "sal.osl", + "systemPathEnsureSeparator: Post condition failed"); +} + +} + +bool osl_systemPathIsRelativePath(const rtl_uString* pustrPath) +{ + OSL_PRECOND(nullptr != pustrPath, "osl_systemPathIsRelativePath: Invalid parameter"); + return ((pustrPath == nullptr) || (pustrPath->length == 0) || (pustrPath->buffer[0] != FPH_CHAR_PATH_SEPARATOR)); +} + +namespace { + +template<typename T> T systemPathMakeAbsolutePath_( + const T& BasePath, + const T& RelPath) +{ + T base(BasePath); + + if (!base.isEmpty()) + systemPathEnsureSeparator(&base); + + return base + RelPath; +} + +} + +OString osl::systemPathMakeAbsolutePath( + const OString& BasePath, + const OString& RelPath) +{ + return systemPathMakeAbsolutePath_(BasePath, RelPath); +} + +OUString osl::systemPathMakeAbsolutePath( + const OUString& BasePath, + const OUString& RelPath) +{ + return systemPathMakeAbsolutePath_(BasePath, RelPath); +} + +void osl_systemPathGetFileNameOrLastDirectoryPart( + const rtl_String* pstrPath, + rtl_String** ppstrFileNameOrLastDirPart) +{ + OSL_PRECOND(pstrPath && ppstrFileNameOrLastDirPart, + "osl_systemPathGetFileNameOrLastDirectoryPart: Invalid parameter"); + + OString path(const_cast<rtl_String*>(pstrPath)); + + osl_systemPathRemoveSeparator(path.pData); + + OString last_part; + + if (path.getLength() > 1 || (path.getLength() == 1 && path[0] != FPH_CHAR_PATH_SEPARATOR)) + { + sal_Int32 idx_ps = path.lastIndexOf(FPH_CHAR_PATH_SEPARATOR); + idx_ps++; // always right to increment by one even if idx_ps == -1! + last_part = path.copy(idx_ps); + } + rtl_string_assign(ppstrFileNameOrLastDirPart, last_part.pData); +} + +bool osl_systemPathIsHiddenFileOrDirectoryEntry( + const rtl_String* pstrPath) +{ + OSL_PRECOND(nullptr != pstrPath, "osl_systemPathIsHiddenFileOrDirectoryEntry: Invalid parameter"); + if ((pstrPath == nullptr) || (pstrPath->length == 0)) + return false; + + OString fdp; + osl_systemPathGetFileNameOrLastDirectoryPart(pstrPath, &fdp.pData); + + return ((fdp.pData->length > 0) && + (fdp.pData->buffer[0] == FPH_CHAR_DOT) && + !osl_systemPathIsLocalOrParentDirectoryEntry(fdp.pData)); +} + +bool osl_systemPathIsLocalOrParentDirectoryEntry( + const rtl_String* pstrPath) +{ + OSL_PRECOND(pstrPath, "osl_systemPathIsLocalOrParentDirectoryEntry: Invalid parameter"); + + OString dirent; + + osl_systemPathGetFileNameOrLastDirectoryPart(pstrPath, &dirent.pData); + + return (dirent == "." || + dirent == ".."); +} + +namespace { + +/** Simple iterator for a path list separated by the specified character +*/ +class path_list_iterator +{ +public: + + /* after construction get_current_item + returns the first path in list, no need + to call reset first + */ + path_list_iterator(OUString path_list, sal_Unicode list_separator = FPH_CHAR_COLON) : + m_path_list(std::move(path_list)), + m_end(m_path_list.getStr() + m_path_list.getLength() + 1), + m_separator(list_separator) + { + reset(); + } + + path_list_iterator(const path_list_iterator&) = delete; + path_list_iterator& operator=(const path_list_iterator&) = delete; + + void reset() + { + m_path_segment_begin = m_path_segment_end = m_path_list.getStr(); + advance(); + } + + void next() + { + OSL_PRECOND(!done(), "path_list_iterator: Already done!"); + + m_path_segment_begin = ++m_path_segment_end; + advance(); + } + + bool done() const + { + return (m_path_segment_end >= m_end); + } + + OUString get_current_item() const + { + return OUString( + m_path_segment_begin, + (m_path_segment_end - m_path_segment_begin)); + } + +private: + /* move m_path_end to the next separator or + to the end of the string + */ + void advance() + { + while (!done() && *m_path_segment_end && (*m_path_segment_end != m_separator)) + ++m_path_segment_end; + + OSL_ASSERT(m_path_segment_end <= m_end); + } + +private: + OUString m_path_list; + const sal_Unicode* m_end; + const sal_Unicode m_separator; + const sal_Unicode* m_path_segment_begin; + const sal_Unicode* m_path_segment_end; +}; + +} + +bool osl_searchPath( + const rtl_uString* pustrFilePath, + const rtl_uString* pustrSearchPathList, + rtl_uString** ppustrPathFound) +{ + OSL_PRECOND(pustrFilePath && pustrSearchPathList && ppustrPathFound, "osl_searchPath: Invalid parameter"); + + bool bfound = false; + OUString fp(const_cast<rtl_uString*>(pustrFilePath)); + OUString pl(const_cast<rtl_uString*>(pustrSearchPathList)); + path_list_iterator pli(pl); + + while (!pli.done()) + { + OUString p = pli.get_current_item(); + systemPathEnsureSeparator(&p); + p += fp; + + if (osl::access(osl::OUStringToOString(p), F_OK) > -1) + { + bfound = true; + rtl_uString_assign(ppustrPathFound, p.pData); + break; + } + pli.next(); + } + return bfound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_path_helper.hxx b/sal/osl/unx/file_path_helper.hxx new file mode 100644 index 000000000..e210b27a3 --- /dev/null +++ b/sal/osl/unx/file_path_helper.hxx @@ -0,0 +1,251 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_FILE_PATH_HELPER_HXX +#define INCLUDED_SAL_OSL_UNX_FILE_PATH_HELPER_HXX + +#include <sal/types.h> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> + +/** + Removes the last separator from the given system path if any and if the path + is not the root path '/' + + @param ppstrPath[inout] a system path if the path is not the root path + and the last character is a path separator it + will be cut off ppstrPath must not be NULL and + must point to a valid rtl_String + + @returns nothing + +*/ +void osl_systemPathRemoveSeparator(rtl_String* pstrPath); + +/** + Returns true if the given path is a relative path and so starts not with '/' + + @param pustrPath [in] a system path - must not be NULL + + @retval sal_True the given path doesn't start with a separator + @retval sal_False the given path starts with a separator + +*/ +bool osl_systemPathIsRelativePath( + const rtl_uString* pustrPath); + +/** + Returns the file or the directory part of the given path + + @param pstrPath [in] a system path, must not be NULL + + @param ppstrFileOrDirPart [out] on return receives the last part of the + given directory or the file name if pstrPath is the + root path '/' an empty string will be returned if + pstrPath has a trailing '/' the last part before the + '/' will be returned else the part after the last '/' + will be returned + + @returns nothing + +*/ +void osl_systemPathGetFileNameOrLastDirectoryPart( + const rtl_String* pstrPath, + rtl_String** ppstrFileNameOrLastDirPart); + +/** + @param pustrPath [in] a system path, must not be NULL + + @retval sal_True the last part of the given system path starts with '.' + @retval sal_False the last part of the given system path is '.' or '..' + alone or doesn't start with a dot + +*/ +bool osl_systemPathIsHiddenFileOrDirectoryEntry( + const rtl_String* pustrPath); + +/************************************************ + osl_systemPathIsLocalOrParentDirectoryEntry + Returns sal_True if the last part of the given + system path is the local directory entry '.' + or the parent directory entry '..' + + @param pstrPath [in] a system path, + must not be NULL + + @returns sal_True if the last part of the + given system path is '.' or '..' + else sal_False + +************************************************/ + +bool osl_systemPathIsLocalOrParentDirectoryEntry( + const rtl_String* pstrPath); + +/************************************************ + osl_searchPath + Searches for a file name or path name in all + directories specified by a given path list. + Symbolic links in the resulting path will not be + resolved, it's up to the caller to do this. + + @param pustrFilePath [in] a file name or + directory name to search for, the name must + be provided as system path not as a file URL + + @param pustrSearchPathList [in] a ':' + separated list of paths in which to search for + the file or directory name + + @param ppustrPathFound [out] on success receives the + complete path of the file or directory found + as a system path + + @returns sal_True if the specified file or + directory was found else sal_False + ***********************************************/ + +bool osl_searchPath( + const rtl_uString* pustrFilePath, + const rtl_uString* pustrSearchPathList, + rtl_uString** ppustrPathFound); + +namespace osl +{ + + /******************************************* + systemPathRemoveSeparator + Removes the last separator from the + given system path if any and if the path + is not the root path '/' + + @param ppustrPath [inout] a system path + if the path is not the root path + and the last character is a + path separator it will be cut off + ppustrPath must not be NULL and + must point to a valid rtl_uString + + @returns nothing + + ******************************************/ + + inline void systemPathRemoveSeparator(/*inout*/ OString& Path) + { + osl_systemPathRemoveSeparator(Path.pData); + } + + /******************************************* + systemPathIsRelativePath + Returns true if the given path is a + relative path and so starts not with '/' + + @param pustrPath [in] a system path + pustrPath must not be NULL + + @returns sal_True if the given path + doesn't start with a separator + else sal_False will be returned + + ******************************************/ + + inline bool systemPathIsRelativePath(const OUString& Path) + { + return osl_systemPathIsRelativePath(Path.pData); + } + + /****************************************** + systemPathMakeAbsolutePath + Append a relative path to a base path + + @param BasePath [in] a system + path that will be considered as + base path + + @param RelPath [in] a system path + that will be considered as + relative path + + @return the + resulting path which is a + concatenation of the base and + the relative path + if base path is empty the + resulting absolute path is the + relative path + if relative path is empty the + resulting absolute path is the + base path + if base and relative path are + empty the resulting absolute + path is also empty + + *****************************************/ + + OString systemPathMakeAbsolutePath( + const OString& BasePath, + const OString& RelPath); + + OUString systemPathMakeAbsolutePath( + const OUString& BasePath, + const OUString& RelPath); + + /******************************************** + systemPathIsHiddenFileOrDirectoryEntry + Returns sal_True if the last part of + given system path is not '.' or '..' + alone and starts with a '.' + + @param pustrPath [in] a system path, + must not be NULL + + @returns sal_True if the last part of + the given system path starts + with '.' or sal_False the last + part is '.' or '..' alone or + doesn't start with a dot + + *********************************************/ + + inline bool systemPathIsHiddenFileOrDirectoryEntry( + const OString& Path) + { + return osl_systemPathIsHiddenFileOrDirectoryEntry(Path.pData); + } + + /************************************************ + searchPath + ***********************************************/ + + inline bool searchPath( + const OUString& ustrFilePath, + const OUString& ustrSearchPathList, + OUString& ustrPathFound) + { + return osl_searchPath( + ustrFilePath.pData, + ustrSearchPathList.pData, + &ustrPathFound.pData); + } + + } // namespace osl + + #endif /* #ifndef _OSL_PATH_HELPER_HXX_ */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_stat.cxx b/sal/osl/unx/file_stat.cxx new file mode 100644 index 000000000..3b702905c --- /dev/null +++ b/sal/osl/unx/file_stat.cxx @@ -0,0 +1,453 @@ +/* -*- 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 "system.hxx" +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> + +#include <osl/diagnose.h> +#include <osl/thread.h> + +#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 (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 + + 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: */ diff --git a/sal/osl/unx/file_url.cxx b/sal/osl/unx/file_url.cxx new file mode 100644 index 000000000..4b5a16885 --- /dev/null +++ b/sal/osl/unx/file_url.cxx @@ -0,0 +1,972 @@ +/* -*- 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 "file_url.hxx" + +#include "system.hxx" + +#include <algorithm> +#include <cassert> +#include <cstring> +#include <stdexcept> +#include <string_view> +#include <type_traits> +#include <limits.h> +#include <errno.h> +#include <strings.h> +#include <unistd.h> + +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <osl/socket.h> +#include <oslsocket.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/process.h> + +#include <rtl/character.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/uri.h> +#include <rtl/uri.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/textcvt.h> +#include <sal/log.hxx> + +#include <uri_internal.hxx> + +#include "file_error_transl.hxx" +#include "file_path_helper.hxx" + +#include "uunxapi.hxx" + +/** @file + + General note + + This file contains the part that handles File URLs. + + File URLs as scheme specific notion of URIs + (RFC2396) may be handled platform independent, but + will not in osl which is considered wrong. + Future version of osl should handle File URLs this + way. In rtl/uri there is already a URI parser etc. + so this code should be consolidated. + +*/ + +using namespace osl; + +namespace { + +// A slightly modified version of Pchar in rtl/source/uri.c, but without +// encoding slashes: +constexpr auto uriCharClass = rtl::createUriCharClass( + u8"!$&'()*+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"); + +} + +oslFileError SAL_CALL osl_getCanonicalName( rtl_uString* ustrFileURL, rtl_uString** pustrValidURL ) +{ + OSL_FAIL("osl_getCanonicalName not implemented"); + + rtl_uString_newFromString(pustrValidURL, ustrFileURL); + return osl_File_E_None; +} + +namespace { + + class UnicodeToTextConverter_Impl + { + rtl_UnicodeToTextConverter m_converter; + + UnicodeToTextConverter_Impl() + : m_converter (rtl_createUnicodeToTextConverter (osl_getThreadTextEncoding())) + {} + + ~UnicodeToTextConverter_Impl() + { + rtl_destroyUnicodeToTextConverter (m_converter); + } + public: + static UnicodeToTextConverter_Impl & getInstance() + { + static UnicodeToTextConverter_Impl g_theConverter; + return g_theConverter; + } + + sal_Size convert( + sal_Unicode const * pSrcBuf, sal_Size nSrcChars, char * pDstBuf, sal_Size nDstBytes, + sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtChars) + { + OSL_ASSERT(m_converter != nullptr); + return rtl_convertUnicodeToText ( + m_converter, nullptr, pSrcBuf, nSrcChars, pDstBuf, nDstBytes, nFlags, pInfo, pSrcCvtChars); + } + }; + +bool convert(OUStringBuffer const & in, OStringBuffer * append) { + assert(append != nullptr); + for (sal_Size nConvert = in.getLength();;) { + auto const oldLen = append->getLength(); + auto n = std::min( + std::max(nConvert, sal_Size(PATH_MAX)), + sal_Size(std::numeric_limits<sal_Int32>::max() - oldLen)); + // approximation of required converted size + auto s = append->appendUninitialized(n); + sal_uInt32 info; + sal_Size converted; + //TODO: context, for reliable treatment of DESTBUFFERTOSMALL: + n = UnicodeToTextConverter_Impl::getInstance().convert( + in.getStr() + in.getLength() - nConvert, nConvert, s, n, + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR + | RTL_UNICODETOTEXT_FLAGS_FLUSH), + &info, &converted); + if ((info & RTL_UNICODETOTEXT_INFO_ERROR) != 0) { + return false; + } + append->setLength(oldLen + n); + assert(converted <= nConvert); + nConvert -= converted; + assert((nConvert == 0) == ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0)); + if ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0) { + break; + } + } + return true; +} + +bool decodeFromUtf8(std::u16string_view text, OString * result) { + assert(result != nullptr); + auto p = text.data(); + auto const end = p + text.size(); + OUStringBuffer ubuf(static_cast<int>(text.size())); + OStringBuffer bbuf(PATH_MAX); + while (p < end) { + rtl::uri::detail::EscapeType t; + sal_uInt32 c = rtl::uri::detail::readUcs4(&p, end, true, RTL_TEXTENCODING_UTF8, &t); + switch (t) { + case rtl::uri::detail::EscapeNo: + if (c == '%') { + return false; + } + [[fallthrough]]; + case rtl::uri::detail::EscapeChar: + if (rtl::isSurrogate(c)) { + return false; + } + ubuf.appendUtf32(c); + break; + case rtl::uri::detail::EscapeOctet: + if (!convert(ubuf, &bbuf)) { + return false; + } + ubuf.setLength(0); + assert(c <= 0xFF); + bbuf.append(char(c)); + break; + } + } + if (!convert(ubuf, &bbuf)) { + return false; + } + *result = bbuf.makeStringAndClear(); + return true; +} + +template<typename T> oslFileError getSystemPathFromFileUrl( + OUString const & url, T * path, bool resolveHome) +{ + assert(path != nullptr); + // For compatibility with assumptions in other parts of the code base, + // assume that anything starting with a slash is a system path instead of a + // (relative) file URL (except if it starts with two slashes, in which case + // it is a relative URL with an authority component): + if (url.isEmpty() + || (url[0] == '/' && (url.getLength() == 1 || url[1] != '/'))) + { + return osl_File_E_INVAL; + } + // Check for non file scheme: + sal_Int32 i = 0; + if (rtl::isAsciiAlpha(url[0])) { + for (sal_Int32 j = 1; j != url.getLength(); ++j) { + auto c = url[j]; + if (c == ':') { + if (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths( + url.pData->buffer, j, + RTL_CONSTASCII_STRINGPARAM("file")) + != 0) + { + return osl_File_E_INVAL; + } + i = j + 1; + break; + } + if (!rtl::isAsciiAlphanumeric(c) && c != '+' && c != '-' + && c != '.') + { + break; + } + } + } + // Handle query or fragment: + if (url.indexOf('?', i) != -1 || url.indexOf('#', i) != -1) + return osl_File_E_INVAL; + // Handle authority, supporting a host of "localhost", "127.0.0.1", or the exact value (e.g., + // not supporting an additional final dot, for simplicity) reported by osl_getLocalHostnameFQDN + // (and, in each case, ignoring case of ASCII letters): + if (url.getLength() - i >= 2 && url[i] == '/' && url[i + 1] == '/') + { + i += 2; + sal_Int32 j = url.indexOf('/', i); + if (j == -1) + j = url.getLength(); + if (j != i + && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths( + url.pData->buffer + i, j - i, + RTL_CONSTASCII_STRINGPARAM("localhost")) + != 0) + && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths( + url.pData->buffer + i, j - i, + RTL_CONSTASCII_STRINGPARAM("127.0.0.1")) + != 0)) + { + OUString hostname; + // The 'file' URI Scheme does imply that we want a FQDN in this case + // See https://tools.ietf.org/html/rfc8089#section-3 + if (osl_getLocalHostnameFQDN(&hostname.pData) != osl_Socket_Ok + || (rtl_ustr_compareIgnoreAsciiCase_WithLength( + url.pData->buffer + i, j - i, hostname.getStr(), hostname.getLength()) + != 0)) + { + return osl_File_E_INVAL; + } + } + i = j; + } + // Handle empty path: + if (i == url.getLength()) + { + *path = "/"; + return osl_File_E_None; + } + // Path must not contain %2F: + if (url.indexOf("%2F", i) != -1 || url.indexOf("%2f", i) != -1) + return osl_File_E_INVAL; + + if constexpr (std::is_same_v<T, rtl::OString>) { + if (!decodeFromUtf8(url.subView(i), path)) { + return osl_File_E_INVAL; + } + } else if constexpr (std::is_same_v<T, rtl::OUString>) { + *path = rtl::Uri::decode( + url.copy(i), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8); + } else { + static_assert(std::is_same_v<T, rtl::OString> || std::is_same_v<T, rtl::OUString>); + } + // Path must not contain %2F: + if (path->indexOf('\0') != -1) + return osl_File_E_INVAL; + + // Handle ~ notation: + if (resolveHome && path->getLength() >= 2 && (*path)[1] == '~') + { + sal_Int32 j = path->indexOf('/', 2); + if (j == -1) + j = path->getLength(); + + if (j == 2) + { + OUString home; + if (!osl::Security().getHomeDir(home)) + { + SAL_WARN("sal.file", "osl::Security::getHomeDir failed"); + return osl_File_E_INVAL; + } + + i = url.indexOf('/', i + 1); + + if (i == -1) + i = url.getLength(); + else + ++i; + + //TODO: cheesy way of ensuring home's path ends in slash: + if (!home.isEmpty() && home[home.getLength() - 1] != '/') + home += "/"; + try + { + home = rtl::Uri::convertRelToAbs(home, url.copy(i)); + } + catch (rtl::MalformedUriException & e) + { + SAL_WARN("sal.file", "rtl::MalformedUriException " << e.getMessage()); + return osl_File_E_INVAL; + } + return getSystemPathFromFileUrl(home, path, false); + } + // FIXME: replace ~user with user's home directory + return osl_File_E_INVAL; + } + return osl_File_E_None; +} + +} + +oslFileError SAL_CALL osl_getSystemPathFromFileURL( rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath ) +{ + OUString path; + oslFileError e; + try + { + e = getSystemPathFromFileUrl( + OUString::unacquired(&ustrFileURL), &path, true); + } + catch (std::length_error &) + { + e = osl_File_E_RANGE; + } + + if (e == osl_File_E_None) + rtl_uString_assign(pustrSystemPath, path.pData); + + return e; +} + +oslFileError SAL_CALL osl_getFileURLFromSystemPath( rtl_uString *ustrSystemPath, rtl_uString **pustrFileURL ) +{ + rtl_uString *pTmp = nullptr; + sal_Int32 nIndex; + + auto const & systemPath = OUString::unacquired(&ustrSystemPath); + + if( systemPath.isEmpty() ) + return osl_File_E_INVAL; + + if( systemPath.startsWith( "file:" ) ) + return osl_File_E_INVAL; + + /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */ + if( systemPath.startsWith("~") ) + { + /* check if another user is specified */ + if( ( systemPath.getLength() == 1 ) || + ( systemPath[1] == '/' ) ) + { + /* osl_getHomeDir returns file URL */ + oslSecurity pSecurity = osl_getCurrentSecurity(); + osl_getHomeDir( pSecurity , &pTmp ); + osl_freeSecurityHandle( pSecurity ); + + if (!pTmp) + return osl_File_E_INVAL; + + /* remove "file://" prefix */ + rtl_uString_newFromStr_WithLength( &pTmp, pTmp->buffer + 7, pTmp->length - 7 ); + + /* replace '~' in original string */ + rtl_uString_newReplaceStrAt( &pTmp, systemPath.pData, 0, 1, pTmp ); + } + else + { + /* FIXME: replace ~user with users home directory */ + return osl_File_E_INVAL; + } + } + + /* check if initial string contains repeated '/' characters */ + nIndex = systemPath.indexOf( "//" ); + if( nIndex != -1 ) + { + sal_Int32 nSrcIndex; + sal_Int32 nDeleted = 0; + + /* if pTmp is not already allocated, copy systemPath for modification */ + if( pTmp == nullptr ) + rtl_uString_newFromString( &pTmp, systemPath.pData ); + + /* adapt index to pTmp */ + nIndex += pTmp->length - systemPath.getLength(); + + /* replace repeated '/' characters with a single '/' */ + for( nSrcIndex = nIndex + 1; nSrcIndex < pTmp->length; nSrcIndex++ ) + { + if( (pTmp->buffer[nSrcIndex] == '/') && (pTmp->buffer[nIndex] == '/') ) + nDeleted++; + else + pTmp->buffer[++nIndex] = pTmp->buffer[nSrcIndex]; + } + + /* adjust length member */ + pTmp->length -= nDeleted; + } + + if( pTmp == nullptr ) + rtl_uString_assign( &pTmp, systemPath.pData ); + + /* file URLs must be URI encoded */ + rtl_uriEncode( pTmp, uriCharClass.data(), rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8, pustrFileURL ); + + rtl_uString_release( pTmp ); + + /* absolute urls should start with 'file://' */ + if( (*pustrFileURL)->buffer[0] == '/' ) + { + rtl_uString *pProtocol = nullptr; + + rtl_uString_newFromAscii( &pProtocol, "file://" ); + rtl_uString_newConcat( pustrFileURL, pProtocol, *pustrFileURL ); + rtl_uString_release( pProtocol ); + } + + return osl_File_E_None; +} + +/* + * relative URLs are not accepted + */ +oslFileError getSystemPathFromFileURL_Ex( + rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath) +{ + rtl_uString* temp = nullptr; + oslFileError osl_error = osl_getSystemPathFromFileURL(ustrFileURL, &temp); + + if (osl_error == osl_File_E_None) + { + if (temp->buffer[0] == '/') + { + *pustrSystemPath = temp; + } + else + { + rtl_uString_release(temp); + osl_error = osl_File_E_INVAL; + } + } + + return osl_error; +} + +namespace +{ + + /** Helper function, return a pointer to the final '\0' + of a string + */ + + sal_Unicode* ustrtoend(sal_Unicode* pStr) + { + return (pStr + rtl_ustr_getLength(pStr)); + } + + sal_Unicode* ustrchrcat(const sal_Unicode chr, sal_Unicode* d) + { + sal_Unicode* p = ustrtoend(d); + *p++ = chr; + *p = 0; + return d; + } + + bool _islastchr(sal_Unicode* pStr, sal_Unicode Chr) + { + sal_Unicode* p = ustrtoend(pStr); + if (p > pStr) + p--; + return (*p == Chr); + } + + /** + Remove the last part of a path, a path that has + only a '/' or no '/' at all will be returned + unmodified + */ + + sal_Unicode* _rmlastpathtoken(sal_Unicode* aPath) + { + /* we may always skip -2 because we + may at least stand on a '/' but + either there is no other character + before this '/' or it's another + character than the '/' + */ + sal_Unicode* p = ustrtoend(aPath) - 2; + + /* move back to the next path separator + or to the start of the string */ + while ((p > aPath) && (*p != '/')) + p--; + + if (p >= aPath) + { + if (*p == '/') + { + p++; + *p = '\0'; + } + else + { + *p = '\0'; + } + } + + return aPath; + } + + oslFileError _osl_resolvepath( + /*inout*/ sal_Unicode* path, + /*inout*/ bool* failed) + { + oslFileError ferr = osl_File_E_None; + + if (!*failed) + { + char unresolved_path[PATH_MAX]; + if (!UnicodeToText(unresolved_path, sizeof(unresolved_path), path, rtl_ustr_getLength(path))) + return oslTranslateFileError(ENAMETOOLONG); + + char resolved_path[PATH_MAX]; + if (realpath(unresolved_path, resolved_path)) + { + if (!TextToUnicode(resolved_path, strlen(resolved_path), path, PATH_MAX)) + return oslTranslateFileError(ENAMETOOLONG); + } + else + { + if (EACCES == errno || ENOTDIR == errno || ENOENT == errno) + *failed = true; + else + ferr = oslTranslateFileError(errno); + } + } + + return ferr; + } + + /** + Works even with non existing paths. The resulting path must not exceed + PATH_MAX else osl_File_E_NAMETOOLONG is the result + */ + + oslFileError osl_getAbsoluteFileURL_impl_(const OUString& unresolved_path, OUString& resolved_path) + { + /* the given unresolved path must not exceed PATH_MAX */ + if (unresolved_path.getLength() >= (PATH_MAX - 2)) + return oslTranslateFileError(ENAMETOOLONG); + + sal_Unicode path_resolved_so_far[PATH_MAX]; + const sal_Unicode* punresolved = unresolved_path.getStr(); + sal_Unicode* presolvedsf = path_resolved_so_far; + + /* reserve space for leading '/' and trailing '\0' + do not exceed this limit */ + sal_Unicode* sentinel = path_resolved_so_far + PATH_MAX - 2; + + /* if realpath fails with error ENOTDIR, EACCES or ENOENT + we will not call it again, because _osl_realpath should also + work with non existing directories etc. */ + bool realpath_failed = false; + oslFileError ferr; + + path_resolved_so_far[0] = '\0'; + + while (*punresolved != '\0') + { + /* ignore '/.' , skip one part back when '/..' */ + if ((*punresolved == '.') && (*presolvedsf == '/')) + { + if (*(punresolved + 1) == '\0') + { + punresolved++; + continue; + } + if (*(punresolved + 1) == '/') + { + punresolved += 2; + continue; + } + if ((*(punresolved + 1) == '.') && (*(punresolved + 2) == '\0' || (*(punresolved + 2) == '/'))) + { + _rmlastpathtoken(path_resolved_so_far); + + presolvedsf = ustrtoend(path_resolved_so_far) - 1; + + if (*(punresolved + 2) == '/') + punresolved += 3; + else + punresolved += 2; + + continue; + } + + /* a file or directory name may start with '.' */ + if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel) + return oslTranslateFileError(ENAMETOOLONG); + + ustrchrcat(*punresolved++, path_resolved_so_far); + + if (*punresolved == '\0' && !realpath_failed) + { + ferr = _osl_resolvepath( + path_resolved_so_far, + &realpath_failed); + + if (ferr != osl_File_E_None) + return ferr; + } + } + else if (*punresolved == '/') + { + if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel) + return oslTranslateFileError(ENAMETOOLONG); + + ustrchrcat(*punresolved++, path_resolved_so_far); + + if (!realpath_failed) + { + ferr = _osl_resolvepath( + path_resolved_so_far, + &realpath_failed); + + if (ferr != osl_File_E_None) + return ferr; + + if (!_islastchr(path_resolved_so_far, '/')) + { + if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel) + return oslTranslateFileError(ENAMETOOLONG); + + ustrchrcat('/', path_resolved_so_far); + } + } + } + else // any other character + { + if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel) + return oslTranslateFileError(ENAMETOOLONG); + + ustrchrcat(*punresolved++, path_resolved_so_far); + + if (*punresolved == '\0' && !realpath_failed) + { + ferr = _osl_resolvepath( + path_resolved_so_far, + &realpath_failed); + + if (ferr != osl_File_E_None) + return ferr; + } + } + } + + sal_Int32 len = rtl_ustr_getLength(path_resolved_so_far); + + OSL_ASSERT(len < PATH_MAX); + + resolved_path = OUString(path_resolved_so_far, len); + + return osl_File_E_None; + } + +} + +oslFileError osl_getAbsoluteFileURL( + rtl_uString* ustrBaseDirURL, + rtl_uString* ustrRelativeURL, + rtl_uString** pustrAbsoluteURL) +{ + /* Work around the below call to getSystemPathFromFileURL rejecting input + that starts with "/" (for whatever reason it behaves that way; but + changing that would start to break lots of tests at least) */ + OUString relUrl(ustrRelativeURL); + if (relUrl.startsWith("//")) + relUrl = "file:" + relUrl; + else if (relUrl.startsWith("/")) + relUrl = "file://" + relUrl; + + OUString unresolved_path; + + FileBase::RC frc = FileBase::getSystemPathFromFileURL(relUrl, unresolved_path); + if (frc != FileBase::E_None) + return oslFileError(frc); + + if (systemPathIsRelativePath(unresolved_path)) + { + OUString base_path; + oslFileError rc = getSystemPathFromFileURL_Ex(ustrBaseDirURL, &base_path.pData); + if (rc != osl_File_E_None) + return rc; + + unresolved_path = systemPathMakeAbsolutePath(base_path, unresolved_path); + } + + OUString resolved_path; + oslFileError rc = osl_getAbsoluteFileURL_impl_(unresolved_path, resolved_path); + if (rc == osl_File_E_None) + { + rc = osl_getFileURLFromSystemPath(resolved_path.pData, pustrAbsoluteURL); + OSL_ASSERT(osl_File_E_None == rc); + } + + return rc; +} + +namespace osl::detail { + + /** + No separate error code if unicode to text conversion or getenv fails because for the + caller there is no difference why a file could not be found in $PATH + */ + bool find_in_PATH(const OUString& file_path, OUString& result) + { + bool bfound = false; + OUString path("PATH"); + OUString env_path; + + if (osl_getEnvironment(path.pData, &env_path.pData) == osl_Process_E_None) + bfound = osl::searchPath(file_path, env_path, result); + + return bfound; + } +} + +namespace +{ + /** + No separate error code if unicode to text conversion or getcwd fails because for the + caller there is no difference why a file could not be found in CDW + */ + bool find_in_CWD(const OUString& file_path, OUString& result) + { + bool bfound = false; + OUString cwd_url; + + if (osl_getProcessWorkingDir(&cwd_url.pData) == osl_Process_E_None) + { + OUString cwd; + FileBase::getSystemPathFromFileURL(cwd_url, cwd); + bfound = osl::searchPath(file_path, cwd, result); + } + return bfound; + } + + bool find_in_searchPath(const OUString& file_path, rtl_uString* search_path, OUString& result) + { + return (search_path && osl::searchPath(file_path, OUString(search_path), result)); + } + +} + +oslFileError osl_searchFileURL(rtl_uString* ustrFilePath, rtl_uString* ustrSearchPath, rtl_uString** pustrURL) +{ + OSL_PRECOND(ustrFilePath && pustrURL, "osl_searchFileURL: invalid parameter"); + + FileBase::RC rc; + OUString file_path; + + // try to interpret search path as file url else assume it's a system path list + rc = FileBase::getSystemPathFromFileURL(ustrFilePath, file_path); + if (rc == FileBase::E_INVAL) + file_path = ustrFilePath; + else if (rc != FileBase::E_None) + return oslFileError(rc); + + bool bfound = false; + OUString result; + + if (find_in_searchPath(file_path, ustrSearchPath, result) || + osl::detail::find_in_PATH(file_path, result) || + find_in_CWD(file_path, result)) + { + OUString resolved; + + if (osl::realpath(result, resolved)) + { + oslFileError osl_error = osl_getFileURLFromSystemPath(resolved.pData, pustrURL); + SAL_WARN_IF(osl_File_E_None != osl_error, "sal.file", "osl_getFileURLFromSystemPath failed"); + bfound = true; + } + } + return bfound ? osl_File_E_None : osl_File_E_NOENT; +} + +oslFileError FileURLToPath(char * buffer, size_t bufLen, rtl_uString* ustrFileURL) +{ + OString strSystemPath; + oslFileError osl_error = osl::detail::convertUrlToPathname( + OUString::unacquired(&ustrFileURL), &strSystemPath); + + if(osl_error != osl_File_E_None) + return osl_error; + + osl_systemPathRemoveSeparator(strSystemPath.pData); + + if (o3tl::make_unsigned(strSystemPath.getLength()) >= bufLen) { + return osl_File_E_OVERFLOW; + } + std::strcpy(buffer, strSystemPath.getStr()); + + return osl_error; +} + +int UnicodeToText( char * buffer, size_t bufLen, const sal_Unicode * uniText, sal_Int32 uniTextLen ) +{ + sal_uInt32 nInfo = 0; + sal_Size nSrcChars = 0; + + sal_Size nDestBytes = UnicodeToTextConverter_Impl::getInstance().convert ( + uniText, uniTextLen, buffer, bufLen, + OUSTRING_TO_OSTRING_CVTFLAGS | RTL_UNICODETOTEXT_FLAGS_FLUSH, &nInfo, &nSrcChars); + + if( nInfo & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL ) + { + errno = EOVERFLOW; + return 0; + } + + /* ensure trailing '\0' */ + buffer[nDestBytes] = '\0'; + return nDestBytes; +} + +namespace +{ + class TextToUnicodeConverter_Impl + { + rtl_TextToUnicodeConverter m_converter; + + TextToUnicodeConverter_Impl() + : m_converter (rtl_createTextToUnicodeConverter (osl_getThreadTextEncoding())) + {} + + ~TextToUnicodeConverter_Impl() + { + rtl_destroyTextToUnicodeConverter (m_converter); + } + + public: + static TextToUnicodeConverter_Impl & getInstance() + { + static TextToUnicodeConverter_Impl g_theConverter; + return g_theConverter; + } + + sal_Size convert( + char const * pSrcBuf, sal_Size nSrcBytes, sal_Unicode * pDstBuf, sal_Size nDstChars, + sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtBytes) + { + OSL_ASSERT(m_converter != nullptr); + return rtl_convertTextToUnicode ( + m_converter, nullptr, pSrcBuf, nSrcBytes, pDstBuf, nDstChars, nFlags, pInfo, pSrcCvtBytes); + } + }; +} + +int TextToUnicode( + const char* text, + size_t text_buffer_size, + sal_Unicode* unic_text, + sal_Int32 unic_text_buffer_size) +{ + sal_uInt32 nInfo = 0; + sal_Size nSrcChars = 0; + + sal_Size nDestBytes = TextToUnicodeConverter_Impl::getInstance().convert( + text, text_buffer_size, unic_text, unic_text_buffer_size, + OSTRING_TO_OUSTRING_CVTFLAGS | RTL_TEXTTOUNICODE_FLAGS_FLUSH, &nInfo, &nSrcChars); + + if (nInfo & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) + { + errno = EOVERFLOW; + return 0; + } + + /* ensure trailing '\0' */ + unic_text[nDestBytes] = '\0'; + return nDestBytes; +} + +oslFileError osl::detail::convertUrlToPathname(OUString const & url, OString * pathname) { + assert(pathname != nullptr); + oslFileError e; + try { + e = getSystemPathFromFileUrl(url, pathname, true); + } catch (std::length_error &) { + e = osl_File_E_RANGE; + } + if (e == osl_File_E_None && !pathname->startsWith("/")) { + e = osl_File_E_INVAL; + } + return e; +} + +oslFileError osl::detail::convertPathnameToUrl(OString const & pathname, OUString * url) { + assert(url != nullptr); + OUStringBuffer buf(10+pathname.getLength()); + buf.append("file:"); + if (pathname.startsWith("/")) { + buf.append("//"); + // so if pathname should ever start with "//" that isn't mistaken for an authority + // component + } + for (sal_Size convert = pathname.getLength();;) { + auto n = std::max(convert, sal_Size(PATH_MAX)); // approximation of required converted size + OUStringBuffer ubuf(static_cast<int>(n)); + auto s = ubuf.appendUninitialized(n); + sal_uInt32 info; + sal_Size converted; + //TODO: context, for reliable treatment of DESTBUFFERTOSMALL: + n = TextToUnicodeConverter_Impl::getInstance().convert( + pathname.getStr() + pathname.getLength() - convert, convert, s, n, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR | RTL_TEXTTOUNICODE_FLAGS_FLUSH), + &info, &converted); + ubuf.setLength(n); + buf.append( + rtl::Uri::encode( + ubuf.makeStringAndClear(), uriCharClass.data(), rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8)); + assert(converted <= convert); + convert -= converted; + if ((info & RTL_TEXTTOUNICODE_INFO_ERROR) != 0) { + assert(convert > 0); + //TODO: see writeEscapeOctet in sal/rtl/uri.cxx + buf.append("%"); + unsigned char c = pathname[pathname.getLength() - convert]; + assert(c >= 0x80); + static sal_Unicode const aHex[16] + = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 }; /* '0'--'9', 'A'--'F' */ + buf.append(aHex[c >> 4]); + buf.append(aHex[c & 15]); + --convert; + continue; + } + assert((convert == 0) == ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0)); + if ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0) { + break; + } + } + *url = buf.makeStringAndClear(); + return osl_File_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_url.hxx b/sal/osl/unx/file_url.hxx new file mode 100644 index 000000000..3ffd9e06b --- /dev/null +++ b/sal/osl/unx/file_url.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_FILE_URL_HXX +#define INCLUDED_SAL_OSL_UNX_FILE_URL_HXX + +#include <osl/file.h> + +namespace rtl { + class OString; + class OUString; +} + +oslFileError getSystemPathFromFileURL_Ex(rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath); + +oslFileError FileURLToPath(char * buffer, size_t bufLen, rtl_uString* ustrFileURL); + +int UnicodeToText(char * buffer, size_t bufLen, const sal_Unicode * uniText, sal_Int32 uniTextLen); + +int TextToUnicode(const char* text, size_t text_buffer_size, sal_Unicode* unic_text, sal_Int32 unic_text_buffer_size); + +namespace osl::detail { + +oslFileError convertUrlToPathname(rtl::OUString const & url, rtl::OString * pathname); + +oslFileError convertPathnameToUrl(rtl::OString const & pathname, rtl::OUString * url); + +bool find_in_PATH(const rtl::OUString& file_path, rtl::OUString& result); + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/file_volume.cxx b/sal/osl/unx/file_volume.cxx new file mode 100644 index 000000000..5e661bd65 --- /dev/null +++ b/sal/osl/unx/file_volume.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <rtl/alloc.h> + +#include "file_error_transl.hxx" +#include "file_url.hxx" +#include "system.hxx" + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sal/macros.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 <mntent.h> +#include <sys/vfs.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: */ diff --git a/sal/osl/unx/interlck.cxx b/sal/osl/unx/interlck.cxx new file mode 100644 index 000000000..81404cb57 --- /dev/null +++ b/sal/osl/unx/interlck.cxx @@ -0,0 +1,95 @@ +/* -*- 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 "system.hxx" + +#include <config_global.h> +#include <osl/interlck.h> + +#if ( defined (__sun) || defined ( NETBSD ) ) && defined ( SPARC ) +#error please use asm/interlck_sparc.s +#elif defined (__sun) && defined ( X86 ) +#error please use asm/interlck_x86.s +#elif HAVE_GCC_BUILTIN_ATOMIC +oslInterlockedCount SAL_CALL osl_incrementInterlockedCount(oslInterlockedCount* pCount) +{ + return __sync_add_and_fetch(pCount, 1); +} +oslInterlockedCount SAL_CALL osl_decrementInterlockedCount(oslInterlockedCount* pCount) +{ + return __sync_sub_and_fetch(pCount, 1); +} +#elif defined ( __GNUC__ ) && ( defined ( X86 ) || defined ( X86_64 ) ) +/* That's possible on x86-64 too since oslInterlockedCount is a sal_Int32 */ + +oslInterlockedCount SAL_CALL osl_incrementInterlockedCount(oslInterlockedCount* pCount) +{ + register oslInterlockedCount nCount asm("%eax"); + nCount = 1; + __asm__ __volatile__ ( + "lock\n\t" + "xaddl %0, %1\n\t" + : "+r" (nCount), "+m" (*pCount) + : /* nothing */ + : "memory"); + return ++nCount; +} + +oslInterlockedCount SAL_CALL osl_decrementInterlockedCount(oslInterlockedCount* pCount) +{ + register oslInterlockedCount nCount asm("%eax"); + nCount = -1; + __asm__ __volatile__ ( + "lock\n\t" + "xaddl %0, %1\n\t" + : "+r" (nCount), "+m" (*pCount) + : /* nothing */ + : "memory"); + return --nCount; +} +#else +/* use only if nothing else works, expensive due to single mutex for all reference counts */ + +static pthread_mutex_t InterLock = PTHREAD_MUTEX_INITIALIZER; + +oslInterlockedCount SAL_CALL osl_incrementInterlockedCount(oslInterlockedCount* pCount) +{ + oslInterlockedCount Count; + + pthread_mutex_lock(&InterLock); + Count = ++(*pCount); + pthread_mutex_unlock(&InterLock); + + return Count; +} + +oslInterlockedCount SAL_CALL osl_decrementInterlockedCount(oslInterlockedCount* pCount) +{ + oslInterlockedCount Count; + + pthread_mutex_lock(&InterLock); + Count = --(*pCount); + pthread_mutex_unlock(&InterLock); + + return Count; +} + +#endif /* default */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/memory.cxx b/sal/osl/unx/memory.cxx new file mode 100644 index 000000000..be3e41fb5 --- /dev/null +++ b/sal/osl/unx/memory.cxx @@ -0,0 +1,35 @@ +/* -*- 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/. + */ + +#include <oslmemory.h> + +#include <stdlib.h> +#ifdef __ANDROID__ +#include <malloc.h> +#endif + +void* osl_aligned_alloc(sal_Size align, sal_Size size) +{ + if (size == 0) + { + return nullptr; + } + +#if defined __ANDROID__ + return memalign(align, size); +#else + void* ptr; + int err = posix_memalign(&ptr, align, size); + return err ? nullptr : ptr; +#endif +} + +void osl_aligned_free(void* p) { free(p); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/module.cxx b/sal/osl/unx/module.cxx new file mode 100644 index 000000000..606c8fa47 --- /dev/null +++ b/sal/osl/unx/module.cxx @@ -0,0 +1,344 @@ +/* -*- 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 <sal/log.hxx> +#include <sal/types.h> +#include <osl/module.h> +#include <osl/thread.h> +#include <osl/process.h> +#include <osl/file.h> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <assert.h> +#include "system.hxx" +#include "file_url.hxx" + +#ifdef AIX +#include <sys/ldr.h> +#endif + +static bool getModulePathFromAddress(void * address, rtl_String ** path) +{ + bool result = false; +#if defined(AIX) + int size = 4 * 1024; + char *buf, *filename=NULL; + struct ld_info *lp; + + if ((buf = (char*)malloc(size)) == NULL) + return false; + + //figure out how big a buffer we need + while (loadquery(L_GETINFO, buf, size) == -1 && errno == ENOMEM) + { + size += 4 * 1024; + free(buf); + if ((buf = (char*)malloc(size)) == NULL) + return false; + } + + lp = (struct ld_info*) buf; + while (lp) + { + unsigned long start = (unsigned long)lp->ldinfo_dataorg; + unsigned long end = start + lp->ldinfo_datasize; + if (start <= (unsigned long)address && end > (unsigned long)address) + { + filename = lp->ldinfo_filename; + break; + } + if (!lp->ldinfo_next) + break; + lp = (struct ld_info*) ((char *) lp + lp->ldinfo_next); + } + + if (filename) + { + rtl_string_newFromStr(path, filename); + result = sal_True; + } + else + { + result = sal_False; + } + + free(buf); +#else +#if HAVE_UNIX_DLAPI + Dl_info dl_info; + + result = dladdr(address, &dl_info) != 0; + + if (result) + { + rtl_string_newFromStr(path, dl_info.dli_fname); + } +#else + (void) address; + (void) path; +#endif +#endif + return result; +} + +#ifndef DISABLE_DYNLOADING + +/*****************************************************************************/ +/* osl_loadModule */ +/*****************************************************************************/ + +oslModule SAL_CALL osl_loadModule(rtl_uString *ustrModuleName, sal_Int32 nRtldMode) +{ + oslModule pModule=nullptr; + rtl_uString* ustrTmp = nullptr; + + SAL_WARN_IF(ustrModuleName == nullptr, "sal.osl", "string is not valid"); + + /* ensure ustrTmp hold valid string */ + if (osl_getSystemPathFromFileURL(ustrModuleName, &ustrTmp) != osl_File_E_None) + rtl_uString_assign(&ustrTmp, ustrModuleName); + + if (ustrTmp) + { + char buffer[PATH_MAX]; + + if (UnicodeToText(buffer, PATH_MAX, ustrTmp->buffer, ustrTmp->length)) + pModule = osl_loadModuleAscii(buffer, nRtldMode); + rtl_uString_release(ustrTmp); + } + + return pModule; +} + +/*****************************************************************************/ +/* osl_loadModuleAscii */ +/*****************************************************************************/ + +oslModule SAL_CALL osl_loadModuleAscii(const char *pModuleName, sal_Int32 nRtldMode) +{ +#if HAVE_UNIX_DLAPI + SAL_WARN_IF( + ((nRtldMode & SAL_LOADMODULE_LAZY) != 0 + && (nRtldMode & SAL_LOADMODULE_NOW) != 0), + "sal.osl", "only either LAZY or NOW"); + if (pModuleName) + { + int rtld_mode = + ((nRtldMode & SAL_LOADMODULE_NOW) ? RTLD_NOW : RTLD_LAZY) | + ((nRtldMode & SAL_LOADMODULE_GLOBAL) ? RTLD_GLOBAL : RTLD_LOCAL); + void* pLib = dlopen(pModuleName, rtld_mode); + + SAL_WARN_IF( + pLib == nullptr, "sal.osl", + "dlopen(" << pModuleName << ", " << rtld_mode << "): " + << dlerror()); + return pLib; + } +#else + (void) pModuleName; + (void) nRtldMode; +#endif + return nullptr; +} + +oslModule osl_loadModuleRelativeAscii( + oslGenericFunction baseModule, char const * relativePath, sal_Int32 mode) +{ + assert(relativePath && "illegal argument"); + if (relativePath[0] == '/') { + return osl_loadModuleAscii(relativePath, mode); + } + rtl_String * path = nullptr; + rtl_String * suffix = nullptr; + oslModule module; + if (!getModulePathFromAddress( + reinterpret_cast< void * >(baseModule), &path)) + { + return nullptr; + } + rtl_string_newFromStr_WithLength( + &path, path->buffer, + (rtl_str_lastIndexOfChar_WithLength(path->buffer, path->length, '/') + + 1)); + /* cut off everything after the last slash; should the original path + contain no slash, the resulting path is the empty string */ + rtl_string_newFromStr(&suffix, relativePath); + rtl_string_newConcat(&path, path, suffix); + rtl_string_release(suffix); + module = osl_loadModuleAscii(path->buffer, mode); + rtl_string_release(path); + return module; +} + +#endif // !DISABLE_DYNLOADING + +/*****************************************************************************/ +/* osl_getModuleHandle */ +/*****************************************************************************/ + +sal_Bool SAL_CALL +osl_getModuleHandle(rtl_uString *, oslModule *pResult) +{ +#if HAVE_UNIX_DLAPI + *pResult = static_cast<oslModule>(RTLD_DEFAULT); + return true; +#else + *pResult = nullptr; + return false; +#endif +} + +/*****************************************************************************/ +/* osl_unloadModule */ +/*****************************************************************************/ +void SAL_CALL osl_unloadModule(oslModule hModule) +{ +#if !defined(DISABLE_DYNLOADING) && HAVE_UNIX_DLAPI + if (hModule) + { + int nRet = dlclose(hModule); + SAL_INFO_IF( + nRet != 0, "sal.osl", "dlclose(" << hModule << "): " << dlerror()); + } +#else + (void) hModule; +#endif +} + +namespace { + +void * getSymbol(oslModule module, char const * symbol) +{ + assert(symbol != nullptr); +#if HAVE_UNIX_DLAPI + // We do want to use dlsym() also in the DISABLE_DYNLOADING case + // just to look up symbols in the static executable, I think: + void * p = dlsym(module, symbol); + SAL_INFO_IF( + p == nullptr, "sal.osl", + "dlsym(" << module << ", " << symbol << "): " << dlerror()); +#else + (void) module; + (void) symbol; + void *p = nullptr; +#endif + return p; +} + +} + +/*****************************************************************************/ +/* osl_getSymbol */ +/*****************************************************************************/ +void* SAL_CALL +osl_getSymbol(oslModule Module, rtl_uString* pSymbolName) +{ + // Arbitrarily using UTF-8: + OString s; + if (!OUString::unacquired(&pSymbolName).convertToString( + &s, RTL_TEXTENCODING_UTF8, + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))) + { + SAL_INFO( + "sal.osl", "cannot convert \"" << OUString::unacquired(&pSymbolName) + << "\" to UTF-8"); + return nullptr; + } + if (s.indexOf('\0') != -1) { + SAL_INFO("sal.osl", "\"" << s << "\" contains embedded NUL"); + return nullptr; + } + return getSymbol(Module, s.getStr()); +} + +/*****************************************************************************/ +/* osl_getAsciiFunctionSymbol */ +/*****************************************************************************/ +oslGenericFunction SAL_CALL +osl_getAsciiFunctionSymbol(oslModule Module, const char *pSymbol) +{ + return reinterpret_cast<oslGenericFunction>(getSymbol(Module, pSymbol)); + // requires conditionally-supported conversion from void * to function + // pointer +} + +/*****************************************************************************/ +/* osl_getFunctionSymbol */ +/*****************************************************************************/ +oslGenericFunction SAL_CALL +osl_getFunctionSymbol(oslModule module, rtl_uString *puFunctionSymbolName) +{ + return reinterpret_cast<oslGenericFunction>( + osl_getSymbol(module, puFunctionSymbolName)); + // requires conditionally-supported conversion from void * to function + // pointer +} + +/*****************************************************************************/ +/* osl_getModuleURLFromAddress */ +/*****************************************************************************/ +sal_Bool SAL_CALL osl_getModuleURLFromAddress(void * addr, rtl_uString ** ppLibraryUrl) +{ + bool result = false; + rtl_String * path = nullptr; + if (getModulePathFromAddress(addr, &path)) + { + rtl_string2UString(ppLibraryUrl, + path->buffer, + path->length, + osl_getThreadTextEncoding(), + OSTRING_TO_OUSTRING_CVTFLAGS); + + SAL_WARN_IF( + *ppLibraryUrl == nullptr, "sal.osl", "rtl_string2UString failed"); + auto const e = osl_getFileURLFromSystemPath(*ppLibraryUrl, ppLibraryUrl); + if (e == osl_File_E_None) + { + SAL_INFO("sal.osl", "osl_getModuleURLFromAddress(" << addr << ") => " << OUString(*ppLibraryUrl)); + + result = true; + } + else + { + SAL_WARN( + "sal.osl", + "osl_getModuleURLFromAddress(" << addr << "), osl_getFileURLFromSystemPath(" + << OUString::unacquired(ppLibraryUrl) << ") failed with " << e); + result = false; + } + rtl_string_release(path); + } + return result; +} + +/*****************************************************************************/ +/* osl_getModuleURLFromFunctionAddress */ +/*****************************************************************************/ +sal_Bool SAL_CALL osl_getModuleURLFromFunctionAddress(oslGenericFunction addr, rtl_uString ** ppLibraryUrl) +{ + return osl_getModuleURLFromAddress( + reinterpret_cast<void*>(addr), ppLibraryUrl); + // requires conditionally-supported conversion from function pointer to + // void * +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/mutex.cxx b/sal/osl/unx/mutex.cxx new file mode 100644 index 000000000..1ca4958fd --- /dev/null +++ b/sal/osl/unx/mutex.cxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#if defined LINUX +// to define __USE_UNIX98, via _XOPEN_SOURCE, enabling pthread_mutexattr_settype +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#endif +#include "system.hxx" +#include "unixerrnostring.hxx" + +#include <sal/log.hxx> +#include <osl/mutex.h> + +#include <pthread.h> +#include <stdlib.h> + + +typedef struct _oslMutexImpl +{ + pthread_mutex_t mutex; +} oslMutexImpl; + +oslMutex SAL_CALL osl_createMutex() +{ + oslMutexImpl* pMutex = static_cast<oslMutexImpl*>(malloc(sizeof(oslMutexImpl))); + pthread_mutexattr_t aMutexAttr; + int nRet=0; + + SAL_WARN_IF(!pMutex, "sal.osl.mutex", "null pMutex"); + + if ( pMutex == nullptr ) + { + return nullptr; + } + + pthread_mutexattr_init(&aMutexAttr); + + nRet = pthread_mutexattr_settype(&aMutexAttr, PTHREAD_MUTEX_RECURSIVE); + if( nRet == 0 ) + nRet = pthread_mutex_init(&(pMutex->mutex), &aMutexAttr); + if ( nRet != 0 ) + { + SAL_WARN("sal.osl.mutex", "pthread_muxex_init failed: " << UnixErrnoString(nRet)); + + free(pMutex); + pMutex = nullptr; + } + + pthread_mutexattr_destroy(&aMutexAttr); + + return pMutex; +} + +void SAL_CALL osl_destroyMutex(oslMutex pMutex) +{ + SAL_WARN_IF(!pMutex, "sal.osl.mutex", "null pMutex"); + + if ( pMutex != nullptr ) + { + int nRet = pthread_mutex_destroy(&(pMutex->mutex)); + if ( nRet != 0 ) + { + SAL_WARN("sal.osl.mutex", "pthread_mutex_destroy failed: " << UnixErrnoString(nRet)); + } + + free(pMutex); + } +} + +#ifdef __COVERITY__ + extern void __coverity_recursive_lock_acquire__(void*); + extern void __coverity_recursive_lock_release__(void*); + extern void __coverity_assert_locked__(void*); +#endif + +sal_Bool SAL_CALL osl_acquireMutex(oslMutex pMutex) +{ + SAL_WARN_IF(!pMutex, "sal.osl.mutex", "null pMutex"); + + if ( pMutex != nullptr ) + { + int nRet = pthread_mutex_lock(&(pMutex->mutex)); + if ( nRet != 0 ) + { + SAL_WARN("sal.osl.mutex", "pthread_mutex_lock failed: " << UnixErrnoString(nRet)); + return false; + } +#ifdef __COVERITY__ + __coverity_recursive_lock_acquire__(pMutex); +#endif + return true; + } + + /* not initialized */ + return false; +} + +sal_Bool SAL_CALL osl_tryToAcquireMutex(oslMutex pMutex) +{ + bool result = false; + + SAL_WARN_IF(!pMutex, "sal.osl.mutex", "null pMutex"); + + if ( pMutex ) + { + int nRet = pthread_mutex_trylock(&(pMutex->mutex)); + if ( nRet == 0 ) + { +#ifdef __COVERITY__ + __coverity_recursive_lock_acquire__(pMutex); +#endif + result = true; + } + } + + return result; +} + +sal_Bool SAL_CALL osl_releaseMutex(oslMutex pMutex) +{ +#ifdef __COVERITY__ + __coverity_assert_locked__(pMutex); +#endif + SAL_WARN_IF(!pMutex, "sal.osl.mutex", "null pMutex"); + + if ( pMutex ) + { + int nRet = pthread_mutex_unlock(&(pMutex->mutex)); + if ( nRet != 0 ) + { + SAL_WARN("sal.osl.mutex", "pthread_mutex_unlock failed: " << UnixErrnoString(nRet)); + return false; + } + +#ifdef __COVERITY__ + __coverity_recursive_lock_release__(pMutex); +#endif + return true; + } + + /* not initialized */ + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/nlsupport.cxx b/sal/osl/unx/nlsupport.cxx new file mode 100644 index 000000000..2780a7fdd --- /dev/null +++ b/sal/osl/unx/nlsupport.cxx @@ -0,0 +1,872 @@ +/* -*- 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 <algorithm> +#include <cstring> + +#include <osl/nlsupport.h> +#include <osl/diagnose.h> +#include <osl/process.h> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include "nlsupport.hxx" + +// these share a lot, so use one define +#if defined(LINUX) || defined(EMSCRIPTEN) || defined(__sun) || \ + defined(FREEBSD) || defined(OPENBSD) || defined(DRAGONFLY) || defined(NETBSD) +#define LO_COMMON_NLS_ARCHS 1 +#else +#define LO_COMMON_NLS_ARCHS 0 +#endif + +#if LO_COMMON_NLS_ARCHS +#include <locale.h> +#include <langinfo.h> +#elif defined(MACOSX) || defined(IOS) +#include <osl/module.h> +#include <osl/thread.h> +#include "system.hxx" +#endif + +#include <string.h> + +namespace { + +struct Pair { + const char *key; + const rtl_TextEncoding value; +}; + +} + +/***************************************************************************** + compare function for binary search + *****************************************************************************/ + +static int +pair_compare (const char *key, const Pair *pair) +{ + int result = rtl_str_compareIgnoreAsciiCase( key, pair->key ); + return result; +} + +/***************************************************************************** + binary search on encoding tables + *****************************************************************************/ + +static const Pair* +pair_search (const char *key, const Pair *base, unsigned int member ) +{ + unsigned int lower = 0; + unsigned int upper = member; + + /* check for validity of input */ + if ( (key == nullptr) || (base == nullptr) || (member == 0) ) + return nullptr; + + /* binary search */ + while ( lower < upper ) + { + const unsigned int current = (lower + upper) / 2; + const int comparison = pair_compare( key, base + current ); + if (comparison < 0) + upper = current; + else if (comparison > 0) + lower = current + 1; + else + return base + current; + } + + return nullptr; +} + +/***************************************************************************** + convert rtl_Locale to locale string + *****************************************************************************/ + +static char * compose_locale( rtl_Locale * pLocale, char * buffer, size_t n ) +{ + /* check if a valid locale is specified */ + if( pLocale && pLocale->Language && + (pLocale->Language->length == 2 || pLocale->Language->length == 3) ) + { + size_t offset = 0; + + /* convert language code to ascii */ + { + rtl_String *pLanguage = nullptr; + + rtl_uString2String( &pLanguage, + pLocale->Language->buffer, pLocale->Language->length, + RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS ); + + if( sal::static_int_cast<sal_uInt32>(pLanguage->length) < n ) + { + strcpy( buffer, pLanguage->buffer ); + offset = pLanguage->length; + } + + rtl_string_release( pLanguage ); + } + + /* convert country code to ascii */ + if( pLocale->Country && (pLocale->Country->length == 2) ) + { + rtl_String *pCountry = nullptr; + + rtl_uString2String( &pCountry, + pLocale->Country->buffer, pLocale->Country->length, + RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS ); + + if( offset + pCountry->length + 1 < n ) + { + strcpy( buffer + offset++, "_" ); + strcpy( buffer + offset, pCountry->buffer ); + offset += pCountry->length; + } + + rtl_string_release( pCountry ); + } + + /* convert variant to ascii - check if there is enough space for the variant string */ + if( pLocale->Variant && pLocale->Variant->length && + ( sal::static_int_cast<sal_uInt32>(pLocale->Variant->length) < n - 6 ) ) + { + rtl_String *pVariant = nullptr; + + rtl_uString2String( &pVariant, + pLocale->Variant->buffer, pLocale->Variant->length, + RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS ); + + if( offset + pVariant->length + 1 < n ) + { + strcpy( buffer + offset, pVariant->buffer ); + } + + rtl_string_release( pVariant ); + } + + return buffer; + } + + return nullptr; +} + +/***************************************************************************** + convert locale string to rtl_Locale + *****************************************************************************/ + +static rtl_Locale * parse_locale( const char * locale ) +{ + assert(locale != nullptr); + + if (*locale == '\0' || std::strcmp(locale, "C") == 0 + || std::strcmp(locale, "POSIX") == 0) + { + return rtl_locale_register(u"C", u"", u""); + } + + size_t len = strlen( locale ); + + rtl_uString * pLanguage = nullptr; + rtl_uString * pCountry = nullptr; + rtl_uString * pVariant = nullptr; + + size_t offset = std::min<size_t>(len, 2); + + rtl_Locale * ret; + + /* language is a two or three letter code */ + if( (len > 3 && locale[3] == '_') || (len == 3 && locale[2] != '_') ) + offset = 3; + + /* convert language code to unicode */ + rtl_string2UString( &pLanguage, locale, offset, RTL_TEXTENCODING_ASCII_US, OSTRING_TO_OUSTRING_CVTFLAGS ); + OSL_ASSERT(pLanguage != nullptr); + + /* convert country code to unicode */ + if( len >= offset+3 && locale[offset] == '_' ) + { + rtl_string2UString( &pCountry, locale + offset + 1, 2, RTL_TEXTENCODING_ASCII_US, OSTRING_TO_OUSTRING_CVTFLAGS ); + OSL_ASSERT(pCountry != nullptr); + offset += 3; + } + + /* convert variant code to unicode - do not rely on "." as delimiter */ + if( len > offset ) { + rtl_string2UString( &pVariant, locale + offset, len - offset, RTL_TEXTENCODING_ASCII_US, OSTRING_TO_OUSTRING_CVTFLAGS ); + OSL_ASSERT(pVariant != nullptr); + } + + ret = rtl_locale_register( pLanguage->buffer, pCountry ? pCountry->buffer : u"", pVariant ? pVariant->buffer : u"" ); + + if (pVariant) rtl_uString_release(pVariant); + if (pCountry) rtl_uString_release(pCountry); + if (pLanguage) rtl_uString_release(pLanguage); + + return ret; +} + +#if LO_COMMON_NLS_ARCHS + +/* + * This implementation of osl_getTextEncodingFromLocale maps + * from nl_langinfo_l(CODESET) to rtl_textencoding defines. + * nl_langinfo() is supported only on Linux, Solaris, + * >= NetBSD 1.6 and >= FreeBSD 4.4 + * + * _nl_language_list[] is an array list of supported encodings. Because + * we are using a binary search, the list has to be in ascending order. + * We are comparing the encodings case insensitive, so the list has + * to be completely upper or lowercase. + */ + +#if defined(__sun) + +/* The values in the below list can be obtained with a script like + * #!/bin/sh + * for i in `locale -a`; do + * LC_ALL=$i locale -k code_set_name + * done + */ +static const Pair nl_language_list[] = { + { "5601", RTL_TEXTENCODING_EUC_KR }, /* ko_KR.EUC */ + { "646", RTL_TEXTENCODING_ISO_8859_1 }, /* fake: ASCII_US */ + { "ANSI-1251", RTL_TEXTENCODING_MS_1251 }, /* ru_RU.ANSI1251 */ + { "BIG5", RTL_TEXTENCODING_BIG5 }, /* zh_CN.BIG5 */ + { "BIG5-HKSCS", RTL_TEXTENCODING_BIG5_HKSCS }, /* zh_CN.BIG5HK */ + { "CNS11643", RTL_TEXTENCODING_EUC_TW }, /* zh_TW.EUC */ + { "EUCJP", RTL_TEXTENCODING_EUC_JP }, /* ja_JP.eucjp */ + { "GB18030", RTL_TEXTENCODING_GB_18030 }, /* zh_CN.GB18030 */ + { "GB2312", RTL_TEXTENCODING_GB_2312 }, /* zh_CN */ + { "GBK", RTL_TEXTENCODING_GBK }, /* zh_CN.GBK */ + { "ISO8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO8859-10", RTL_TEXTENCODING_ISO_8859_10 }, + { "ISO8859-13", RTL_TEXTENCODING_ISO_8859_13 }, /* lt_LT lv_LV */ + { "ISO8859-14", RTL_TEXTENCODING_ISO_8859_14 }, + { "ISO8859-15", RTL_TEXTENCODING_ISO_8859_15 }, + { "ISO8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, + { "KOI8-U", RTL_TEXTENCODING_KOI8_U }, + { "PCK", RTL_TEXTENCODING_MS_932 }, + { "SUN_EU_GREEK", RTL_TEXTENCODING_ISO_8859_7 }, /* 8859-7 + Euro */ + { "TIS620.2533", RTL_TEXTENCODING_MS_874 }, /* th_TH.TIS620 */ + { "UTF-8", RTL_TEXTENCODING_UTF8 } +}; + +/* XXX MS-874 is an extension to tis620, so this is not + * really equivalent */ + +#elif defined(LINUX) || defined(EMSCRIPTEN) + +#if !defined(CODESET) +#define CODESET _NL_CTYPE_CODESET_NAME +#endif + +const Pair nl_language_list[] = { + { "ANSI_X3.110-1983", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-99 NAPLPS */ + { "ANSI_X3.4-1968", RTL_TEXTENCODING_ISO_8859_1 }, /* fake: ASCII_US */ + { "ASMO_449", RTL_TEXTENCODING_DONTKNOW }, /* ISO_9036 ARABIC7 */ + { "BALTIC", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-179 */ + { "BIG5", RTL_TEXTENCODING_BIG5 }, /* locale: zh_TW */ + { "BIG5-HKSCS", RTL_TEXTENCODING_BIG5_HKSCS }, /* locale: zh_CN.BIG5HK */ + { "BIG5HKSCS", RTL_TEXTENCODING_BIG5_HKSCS }, /* deprecated */ + { "BS_4730", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-4 ISO646-GB */ + { "BS_VIEWDATA", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-47 */ + { "CP1250", RTL_TEXTENCODING_MS_1250 }, /* MS-EE */ + { "CP1251", RTL_TEXTENCODING_MS_1251 }, /* MS-CYRL */ + { "CP1252", RTL_TEXTENCODING_MS_1252 }, /* MS-ANSI */ + { "CP1253", RTL_TEXTENCODING_MS_1253 }, /* MS-GREEK */ + { "CP1254", RTL_TEXTENCODING_MS_1254 }, /* MS-TURK */ + { "CP1255", RTL_TEXTENCODING_MS_1255 }, /* MS-HEBR */ + { "CP1256", RTL_TEXTENCODING_MS_1256 }, /* MS-ARAB */ + { "CP1257", RTL_TEXTENCODING_MS_1257 }, /* WINBALTRIM */ + { "CSA_Z243.4-1985-1", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-121 */ + { "CSA_Z243.4-1985-2", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-122 CSA7-2 */ + { "CSA_Z243.4-1985-GR", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-123 */ + { "CSN_369103", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-139 */ + { "CWI", RTL_TEXTENCODING_DONTKNOW }, /* CWI-2 CP-HU */ + { "DEC-MCS", RTL_TEXTENCODING_DONTKNOW }, /* DEC */ + { "DIN_66003", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-21 */ + { "DS_2089", RTL_TEXTENCODING_DONTKNOW }, /* DS2089 ISO646-DK */ + { "EBCDIC-AT-DE", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-AT-DE-A", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-CA-FR", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-DK-NO", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-DK-NO-A", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-ES", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-ES-A", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-ES-S", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-FI-SE", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-FI-SE-A", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-FR", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-IS-FRISS", RTL_TEXTENCODING_DONTKNOW }, /* FRISS */ + { "EBCDIC-IT", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-PT", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-UK", RTL_TEXTENCODING_DONTKNOW }, + { "EBCDIC-US", RTL_TEXTENCODING_DONTKNOW }, + { "ECMA-CYRILLIC", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-111 */ + { "ES", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-17 */ + { "ES2", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-85 */ + { "EUC-JP", RTL_TEXTENCODING_EUC_JP }, /* locale: ja_JP.eucjp */ + { "EUC-KR", RTL_TEXTENCODING_EUC_KR }, /* locale: ko_KR.euckr */ + { "EUC-TW", RTL_TEXTENCODING_EUC_TW }, /* locale: zh_TW.euctw */ + { "GB18030", RTL_TEXTENCODING_GB_18030 }, /* locale: zh_CN.gb18030 */ + { "GB2312", RTL_TEXTENCODING_GB_2312 }, /* locale: zh_CN */ + { "GB_1988-80", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-57 */ + { "GBK", RTL_TEXTENCODING_GBK }, /* locale: zh_CN.GBK */ + { "GOST_19768-74", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-153 */ + { "GREEK-CCITT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-150 */ + { "GREEK7", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-88 */ + { "GREEK7-OLD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-18 */ + { "HP-ROMAN8", RTL_TEXTENCODING_DONTKNOW }, /* ROMAN8 R8 */ + { "IBM037", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-[US|CA|WT] */ + { "IBM038", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-INT CP038 */ + { "IBM1004", RTL_TEXTENCODING_DONTKNOW }, /* CP1004 OS2LATIN1 */ + { "IBM1026", RTL_TEXTENCODING_DONTKNOW }, /* CP1026 1026 */ + { "IBM1047", RTL_TEXTENCODING_DONTKNOW }, /* CP1047 1047 */ + { "IBM256", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-INT1 */ + { "IBM273", RTL_TEXTENCODING_DONTKNOW }, /* CP273 */ + { "IBM274", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-BE CP274 */ + { "IBM275", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-BR CP275 */ + { "IBM277", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CP-[DK|NO] */ + { "IBM278", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CP-[FISE]*/ + { "IBM280", RTL_TEXTENCODING_DONTKNOW }, /* CP280 EBCDIC-CP-IT*/ + { "IBM281", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-JP-E CP281 */ + { "IBM284", RTL_TEXTENCODING_DONTKNOW }, /* CP284 EBCDIC-CP-ES */ + { "IBM285", RTL_TEXTENCODING_DONTKNOW }, /* CP285 EBCDIC-CP-GB */ + { "IBM290", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-JP-KANA */ + { "IBM297", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CP-FR */ + { "IBM420", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CP-AR1 */ + { "IBM423", RTL_TEXTENCODING_DONTKNOW }, /* CP423 EBCDIC-CP-GR */ + { "IBM424", RTL_TEXTENCODING_DONTKNOW }, /* CP424 EBCDIC-CP-HE */ + { "IBM437", RTL_TEXTENCODING_IBM_437 }, /* CP437 437 */ + { "IBM500", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CP-[BE|CH] */ + { "IBM850", RTL_TEXTENCODING_IBM_850 }, /* CP850 850 */ + { "IBM851", RTL_TEXTENCODING_DONTKNOW }, /* CP851 851 */ + { "IBM852", RTL_TEXTENCODING_IBM_852 }, /* CP852 852 */ + { "IBM855", RTL_TEXTENCODING_IBM_855 }, /* CP855 855 */ + { "IBM857", RTL_TEXTENCODING_IBM_857 }, /* CP857 857 */ + { "IBM860", RTL_TEXTENCODING_IBM_860 }, /* CP860 860 */ + { "IBM861", RTL_TEXTENCODING_IBM_861 }, /* CP861 861 CP-IS */ + { "IBM862", RTL_TEXTENCODING_IBM_862 }, /* CP862 862 */ + { "IBM863", RTL_TEXTENCODING_IBM_863 }, /* CP863 863 */ + { "IBM864", RTL_TEXTENCODING_IBM_864 }, /* CP864 */ + { "IBM865", RTL_TEXTENCODING_IBM_865 }, /* CP865 865 */ + { "IBM866", RTL_TEXTENCODING_IBM_866 }, /* CP866 866 */ + { "IBM868", RTL_TEXTENCODING_DONTKNOW }, /* CP868 CP-AR */ + { "IBM869", RTL_TEXTENCODING_IBM_869 }, /* CP869 869 CP-GR */ + { "IBM870", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-[ROECE|YU] */ + { "IBM871", RTL_TEXTENCODING_DONTKNOW }, /* CP871 EBCDIC-CP-IS */ + { "IBM875", RTL_TEXTENCODING_DONTKNOW }, /* CP875 EBCDIC-GREEK */ + { "IBM880", RTL_TEXTENCODING_DONTKNOW }, /* EBCDIC-CYRILLIC */ + { "IBM891", RTL_TEXTENCODING_DONTKNOW }, /* CP891 */ + { "IBM903", RTL_TEXTENCODING_DONTKNOW }, /* CP903 */ + { "IBM904", RTL_TEXTENCODING_DONTKNOW }, /* CP904 904 */ + { "IBM905", RTL_TEXTENCODING_DONTKNOW }, /* CP905 EBCDIC-CP-TR */ + { "IBM918", RTL_TEXTENCODING_DONTKNOW }, /* CP918 EBCDIC-AR2 */ + { "IEC_P27-1", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-143 */ + { "INIS", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-49 */ + { "INIS-8", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-50 */ + { "INIS-CYRILLIC", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-51 */ + { "INVARIANT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-170 */ + { "ISO-8859-1", RTL_TEXTENCODING_ISO_8859_1 }, /* ISO-IR-100 CP819 */ + { "ISO-8859-10", RTL_TEXTENCODING_ISO_8859_10 }, /* ISO-IR-157 LATIN6 */ + { "ISO-8859-13", RTL_TEXTENCODING_ISO_8859_13 }, /* ISO-IR-179 LATIN7 */ + { "ISO-8859-14", RTL_TEXTENCODING_ISO_8859_14 }, /* LATIN8 L8 */ + { "ISO-8859-15", RTL_TEXTENCODING_ISO_8859_15 }, + { "ISO-8859-2", RTL_TEXTENCODING_ISO_8859_2 }, /* LATIN2 L2 */ + { "ISO-8859-3", RTL_TEXTENCODING_ISO_8859_3 }, /* LATIN3 L3 */ + { "ISO-8859-4", RTL_TEXTENCODING_ISO_8859_4 }, /* LATIN4 L4 */ + { "ISO-8859-5", RTL_TEXTENCODING_ISO_8859_5 }, /* CYRILLIC */ + { "ISO-8859-6", RTL_TEXTENCODING_ISO_8859_6 }, /* ECMA-114 ARABIC */ + { "ISO-8859-7", RTL_TEXTENCODING_ISO_8859_7 }, /* ECMA-118 GREEK8 */ + { "ISO-8859-8", RTL_TEXTENCODING_ISO_8859_8 }, /* ISO_8859-8 HEBREW */ + { "ISO-8859-9", RTL_TEXTENCODING_ISO_8859_9 }, /* ISO_8859-9 LATIN5 */ + { "ISO-IR-90", RTL_TEXTENCODING_DONTKNOW }, /* ISO_6937-2:1983 */ + { "ISO_10367-BOX", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-155 */ + { "ISO_2033-1983", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-98 E13B */ + { "ISO_5427", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-37 KOI-7 */ + { "ISO_5427-EXT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-54 */ + { "ISO_5428", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-55 */ + { "ISO_646.BASIC", RTL_TEXTENCODING_ASCII_US }, /* REF */ + { "ISO_646.IRV", RTL_TEXTENCODING_ASCII_US }, /* ISO-IR-2 IRV */ + { "ISO_646.IRV:1983", RTL_TEXTENCODING_ISO_8859_1 }, /* fake: ASCII_US, used for "C" locale*/ + { "ISO_6937", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-156 ISO6937*/ + { "ISO_6937-2-25", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-152 */ + { "ISO_6937-2-ADD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-142 */ + { "ISO_8859-SUPP", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-154 */ + { "IT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-15 */ + { "JIS_C6220-1969-JP", RTL_TEXTENCODING_DONTKNOW }, /* KATAKANA X0201-7 */ + { "JIS_C6220-1969-RO", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-14 */ + { "JIS_C6229-1984-A", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-91 */ + { "JIS_C6229-1984-B", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-92 */ + { "JIS_C6229-1984-B-ADD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-93 */ + { "JIS_C6229-1984-HAND", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-94 */ + { "JIS_C6229-1984-HAND-ADD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-95 */ + { "JIS_C6229-1984-KANA", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-96 */ + { "JIS_X0201", RTL_TEXTENCODING_DONTKNOW }, /* X0201 */ + { "JUS_I.B1.002", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-141 */ + { "JUS_I.B1.003-MAC", RTL_TEXTENCODING_DONTKNOW }, /* MACEDONIAN */ + { "JUS_I.B1.003-SERB", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-146 SERBIAN */ + { "KOI-8", RTL_TEXTENCODING_DONTKNOW }, + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, + { "KOI8-U", RTL_TEXTENCODING_KOI8_U }, + { "KSC5636", RTL_TEXTENCODING_DONTKNOW }, /* ISO646-KR */ + { "LATIN-GREEK", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-19 */ + { "LATIN-GREEK-1", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-27 */ + { "MAC-IS", RTL_TEXTENCODING_APPLE_ROMAN }, + { "MAC-UK", RTL_TEXTENCODING_APPLE_ROMAN }, + { "MACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN }, /* MAC */ + { "MSZ_7795.3", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-86 */ + { "NATS-DANO", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-9-1 */ + { "NATS-DANO-ADD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-9-2 */ + { "NATS-SEFI", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-8-1 */ + { "NATS-SEFI-ADD", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-8-2 */ + { "NC_NC00-10", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-151 */ + { "NEXTSTEP", RTL_TEXTENCODING_DONTKNOW }, /* NEXT */ + { "NF_Z_62-010", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-69 */ + { "NF_Z_62-010_(1973)", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-25 */ + { "NS_4551-1", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-60 */ + { "NS_4551-2", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-61 */ + { "PT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-16 */ + { "PT2", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-84 */ + { "SAMI", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-158 */ + { "SEN_850200_B", RTL_TEXTENCODING_DONTKNOW }, /* ISO646-[FI|SE] */ + { "SEN_850200_C", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-11 */ + { "T.101-G2", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-128 */ + { "T.61-7BIT", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-102 */ + { "T.61-8BIT", RTL_TEXTENCODING_DONTKNOW }, /* T.61 ISO-IR-103 */ + { "TIS-620", RTL_TEXTENCODING_MS_874 }, /* locale: th_TH */ + { "UTF-8", RTL_TEXTENCODING_UTF8 }, /* ISO-10646/UTF-8 */ + { "VIDEOTEX-SUPPL", RTL_TEXTENCODING_DONTKNOW }, /* ISO-IR-70 */ + { "WIN-SAMI-2", RTL_TEXTENCODING_DONTKNOW } /* WS2 */ +}; + +#elif defined(FREEBSD) || defined(DRAGONFLY) + +static const Pair nl_language_list[] = { + { "ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "BIG5", RTL_TEXTENCODING_BIG5 }, /* China - Traditional Chinese */ + { "CP1251", RTL_TEXTENCODING_MS_1251 }, /* MS-CYRL */ + { "CP866", RTL_TEXTENCODING_IBM_866 }, /* CP866 866 */ + { "EUCCN", RTL_TEXTENCODING_EUC_CN }, /* China - Simplified Chinese */ + { "EUCJP", RTL_TEXTENCODING_EUC_JP }, /* Japan */ + { "EUCKR", RTL_TEXTENCODING_EUC_KR }, /* Korea */ + { "ISO8859-1", RTL_TEXTENCODING_ISO_8859_1 }, /* Western */ + { "ISO8859-15", RTL_TEXTENCODING_ISO_8859_15 }, /* Western Updated (w/Euro sign) */ + { "ISO8859-2", RTL_TEXTENCODING_ISO_8859_2 }, /* Central European */ + { "ISO8859-4", RTL_TEXTENCODING_ISO_8859_4 }, /* LATIN4 L4 */ + { "ISO8859-5", RTL_TEXTENCODING_ISO_8859_5 }, /* Cyrillic */ + { "ISO8859-7", RTL_TEXTENCODING_ISO_8859_7 }, /* Greek */ + { "ISO8859-9", RTL_TEXTENCODING_ISO_8859_9 }, /* Turkish */ + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, /* KOI8-R */ + { "KOI8-U", RTL_TEXTENCODING_KOI8_U }, /* KOI8-U */ + { "SJIS", RTL_TEXTENCODING_SHIFT_JIS }, /* Japan */ + { "US-ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "UTF-8", RTL_TEXTENCODING_UTF8 } /* ISO-10646/UTF-8 */ +}; + +#elif defined(NETBSD) + +static const Pair nl_language_list[] = { + { "ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "BIG5", RTL_TEXTENCODING_BIG5 }, /* China - Traditional Chinese */ + { "Big5", RTL_TEXTENCODING_BIG5 }, /* China - Traditional Chinese */ + { "Big5-HKSCS", RTL_TEXTENCODING_BIG5_HKSCS }, /* locale: zh_CN.BIG5HK */ + { "Big5HKSCS", RTL_TEXTENCODING_BIG5_HKSCS }, /* deprecated */ + { "CP1251", RTL_TEXTENCODING_MS_1251 }, /* MS-CYRL */ + { "CP866", RTL_TEXTENCODING_IBM_866 }, /* CP866 866 */ + { "CTEXT", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "eucCN", RTL_TEXTENCODING_EUC_CN }, /* China - Simplified Chinese */ + { "eucJP", RTL_TEXTENCODING_EUC_JP }, /* Japan */ + { "eucKR", RTL_TEXTENCODING_EUC_KR }, /* Korea */ + { "eucTW", RTL_TEXTENCODING_EUC_TW }, /* China - Traditional Chinese */ + { "GB18030", RTL_TEXTENCODING_GB_18030 }, /* locale: zh_CN.gb18030 */ + { "GB2312", RTL_TEXTENCODING_GB_2312 }, /* locale: zh_CN */ + { "ISO-2022-JP", RTL_TEXTENCODING_DONTKNOW }, /* */ + { "ISO-2022-JP-2", RTL_TEXTENCODING_DONTKNOW }, /* */ + { "ISO8859-1", RTL_TEXTENCODING_ISO_8859_1 }, /* Western */ + { "ISO8859-13", RTL_TEXTENCODING_ISO_8859_13 }, /* ISO-IR-179 LATIN7 */ + { "ISO8859-15", RTL_TEXTENCODING_ISO_8859_15 }, /* Western Updated (w/Euro sign) */ + { "ISO8859-2", RTL_TEXTENCODING_ISO_8859_2 }, /* Central European */ + { "ISO8859-4", RTL_TEXTENCODING_ISO_8859_4 }, /* LATIN4 L4 */ + { "ISO8859-5", RTL_TEXTENCODING_ISO_8859_5 }, /* Cyrillic */ + { "ISO8859-7", RTL_TEXTENCODING_ISO_8859_7 }, /* Greek */ + { "ISO8859-9", RTL_TEXTENCODING_ISO_8859_9 }, /* Turkish */ + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, /* KOI8-R */ + { "KOI8-U", RTL_TEXTENCODING_KOI8_U }, /* KOI8-U */ + { "PT154", RTL_TEXTENCODING_PT154 }, /* */ + { "SJIS", RTL_TEXTENCODING_SHIFT_JIS }, /* Japan */ + { "US-ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "UTF-8", RTL_TEXTENCODING_UTF8 } /* ISO-10646/UTF-8 */ +}; + +#elif defined(OPENBSD) + +static const Pair nl_language_list[] = { + { "ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "BIG5", RTL_TEXTENCODING_BIG5 }, /* China - Traditional Chinese */ + { "CP1251", RTL_TEXTENCODING_MS_1251 }, /* MS-CYRL */ + { "CP866", RTL_TEXTENCODING_IBM_866 }, /* CP866 866 */ + { "EUCCN", RTL_TEXTENCODING_EUC_CN }, /* China - Simplified Chinese */ + { "EUCJP", RTL_TEXTENCODING_EUC_JP }, /* Japan */ + { "EUCKR", RTL_TEXTENCODING_EUC_KR }, /* Korea */ + { "ISO8859-1", RTL_TEXTENCODING_ISO_8859_1 }, /* Western */ + { "ISO8859-15", RTL_TEXTENCODING_ISO_8859_15 }, /* Western Updated (w/Euro sign) */ + { "ISO8859-2", RTL_TEXTENCODING_ISO_8859_2 }, /* Central European */ + { "ISO8859-4", RTL_TEXTENCODING_ISO_8859_4 }, /* LATIN4 L4 */ + { "ISO8859-5", RTL_TEXTENCODING_ISO_8859_5 }, /* Cyrillic */ + { "ISO8859-7", RTL_TEXTENCODING_ISO_8859_7 }, /* Greek */ + { "ISO8859-9", RTL_TEXTENCODING_ISO_8859_9 }, /* Turkish */ + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, /* KOI8-R */ + { "KOI8-U", RTL_TEXTENCODING_KOI8_U }, /* KOI8-U */ + { "SJIS", RTL_TEXTENCODING_SHIFT_JIS }, /* Japan */ + { "US-ASCII", RTL_TEXTENCODING_ASCII_US }, /* US-ASCII */ + { "UTF-8", RTL_TEXTENCODING_UTF8 } /* ISO-10646/UTF-8 */ +}; + +#else +#error Unhandled individual LO_COMMON_NLS_ARCHS +#endif // individual common NLS archs + +/***************************************************************************** + return the text encoding corresponding to the given locale + *****************************************************************************/ + +rtl_TextEncoding osl_getTextEncodingFromLocale( rtl_Locale * pLocale ) +{ + const Pair *language=nullptr; + + char locale_buf[64] = ""; + char codeset_buf[64]; + + char *codeset = nullptr; + + /* default to process locale if pLocale == NULL */ + if( pLocale == nullptr ) + osl_getProcessLocale( &pLocale ); + + /* convert rtl_Locale to locale string */ + compose_locale( pLocale, locale_buf, 64 ); + + locale_t ctype_locale = newlocale( + LC_CTYPE_MASK, locale_buf, static_cast<locale_t>(0)); + if (ctype_locale == static_cast<locale_t>(0)) + { + return RTL_TEXTENCODING_DONTKNOW; + } + + /* get the charset as indicated by the LC_CTYPE locale */ +#if defined(NETBSD) && !defined(CODESET) + codeset = NULL; +#else + codeset = nl_langinfo_l(CODESET, ctype_locale); + // per SUSv4, the return value of nl_langinfo_l can be invalidated by a + // subsequent call to nl_langinfo (not nl_langinfo_l) in any thread, but + // we cannot guard against that (at least, no code in LO itself should + // call nl_langinfo) +#endif + + if ( codeset != nullptr ) + { + /* get codeset into mt save memory */ + strncpy( codeset_buf, codeset, sizeof(codeset_buf) ); + codeset_buf[sizeof(codeset_buf) - 1] = 0; + codeset = codeset_buf; + } + + freelocale(ctype_locale); + + /* search the codeset in our language list */ + if ( codeset != nullptr ) + { + language = pair_search (codeset, nl_language_list, SAL_N_ELEMENTS( nl_language_list ) ); + } + + OSL_ASSERT( language && ( RTL_TEXTENCODING_DONTKNOW != language->value ) ); + + /* a matching item in our list provides a mapping from codeset to + * rtl-codeset */ + if ( language != nullptr ) + return language->value; + + return RTL_TEXTENCODING_DONTKNOW; +} + +/***************************************************************************** + return the current process locale + *****************************************************************************/ + +void imp_getProcessLocale( rtl_Locale ** ppLocale ) +{ + char const * locale = getenv("LC_ALL"); + if (locale == nullptr || *locale == '\0') { + locale = getenv("LC_CTYPE"); + if (locale == nullptr || *locale == '\0') { + locale = getenv("LANG"); + if (locale == nullptr || *locale == '\0') { + locale = "C"; + } + } + } + // coverity[overrun-buffer-val : FALSE] - coverity gets this very wrong + *ppLocale = parse_locale(locale); +} + +#else // !LO_COMMON_NLS_ARCHS + +/* + * This implementation of osl_getTextEncodingFromLocale maps + * from the ISO language codes. + */ + +const Pair full_locale_list[] = { + { "ja_JP.eucJP", RTL_TEXTENCODING_EUC_JP }, + { "ja_JP.EUC", RTL_TEXTENCODING_EUC_JP }, + { "ko_KR.EUC", RTL_TEXTENCODING_EUC_KR }, + { "zh_CN.EUC", RTL_TEXTENCODING_EUC_CN }, + { "zh_TW.EUC", RTL_TEXTENCODING_EUC_TW } +}; + +const Pair locale_extension_list[] = { + { "big5", RTL_TEXTENCODING_BIG5 }, + { "big5hk", RTL_TEXTENCODING_BIG5_HKSCS }, + { "gb18030", RTL_TEXTENCODING_GB_18030 }, + { "euc", RTL_TEXTENCODING_EUC_JP }, + { "iso8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "iso8859-10", RTL_TEXTENCODING_ISO_8859_10 }, + { "iso8859-13", RTL_TEXTENCODING_ISO_8859_13 }, + { "iso8859-14", RTL_TEXTENCODING_ISO_8859_14 }, + { "iso8859-15", RTL_TEXTENCODING_ISO_8859_15 }, + { "iso8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "iso8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "iso8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "iso8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "iso8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "iso8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "iso8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "iso8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "koi8-r", RTL_TEXTENCODING_KOI8_R }, + { "koi8-u", RTL_TEXTENCODING_KOI8_U }, + { "pck", RTL_TEXTENCODING_MS_932 }, +#if (0) + { "sun_eu_greek", RTL_TEXTENCODING_DONTKNOW }, +#endif + { "utf-16", RTL_TEXTENCODING_UNICODE }, + { "utf-7", RTL_TEXTENCODING_UTF7 }, + { "utf-8", RTL_TEXTENCODING_UTF8 } +}; + +const Pair iso_language_list[] = { + { "af", RTL_TEXTENCODING_ISO_8859_1 }, + { "ar", RTL_TEXTENCODING_ISO_8859_6 }, + { "az", RTL_TEXTENCODING_ISO_8859_9 }, + { "be", RTL_TEXTENCODING_ISO_8859_5 }, + { "bg", RTL_TEXTENCODING_ISO_8859_5 }, + { "ca", RTL_TEXTENCODING_ISO_8859_1 }, + { "cs", RTL_TEXTENCODING_ISO_8859_2 }, + { "da", RTL_TEXTENCODING_ISO_8859_1 }, + { "de", RTL_TEXTENCODING_ISO_8859_1 }, + { "el", RTL_TEXTENCODING_ISO_8859_7 }, + { "en", RTL_TEXTENCODING_ISO_8859_1 }, + { "es", RTL_TEXTENCODING_ISO_8859_1 }, + { "et", RTL_TEXTENCODING_ISO_8859_4 }, + { "eu", RTL_TEXTENCODING_ISO_8859_1 }, + { "fa", RTL_TEXTENCODING_ISO_8859_6 }, + { "fi", RTL_TEXTENCODING_ISO_8859_1 }, + { "fo", RTL_TEXTENCODING_ISO_8859_1 }, + { "fr", RTL_TEXTENCODING_ISO_8859_1 }, + { "gr", RTL_TEXTENCODING_ISO_8859_7 }, + { "he", RTL_TEXTENCODING_ISO_8859_8 }, + { "hi", RTL_TEXTENCODING_DONTKNOW }, + { "hr", RTL_TEXTENCODING_ISO_8859_2 }, + { "hu", RTL_TEXTENCODING_ISO_8859_2 }, + { "hy", RTL_TEXTENCODING_DONTKNOW }, + { "id", RTL_TEXTENCODING_ISO_8859_1 }, + { "is", RTL_TEXTENCODING_ISO_8859_1 }, + { "it", RTL_TEXTENCODING_ISO_8859_1 }, + { "iw", RTL_TEXTENCODING_ISO_8859_8 }, + { "ja", RTL_TEXTENCODING_EUC_JP }, + { "ka", RTL_TEXTENCODING_DONTKNOW }, + { "kk", RTL_TEXTENCODING_ISO_8859_5 }, + { "ko", RTL_TEXTENCODING_EUC_KR }, + { "lt", RTL_TEXTENCODING_ISO_8859_4 }, + { "lv", RTL_TEXTENCODING_ISO_8859_4 }, + { "mk", RTL_TEXTENCODING_ISO_8859_5 }, + { "mr", RTL_TEXTENCODING_DONTKNOW }, + { "ms", RTL_TEXTENCODING_ISO_8859_1 }, + { "nl", RTL_TEXTENCODING_ISO_8859_1 }, + { "no", RTL_TEXTENCODING_ISO_8859_1 }, + { "pl", RTL_TEXTENCODING_ISO_8859_2 }, + { "pt", RTL_TEXTENCODING_ISO_8859_1 }, + { "ro", RTL_TEXTENCODING_ISO_8859_2 }, + { "ru", RTL_TEXTENCODING_ISO_8859_5 }, + { "sa", RTL_TEXTENCODING_DONTKNOW }, + { "sk", RTL_TEXTENCODING_ISO_8859_2 }, + { "sl", RTL_TEXTENCODING_ISO_8859_2 }, + { "sq", RTL_TEXTENCODING_ISO_8859_2 }, + { "sv", RTL_TEXTENCODING_ISO_8859_1 }, + { "sw", RTL_TEXTENCODING_ISO_8859_1 }, + { "ta", RTL_TEXTENCODING_DONTKNOW }, + { "th", RTL_TEXTENCODING_DONTKNOW }, + { "tr", RTL_TEXTENCODING_ISO_8859_9 }, + { "tt", RTL_TEXTENCODING_ISO_8859_5 }, + { "uk", RTL_TEXTENCODING_ISO_8859_5 }, + { "ur", RTL_TEXTENCODING_ISO_8859_6 }, + { "uz", RTL_TEXTENCODING_ISO_8859_9 }, + { "vi", RTL_TEXTENCODING_DONTKNOW }, + { "zh", RTL_TEXTENCODING_BIG5 } +}; + +/***************************************************************************** + return the text encoding corresponding to the given locale + *****************************************************************************/ + +rtl_TextEncoding osl_getTextEncodingFromLocale( rtl_Locale * pLocale ) +{ + const Pair *language = nullptr; + char locale_buf[64] = ""; + + /* default to process locale if pLocale == NULL */ + if( nullptr == pLocale ) + osl_getProcessLocale( &pLocale ); + + /* convert rtl_Locale to locale string */ + if( compose_locale( pLocale, locale_buf, 64 ) ) + { + /* check special handling list (EUC) first */ + language = pair_search( locale_buf, full_locale_list, SAL_N_ELEMENTS( full_locale_list ) ); + + if( nullptr == language ) + { + /* + * check if there is a charset qualifier at the end of the given locale string + * e.g. de.ISO8859-15 or de.ISO8859-15@euro which strongly indicates what + * charset to use + */ + char* cp = strrchr( locale_buf, '.' ); + + if( nullptr != cp ) + { + language = pair_search( cp + 1, locale_extension_list, SAL_N_ELEMENTS( locale_extension_list ) ); + } + } + + /* use iso language code to determine the charset */ + if( nullptr == language ) + { + /* iso lang codes have 2 characters */ + locale_buf[2] = '\0'; + + language = pair_search( locale_buf, iso_language_list, SAL_N_ELEMENTS( iso_language_list ) ); + } + } + + /* a matching item in our list provides a mapping from codeset to + * rtl-codeset */ + if ( language != nullptr ) + return language->value; + + return RTL_TEXTENCODING_DONTKNOW; +} + +#if defined(MACOSX) || defined(IOS) + +/***************************************************************************** + return the current process locale + *****************************************************************************/ + +void imp_getProcessLocale( rtl_Locale ** ppLocale ) +{ + OUString loc16(macosx_getLocale()); + OString locale; + if (!loc16.convertToString( + &locale, RTL_TEXTENCODING_UTF8, + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR + | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))) + { + SAL_INFO("sal.osl", "Cannot convert \"" << loc16 << "\" to UTF-8"); + } + + /* handle the case where OS specific method of finding locale fails */ + if ( locale.isEmpty() ) + { + /* simulate behavior of setlocale */ + locale = getenv( "LC_ALL" ); + + if( locale.isEmpty() ) + locale = getenv( "LC_CTYPE" ); + + if( locale.isEmpty() ) + locale = getenv( "LANG" ); + + if( locale.isEmpty() ) + locale = "C"; + } + + /* return the locale */ + *ppLocale = parse_locale( locale.getStr() ); +} + +#else // !MACOSX && !IOS + +/***************************************************************************** + return the current process locale + *****************************************************************************/ + +void imp_getProcessLocale( rtl_Locale ** ppLocale ) +{ +#ifdef ANDROID + /* No locale environment variables on Android, so why even bother + * with getenv(). + */ + const char* locale = "en-US.UTF-8"; +#else + /* simulate behavior off setlocale */ + const char* locale = getenv("LC_ALL"); + + if( NULL == locale ) + locale = getenv( "LC_CTYPE" ); + + if( NULL == locale ) + locale = getenv( "LANG" ); + + if( NULL == locale ) + locale = "C"; + +#endif + *ppLocale = parse_locale( locale ); +} + +#endif // !MACOSX && !IOS +#endif // !LO_COMMON_NLS_ARCHS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/nlsupport.hxx b/sal/osl/unx/nlsupport.hxx new file mode 100644 index 000000000..ba5acf23c --- /dev/null +++ b/sal/osl/unx/nlsupport.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_NLSUPPORT_HXX +#define INCLUDED_SAL_OSL_UNX_NLSUPPORT_HXX + +#include <sal/config.h> + +#include <rtl/locale.h> + +namespace rtl +{ +class OUString; +} + +void imp_getProcessLocale(rtl_Locale**); + +#if defined IOS || defined MACOSX +OUString macosx_getLocale(); +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/osxlocale.cxx b/sal/osl/unx/osxlocale.cxx new file mode 100644 index 000000000..f82ea1436 --- /dev/null +++ b/sal/osl/unx/osxlocale.cxx @@ -0,0 +1,104 @@ +/* -*- 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/types.h> +#include <assert.h> + +#include <premac.h> +#ifndef IOS +#include <CoreServices/CoreServices.h> +#endif +#include <CoreFoundation/CoreFoundation.h> +#include <postmac.h> + +#include <rtl/ustrbuf.hxx> + +#include "nlsupport.hxx" + +namespace +{ + template <typename T> + class CFGuard + { + public: + explicit CFGuard(T& rT) : rT_(rT) {} + ~CFGuard() { if (rT_) CFRelease(rT_); } + private: + T& rT_; + }; + + typedef CFGuard<CFArrayRef> CFArrayGuard; + typedef CFGuard<CFStringRef> CFStringGuard; + typedef CFGuard<CFPropertyListRef> CFPropertyListGuard; + + /** Get the current process locale from system + */ + CFStringRef getProcessLocale() + { + CFPropertyListRef pref = CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), kCFPreferencesCurrentApplication); + CFPropertyListGuard proplGuard(pref); + + if (pref == nullptr) // return fallback value 'en_US' + return CFStringCreateWithCString(kCFAllocatorDefault, "en_US", kCFStringEncodingASCII); + + CFStringRef sref = (CFGetTypeID(pref) == CFArrayGetTypeID()) ? static_cast<CFStringRef>(CFArrayGetValueAtIndex(static_cast<CFArrayRef>(pref), 0)) : static_cast<CFStringRef>(pref); + + return CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, sref); + } + + void append(OUStringBuffer & buffer, CFStringRef string) { + CFIndex n = CFStringGetLength(string); + CFStringGetCharacters( + string, CFRangeMake(0, n), + reinterpret_cast<UniChar *>(buffer.appendUninitialized(n))); + } +} + +/** Grab current locale from system. +*/ +OUString macosx_getLocale() +{ + CFStringRef sref = getProcessLocale(); + CFStringGuard sGuard(sref); + + assert(sref != nullptr && "osxlocale.cxx: getProcessLocale must return a non-NULL value"); + + // split the string into substrings; the first two (if there are two) substrings + // are language and country + CFArrayRef subs = CFStringCreateArrayBySeparatingStrings(nullptr, sref, CFSTR("-")); + CFArrayGuard arrGuard(subs); + + OUStringBuffer buf; + append(buf, static_cast<CFStringRef>(CFArrayGetValueAtIndex(subs, 0))); + + // country also available? Assumption: if the array contains more than one + // value the second value is always the country! + if (CFArrayGetCount(subs) > 1) + { + buf.append("_"); + append(buf, static_cast<CFStringRef>(CFArrayGetValueAtIndex(subs, 1))); + } + // Append 'UTF-8' to the locale because the macOS file + // system interface is UTF-8 based and sal tries to determine + // the file system locale from the locale information + buf.append(".UTF-8"); + return buf.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/pipe.cxx b/sal/osl/unx/pipe.cxx new file mode 100644 index 000000000..45d31e008 --- /dev/null +++ b/sal/osl/unx/pipe.cxx @@ -0,0 +1,523 @@ +/* -*- 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 "system.hxx" + +#include <o3tl/safeint.hxx> +#include <osl/pipe.h> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/interlck.h> +#include <rtl/string.h> +#include <rtl/ustring.h> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> + +#include "sockimpl.hxx" +#include "secimpl.hxx" +#include "unixerrnostring.hxx" + +#include <cassert> +#include <cstring> + +constexpr OStringLiteral PIPEDEFAULTPATH = "/tmp"; +constexpr OStringLiteral PIPEALTERNATEPATH = "/var/tmp"; + +static oslPipe osl_psz_createPipe(const char *pszPipeName, oslPipeOptions Options, oslSecurity Security); + +struct +{ + int errcode; + oslPipeError error; +} const PipeError[]= { + { 0, osl_Pipe_E_None }, /* no error */ + { EPROTOTYPE, osl_Pipe_E_NoProtocol }, /* Protocol wrong type for socket */ + { ENOPROTOOPT, osl_Pipe_E_NoProtocol }, /* Protocol not available */ + { EPROTONOSUPPORT, osl_Pipe_E_NoProtocol }, /* Protocol not supported */ +#ifdef ESOCKTNOSUPPORT + { ESOCKTNOSUPPORT, osl_Pipe_E_NoProtocol }, /* Socket type not supported */ +#endif + { EPFNOSUPPORT, osl_Pipe_E_NoProtocol }, /* Protocol family not supported */ + { EAFNOSUPPORT, osl_Pipe_E_NoProtocol }, /* Address family not supported by */ + /* protocol family */ + { ENETRESET, osl_Pipe_E_NetworkReset }, /* Network dropped connection because */ + /* of reset */ + { ECONNABORTED, osl_Pipe_E_ConnectionAbort }, /* Software caused connection abort */ + { ECONNRESET, osl_Pipe_E_ConnectionReset }, /* Connection reset by peer */ + { ENOBUFS, osl_Pipe_E_NoBufferSpace }, /* No buffer space available */ + { ETIMEDOUT, osl_Pipe_E_TimedOut }, /* Connection timed out */ + { ECONNREFUSED, osl_Pipe_E_ConnectionRefused }, /* Connection refused */ + { -1, osl_Pipe_E_invalidError } +}; + +static oslPipeError osl_PipeErrorFromNative(int nativeType) +{ + int i = 0; + + while ((PipeError[i].error != osl_Pipe_E_invalidError) && + (PipeError[i].errcode != nativeType)) + { + i++; + } + + return PipeError[i].error; +} + +static oslPipe createPipeImpl() +{ + oslPipe pPipeImpl; + + pPipeImpl = static_cast< oslPipe >(calloc(1, sizeof(struct oslPipeImpl))); + if (!pPipeImpl) + return nullptr; + + pPipeImpl->m_nRefCount = 1; + pPipeImpl->m_bClosed = false; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pPipeImpl->m_bIsInShutdown = false; + pPipeImpl->m_bIsAccepting = false; +#endif + + return pPipeImpl; +} + +static void destroyPipeImpl(oslPipe pImpl) +{ + if (pImpl) + free(pImpl); +} + +oslPipe SAL_CALL osl_createPipe(rtl_uString *ustrPipeName, oslPipeOptions Options, oslSecurity Security) +{ + oslPipe pPipe = nullptr; + rtl_String* strPipeName = nullptr; + + if (ustrPipeName) + { + rtl_uString2String(&strPipeName, + rtl_uString_getStr(ustrPipeName), + rtl_uString_getLength(ustrPipeName), + osl_getThreadTextEncoding(), + OUSTRING_TO_OSTRING_CVTFLAGS); + char* pszPipeName = rtl_string_getStr(strPipeName); + pPipe = osl_psz_createPipe(pszPipeName, Options, Security); + + if (strPipeName) + rtl_string_release(strPipeName); + } + + return pPipe; + +} + +static OString +getBootstrapSocketPath() +{ + OUString pValue; + + if (rtl::Bootstrap::get("OSL_SOCKET_PATH", pValue)) + { + return OUStringToOString(pValue, RTL_TEXTENCODING_UTF8); + } + return ""; +} + +static oslPipe osl_psz_createPipe(const char *pszPipeName, oslPipeOptions Options, + oslSecurity Security) +{ + int Flags; + size_t len; + struct sockaddr_un addr; + + OString name; + oslPipe pPipe; + + if (access(PIPEDEFAULTPATH.getStr(), W_OK) == 0) + name = PIPEDEFAULTPATH; + else if (access(PIPEALTERNATEPATH.getStr(), W_OK) == 0) + name = PIPEALTERNATEPATH; + else { + name = getBootstrapSocketPath (); + } + + name += "/"; + + if (Security) + { + char Ident[256]; + + Ident[0] = '\0'; + + OSL_VERIFY(osl_psz_getUserIdent(Security, Ident, sizeof(Ident))); + + name += OString::Concat("OSL_PIPE_") + Ident + "_" + pszPipeName; + } + else + { + name += OString::Concat("OSL_PIPE_") + pszPipeName; + } + + if (o3tl::make_unsigned(name.getLength()) >= sizeof addr.sun_path) + { + SAL_WARN("sal.osl.pipe", "osl_createPipe: pipe name too long"); + return nullptr; + } + + /* alloc memory */ + pPipe = createPipeImpl(); + + if (!pPipe) + return nullptr; + + /* create socket */ + pPipe->m_Socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (pPipe->m_Socket < 0) + { + SAL_WARN("sal.osl.pipe", "socket() failed: " << UnixErrnoString(errno)); + destroyPipeImpl(pPipe); + return nullptr; + } + + /* set close-on-exec flag */ + if ((Flags = fcntl(pPipe->m_Socket, F_GETFD, 0)) != -1) + { + Flags |= FD_CLOEXEC; + if (fcntl(pPipe->m_Socket, F_SETFD, Flags) == -1) + { + SAL_WARN("sal.osl.pipe", "fcntl() failed: " << UnixErrnoString(errno)); + } + } + + memset(&addr, 0, sizeof(addr)); + + SAL_INFO("sal.osl.pipe", "new pipe on fd " << pPipe->m_Socket << " '" << name << "'"); + + addr.sun_family = AF_UNIX; + // coverity[fixed_size_dest : FALSE] - safe, see check above + strcpy(addr.sun_path, name.getStr()); +#if defined(FREEBSD) + len = SUN_LEN(&addr); +#else + len = sizeof(addr); +#endif + + if (Options & osl_Pipe_CREATE) + { + struct stat status; + + /* check if there exists an orphan filesystem entry */ + if ((stat(name.getStr(), &status) == 0) && + (S_ISSOCK(status.st_mode) || S_ISFIFO(status.st_mode))) + { + if (connect(pPipe->m_Socket, reinterpret_cast< sockaddr* >(&addr), len) >= 0) + { + close (pPipe->m_Socket); + destroyPipeImpl(pPipe); + return nullptr; + } + + unlink(name.getStr()); + } + + /* ok, fs clean */ + if (bind(pPipe->m_Socket, reinterpret_cast< sockaddr* >(&addr), len) < 0) + { + SAL_WARN("sal.osl.pipe", "bind() failed: " << UnixErrnoString(errno)); + close(pPipe->m_Socket); + destroyPipeImpl(pPipe); + return nullptr; + } + + /* Only give access to all if no security handle was specified, otherwise security + depends on umask */ + + if (!Security) + (void)chmod(name.getStr(),S_IRWXU | S_IRWXG |S_IRWXO); + + strcpy(pPipe->m_Name, name.getStr()); // safe, see check above + + if (listen(pPipe->m_Socket, 5) < 0) + { + SAL_WARN("sal.osl.pipe", "listen() failed: " << UnixErrnoString(errno)); + // cid#1255391 warns about unlink(name) after stat(name, &status) + // above, but the intervening call to bind makes those two clearly + // unrelated, as it would fail if name existed at that point in + // time: + // coverity[toctou] - this is bogus + unlink(name.getStr()); /* remove filesystem entry */ + close(pPipe->m_Socket); + destroyPipeImpl(pPipe); + return nullptr; + } + + return pPipe; + } + + /* osl_pipe_OPEN */ + if (access(name.getStr(), F_OK) != -1) + { + if (connect(pPipe->m_Socket, reinterpret_cast< sockaddr* >(&addr), len) >= 0) + return pPipe; + + SAL_WARN("sal.osl.pipe", "connect() failed: " << UnixErrnoString(errno)); + } + + close (pPipe->m_Socket); + destroyPipeImpl(pPipe); + return nullptr; +} + +void SAL_CALL osl_acquirePipe(oslPipe pPipe) +{ + osl_atomic_increment(&(pPipe->m_nRefCount)); +} + +void SAL_CALL osl_releasePipe(oslPipe pPipe) +{ + if (!pPipe) + return; + + if (osl_atomic_decrement(&(pPipe->m_nRefCount)) == 0) + { + if (!pPipe->m_bClosed) + osl_closePipe(pPipe); + + destroyPipeImpl(pPipe); + } +} + +void SAL_CALL osl_closePipe(oslPipe pPipe) +{ + int nRet; + int ConnFD; + + if (!pPipe) + return; + + if (pPipe->m_bClosed) + return; + + ConnFD = pPipe->m_Socket; + + /* Thread does not return from accept on linux, so + connect to the accepting pipe + */ +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + struct sockaddr_un addr; + + if (pPipe->m_bIsAccepting) + { + pPipe->m_bIsInShutdown = true; + pPipe->m_Socket = -1; + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + { + SAL_WARN("sal.osl.pipe", "socket() failed: " << UnixErrnoString(errno)); + return; + } + + memset(&addr, 0, sizeof(addr)); + + SAL_INFO("sal.osl.pipe", "osl_destroyPipe : Pipe Name '" << pPipe->m_Name << "'"); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, pPipe->m_Name); // safe, as both are same size + + nRet = connect(fd, reinterpret_cast< sockaddr* >(&addr), sizeof(addr)); + if (nRet < 0) + SAL_WARN("sal.osl.pipe", "connect() failed: " << UnixErrnoString(errno)); + + close(fd); + } +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + + nRet = shutdown(ConnFD, 2); + if (nRet < 0) + SAL_WARN("sal.osl.pipe", "shutdown() failed: " << UnixErrnoString(errno)); + + nRet = close(ConnFD); + if (nRet < 0) + SAL_WARN("sal.osl.pipe", "close() failed: " << UnixErrnoString(errno)); + + /* remove filesystem entry */ + if (pPipe->m_Name[0] != '\0') + unlink(pPipe->m_Name); + + pPipe->m_bClosed = true; +} + +oslPipe SAL_CALL osl_acceptPipe(oslPipe pPipe) +{ + int s; + oslPipe pAcceptedPipe; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "invalid pipe"); + if (!pPipe) + return nullptr; + + assert(pPipe->m_Name[0] != '\0'); // you cannot have an empty pipe name + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pPipe->m_bIsAccepting = true; +#endif + + s = accept(pPipe->m_Socket, nullptr, nullptr); + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pPipe->m_bIsAccepting = false; +#endif + + if (s < 0) + { + SAL_WARN("sal.osl.pipe", "accept() failed: " << UnixErrnoString(errno)); + return nullptr; + } + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + if (pPipe->m_bIsInShutdown) + { + close(s); + return nullptr; + } +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + + /* alloc memory */ + pAcceptedPipe = createPipeImpl(); + + assert(pAcceptedPipe); // should never be the case that an oslPipe cannot be initialized + if (!pAcceptedPipe) + { + close(s); + return nullptr; + } + + /* set close-on-exec flag */ + int flags; + if ((flags = fcntl(s, F_GETFD, 0)) >= 0) + { + flags |= FD_CLOEXEC; + if (fcntl(s, F_SETFD, flags) < 0) + SAL_WARN("sal.osl.pipe", "fcntl() failed: " << UnixErrnoString(errno)); + } + + pAcceptedPipe->m_Socket = s; + + return pAcceptedPipe; +} + +sal_Int32 SAL_CALL osl_receivePipe(oslPipe pPipe, + void* pBuffer, + sal_Int32 BytesToRead) +{ + int nRet = 0; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_receivePipe: invalid pipe"); + if (!pPipe) + { + SAL_WARN("sal.osl.pipe", "osl_receivePipe: Invalid socket"); + errno=EINVAL; + return -1; + } + + nRet = recv(pPipe->m_Socket, pBuffer, BytesToRead, 0); + + if (nRet < 0) + SAL_WARN("sal.osl.pipe", "recv() failed: " << UnixErrnoString(errno)); + + return nRet; +} + +sal_Int32 SAL_CALL osl_sendPipe(oslPipe pPipe, + const void* pBuffer, + sal_Int32 BytesToSend) +{ + int nRet=0; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_sendPipe: invalid pipe"); + if (!pPipe) + { + SAL_WARN("sal.osl.pipe", "osl_sendPipe: Invalid socket"); + errno=EINVAL; + return -1; + } + + nRet = send(pPipe->m_Socket, pBuffer, BytesToSend, 0); + + if (nRet <= 0) + SAL_WARN("sal.osl.pipe", "send() failed: " << UnixErrnoString(errno)); + + return nRet; +} + +oslPipeError SAL_CALL osl_getLastPipeError(SAL_UNUSED_PARAMETER oslPipe) +{ + return osl_PipeErrorFromNative(errno); +} + +sal_Int32 SAL_CALL osl_writePipe(oslPipe pPipe, const void *pBuffer, sal_Int32 n) +{ + /* loop until all desired bytes were send or an error occurred */ + sal_Int32 BytesSend = 0; + sal_Int32 BytesToSend = n; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_writePipe: invalid pipe"); // osl_sendPipe detects invalid pipe + while (BytesToSend > 0) + { + sal_Int32 RetVal; + + RetVal= osl_sendPipe(pPipe, pBuffer, BytesToSend); + + /* error occurred? */ + if (RetVal <= 0) + break; + + BytesToSend -= RetVal; + BytesSend += RetVal; + pBuffer= static_cast< char const* >(pBuffer) + RetVal; + } + + return BytesSend; +} + +sal_Int32 SAL_CALL osl_readPipe( oslPipe pPipe, void *pBuffer , sal_Int32 n ) +{ + /* loop until all desired bytes were read or an error occurred */ + sal_Int32 BytesRead = 0; + sal_Int32 BytesToRead = n; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_readPipe: invalid pipe"); // osl_receivePipe detects invalid pipe + while (BytesToRead > 0) + { + sal_Int32 RetVal; + RetVal= osl_receivePipe(pPipe, pBuffer, BytesToRead); + + /* error occurred? */ + if (RetVal <= 0) + break; + + BytesToRead -= RetVal; + BytesRead += RetVal; + pBuffer= static_cast< char* >(pBuffer) + RetVal; + } + + return BytesRead; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/process.cxx b/sal/osl/unx/process.cxx new file mode 100644 index 000000000..f75534fdc --- /dev/null +++ b/sal/osl/unx/process.cxx @@ -0,0 +1,1194 @@ +/* -*- 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 <rtl/ustring.hxx> + +#include <cassert> + +/* + * ToDo: + * - cleanup of process status things + * - cleanup of process spawning + * - cleanup of resource transfer + */ + +#if defined(__sun) + // The procfs may only be used without LFS in 32bits. +# ifdef _FILE_OFFSET_BITS +# undef _FILE_OFFSET_BITS +# endif +#endif + +#if defined(FREEBSD) || defined(NETBSD) || defined(DRAGONFLY) +#include <machine/param.h> +#endif + +#include "system.hxx" +#include "unixerrnostring.hxx" +#if defined(__sun) +# include <sys/procfs.h> +#endif +#include <osl/diagnose.h> +#include <osl/mutex.h> +#include <osl/process.h> +#include <osl/conditn.h> +#include <osl/thread.h> +#include <osl/file.h> +#include <osl/file.hxx> +#include <osl/signal.h> +#include <rtl/alloc.h> +#include <sal/log.hxx> + +#include <grp.h> + +#include "createfilehandlefromfd.hxx" +#include "file_url.hxx" +#include "readwrite_helper.hxx" +#include "sockimpl.hxx" +#include "secimpl.hxx" + +#define MAX_ARGS 255 +#define MAX_ENVS 255 + +namespace +{ + +struct oslProcessImpl { + pid_t m_pid; + oslCondition m_terminated; + int m_status; + oslProcessImpl* m_pnext; +}; + +struct ProcessData +{ + const char* m_pszArgs[MAX_ARGS + 1]; + const char* m_pszDir; + char* m_pszEnv[MAX_ENVS + 1]; + uid_t m_uid; + gid_t m_gid; + char* m_name; + oslCondition m_started; + oslProcessImpl* m_pProcImpl; + oslFileHandle *m_pInputWrite; + oslFileHandle *m_pOutputRead; + oslFileHandle *m_pErrorRead; +}; + +oslProcessImpl* ChildList; +oslMutex ChildListMutex; + +} //Anonymous namespace + +static oslProcessError osl_psz_executeProcess(char *pszImageName, + char *pszArguments[], + oslProcessOption Options, + oslSecurity Security, + char *pszDirectory, + char *pszEnvironments[], + oslProcess *pProcess, + oslFileHandle *pInputWrite, + oslFileHandle *pOutputRead, + oslFileHandle *pErrorRead ); + +extern "C" { + +static void ChildStatusProc(void *pData) +{ + osl_setThreadName("osl_executeProcess"); + + pid_t pid = -1; + int status = 0; + int channel[2] = { -1, -1 }; + ProcessData data; + ProcessData *pdata; + int stdOutput[2] = { -1, -1 }, stdInput[2] = { -1, -1 }, stdError[2] = { -1, -1 }; + + pdata = static_cast<ProcessData *>(pData); + + /* make a copy of our data, because forking will only copy + our local stack of the thread, so the process data will not be accessible + in our child process */ + memcpy(&data, pData, sizeof(data)); + +#ifdef NO_CHILD_PROCESSES +#define fork() (errno = EINVAL, -1) +#endif + if (socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == -1) + { + status = errno; + SAL_WARN("sal.osl", "executeProcess socketpair() errno " << status); + } + + (void) fcntl(channel[0], F_SETFD, FD_CLOEXEC); + (void) fcntl(channel[1], F_SETFD, FD_CLOEXEC); + + /* Create redirected IO pipes */ + if ( status == 0 && data.m_pInputWrite && pipe( stdInput ) == -1 ) + { + status = errno; + assert(status != 0); + SAL_WARN("sal.osl", "executeProcess pipe(stdInput) errno " << status); + } + + if ( status == 0 && data.m_pOutputRead && pipe( stdOutput ) == -1 ) + { + status = errno; + assert(status != 0); + SAL_WARN("sal.osl", "executeProcess pipe(stdOutput) errno " << status); + } + + if ( status == 0 && data.m_pErrorRead && pipe( stdError ) == -1 ) + { + status = errno; + assert(status != 0); + SAL_WARN("sal.osl", "executeProcess pipe(stdError) errno " << status); + } + + if ( (status == 0) && ((pid = fork()) == 0) ) + { + /* Child */ + int chstatus = 0; + int errno_copy; + + if (channel[0] != -1) close(channel[0]); + + if ((data.m_uid != uid_t(-1)) && ((data.m_uid != getuid()) || (data.m_gid != getgid()))) + { + OSL_ASSERT(geteuid() == 0); /* must be root */ + + if (! INIT_GROUPS(data.m_name, data.m_gid) || (setuid(data.m_uid) != 0)) + { + // ignore; can't do much about it here after fork + } + + unsetenv("HOME"); + } + + if (data.m_pszDir) + chstatus = chdir(data.m_pszDir); + + if (chstatus == 0 && ((data.m_uid == uid_t(-1)) || ((data.m_uid == getuid()) && (data.m_gid == getgid())))) + { + int i; + for (i = 0; data.m_pszEnv[i] != nullptr; i++) + { + if (strchr(data.m_pszEnv[i], '=') == nullptr) + { + unsetenv(data.m_pszEnv[i]); /*TODO: check error return*/ + } + else + { + putenv(data.m_pszEnv[i]); /*TODO: check error return*/ + } + } + + /* Connect std IO to pipe ends */ + + /* Write end of stdInput not used in child process */ + if (stdInput[1] != -1) close( stdInput[1] ); + + /* Read end of stdOutput not used in child process */ + if (stdOutput[0] != -1) close( stdOutput[0] ); + + /* Read end of stdError not used in child process */ + if (stdError[0] != -1) close( stdError[0] ); + + /* Redirect pipe ends to std IO */ + + if ( stdInput[0] != STDIN_FILENO ) + { + dup2( stdInput[0], STDIN_FILENO ); + if (stdInput[0] != -1) close( stdInput[0] ); + } + + if ( stdOutput[1] != STDOUT_FILENO ) + { + dup2( stdOutput[1], STDOUT_FILENO ); + if (stdOutput[1] != -1) close( stdOutput[1] ); + } + + if ( stdError[1] != STDERR_FILENO ) + { + dup2( stdError[1], STDERR_FILENO ); + if (stdError[1] != -1) close( stdError[1] ); + } + + // No need to check the return value of execv. If we return from + // it, an error has occurred. + execv(data.m_pszArgs[0], const_cast<char **>(data.m_pszArgs)); + } + + /* if we reach here, something went wrong */ + errno_copy = errno; + if ( !safeWrite(channel[1], &errno_copy, sizeof(errno_copy)) ) + { + // ignore; can't do much about it here after fork + } + + if ( channel[1] != -1 ) + close(channel[1]); + + _exit(255); + } + else + { /* Parent */ + int i = -1; + if (channel[1] != -1) close(channel[1]); + + /* Close unused pipe ends */ + if (stdInput[0] != -1) close( stdInput[0] ); + if (stdOutput[1] != -1) close( stdOutput[1] ); + if (stdError[1] != -1) close( stdError[1] ); + + if (pid > 0) + { + while ((i = read(channel[0], &status, sizeof(status))) < 0) + { + if (errno != EINTR) + break; + } + } + + if (channel[0] != -1) close(channel[0]); + + if ((pid > 0) && (i == 0)) + { + pid_t child_pid; + osl_acquireMutex(ChildListMutex); + + pdata->m_pProcImpl->m_pid = pid; + pdata->m_pProcImpl->m_pnext = ChildList; + ChildList = pdata->m_pProcImpl; + + /* Store used pipe ends in data structure */ + + if ( pdata->m_pInputWrite ) + *(pdata->m_pInputWrite) = osl::detail::createFileHandleFromFD( stdInput[1] ); + + if ( pdata->m_pOutputRead ) + *(pdata->m_pOutputRead) = osl::detail::createFileHandleFromFD( stdOutput[0] ); + + if ( pdata->m_pErrorRead ) + *(pdata->m_pErrorRead) = osl::detail::createFileHandleFromFD( stdError[0] ); + + osl_releaseMutex(ChildListMutex); + + osl_setCondition(pdata->m_started); + + do + { + child_pid = waitpid(pid, &status, 0); + } while ( 0 > child_pid && EINTR == errno ); + + if ( child_pid < 0) + { + SAL_WARN("sal.osl", "Failed to wait for child process: " << UnixErrnoString(errno)); + + /* + We got another error than EINTR. Anyway we have to wake up the + waiting thread under any circumstances */ + + child_pid = pid; + } + + if ( child_pid > 0 ) + { + oslProcessImpl* pChild; + + osl_acquireMutex(ChildListMutex); + + pChild = ChildList; + + /* check if it is one of our child processes */ + while (pChild != nullptr) + { + if (pChild->m_pid == child_pid) + { + if (WIFEXITED(status)) + pChild->m_status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + pChild->m_status = 128 + WTERMSIG(status); + else + pChild->m_status = -1; + + osl_setCondition(pChild->m_terminated); + } + + pChild = pChild->m_pnext; + } + + osl_releaseMutex(ChildListMutex); + } + } + else + { + SAL_WARN("sal.osl", "ChildStatusProc : starting '" << data.m_pszArgs[0] << "' failed"); + SAL_WARN("sal.osl", "Failed to launch child process, child reports " << UnixErrnoString(status)); + + /* Close pipe ends */ + if ( pdata->m_pInputWrite ) + *pdata->m_pInputWrite = nullptr; + + if ( pdata->m_pOutputRead ) + *pdata->m_pOutputRead = nullptr; + + if ( pdata->m_pErrorRead ) + *pdata->m_pErrorRead = nullptr; + + if (stdInput[1] != -1) close( stdInput[1] ); + if (stdOutput[0] != -1) close( stdOutput[0] ); + if (stdError[0] != -1) close( stdError[0] ); + + /* if pid > 0 then a process was created, even if it later failed + e.g. bash searching for a command to execute, and we still + need to clean it up to avoid "defunct" processes */ + if (pid > 0) + { + pid_t child_pid; + do + { + child_pid = waitpid(pid, &status, 0); + } while ( 0 > child_pid && EINTR == errno ); + } + + /* notify (and unblock) parent thread */ + osl_setCondition(pdata->m_started); + } + } +} + +} + +oslProcessError SAL_CALL osl_executeProcess_WithRedirectedIO( + rtl_uString *ustrImageName, + rtl_uString *ustrArguments[], + sal_uInt32 nArguments, + oslProcessOption Options, + oslSecurity Security, + rtl_uString *ustrWorkDir, + rtl_uString *ustrEnvironment[], + sal_uInt32 nEnvironmentVars, + oslProcess *pProcess, + oslFileHandle *pInputWrite, + oslFileHandle *pOutputRead, + oslFileHandle *pErrorRead + ) +{ + OUString image; + if (ustrImageName == nullptr) + { + if (nArguments == 0) + { + return osl_Process_E_InvalidError; + } + image = OUString::unacquired(ustrArguments); + } + else + { + osl::FileBase::RC e = osl::FileBase::getSystemPathFromFileURL( + OUString::unacquired(&ustrImageName), image); + if (e != osl::FileBase::E_None) + { + SAL_INFO( + "sal.osl", + "getSystemPathFromFileURL(" + << OUString::unacquired(&ustrImageName) + << ") failed with " << e); + return osl_Process_E_Unknown; + } + } + + if ((Options & osl_Process_SEARCHPATH) != 0) + { + OUString path; + if (osl::detail::find_in_PATH(image, path)) + { + image = path; + } + } + + oslProcessError Error; + char* pszWorkDir=nullptr; + char** pArguments=nullptr; + char** pEnvironment=nullptr; + unsigned int idx; + + char szImagePath[PATH_MAX] = ""; + if (!image.isEmpty() + && (UnicodeToText( + szImagePath, SAL_N_ELEMENTS(szImagePath), image.getStr(), + image.getLength()) + == 0)) + { + int e = errno; + SAL_INFO("sal.osl", "UnicodeToText(" << image << ") failed with " << e); + return osl_Process_E_Unknown; + } + + char szWorkDir[PATH_MAX] = ""; + if ( ustrWorkDir != nullptr && ustrWorkDir->length ) + { + oslFileError e = FileURLToPath( szWorkDir, PATH_MAX, ustrWorkDir ); + if (e != osl_File_E_None) + { + SAL_INFO( + "sal.osl", + "FileURLToPath(" << OUString::unacquired(&ustrWorkDir) + << ") failed with " << e); + return osl_Process_E_Unknown; + } + pszWorkDir = szWorkDir; + } + + if ( pArguments == nullptr && nArguments > 0 ) + { + pArguments = static_cast<char**>(malloc( ( nArguments + 2 ) * sizeof(char*) )); + } + + for ( idx = 0 ; idx < nArguments ; ++idx ) + { + rtl_String* strArg =nullptr; + + rtl_uString2String( &strArg, + rtl_uString_getStr(ustrArguments[idx]), + rtl_uString_getLength(ustrArguments[idx]), + osl_getThreadTextEncoding(), + OUSTRING_TO_OSTRING_CVTFLAGS ); + + pArguments[idx]=strdup(rtl_string_getStr(strArg)); + rtl_string_release(strArg); + pArguments[idx+1]=nullptr; + } + + for ( idx = 0 ; idx < nEnvironmentVars ; ++idx ) + { + rtl_String* strEnv=nullptr; + + if ( pEnvironment == nullptr ) + { + pEnvironment = static_cast<char**>(malloc( ( nEnvironmentVars + 2 ) * sizeof(char*) )); + } + + rtl_uString2String( &strEnv, + rtl_uString_getStr(ustrEnvironment[idx]), + rtl_uString_getLength(ustrEnvironment[idx]), + osl_getThreadTextEncoding(), + OUSTRING_TO_OSTRING_CVTFLAGS ); + + pEnvironment[idx]=strdup(rtl_string_getStr(strEnv)); + rtl_string_release(strEnv); + pEnvironment[idx+1]=nullptr; + } + + Error = osl_psz_executeProcess(szImagePath, + pArguments, + Options, + Security, + pszWorkDir, + pEnvironment, + pProcess, + pInputWrite, + pOutputRead, + pErrorRead + ); + + if ( pArguments != nullptr ) + { + for ( idx = 0 ; idx < nArguments ; ++idx ) + { + if ( pArguments[idx] != nullptr ) + { + free(pArguments[idx]); + } + } + free(pArguments); + } + + if ( pEnvironment != nullptr ) + { + for ( idx = 0 ; idx < nEnvironmentVars ; ++idx ) + { + if ( pEnvironment[idx] != nullptr ) + { + free(pEnvironment[idx]); + } + } + free(pEnvironment); + } + + return Error; +} + +oslProcessError SAL_CALL osl_executeProcess( + rtl_uString *ustrImageName, + rtl_uString *ustrArguments[], + sal_uInt32 nArguments, + oslProcessOption Options, + oslSecurity Security, + rtl_uString *ustrWorkDir, + rtl_uString *ustrEnvironment[], + sal_uInt32 nEnvironmentVars, + oslProcess *pProcess + ) +{ + return osl_executeProcess_WithRedirectedIO( + ustrImageName, + ustrArguments, + nArguments, + Options, + Security, + ustrWorkDir, + ustrEnvironment, + nEnvironmentVars, + pProcess, + nullptr, + nullptr, + nullptr + ); +} + +oslProcessError osl_psz_executeProcess(char *pszImageName, + char *pszArguments[], + oslProcessOption Options, + oslSecurity Security, + char *pszDirectory, + char *pszEnvironments[], + oslProcess *pProcess, + oslFileHandle *pInputWrite, + oslFileHandle *pOutputRead, + oslFileHandle *pErrorRead + ) +{ + int i; + ProcessData Data; + oslThread hThread; + + memset(&Data,0,sizeof(ProcessData)); + Data.m_pInputWrite = pInputWrite; + Data.m_pOutputRead = pOutputRead; + Data.m_pErrorRead = pErrorRead; + + OSL_ASSERT(pszImageName != nullptr); + + if ( pszImageName == nullptr ) + { + return osl_Process_E_NotFound; + } + + Data.m_pszArgs[0] = strdup(pszImageName); + Data.m_pszArgs[1] = nullptr; + + if ( pszArguments != nullptr ) + { + for (i = 0; ((i + 2) < MAX_ARGS) && (pszArguments[i] != nullptr); i++) + Data.m_pszArgs[i+1] = strdup(pszArguments[i]); + Data.m_pszArgs[i+2] = nullptr; + } + + Data.m_pszDir = (pszDirectory != nullptr) ? strdup(pszDirectory) : nullptr; + + if (pszEnvironments != nullptr) + { + for (i = 0; ((i + 1) < MAX_ENVS) && (pszEnvironments[i] != nullptr); i++) + Data.m_pszEnv[i] = strdup(pszEnvironments[i]); + Data.m_pszEnv[i+1] = nullptr; + } + else + Data.m_pszEnv[0] = nullptr; + + if (Security != nullptr) + { + Data.m_uid = static_cast<oslSecurityImpl*>(Security)->m_pPasswd.pw_uid; + Data.m_gid = static_cast<oslSecurityImpl*>(Security)->m_pPasswd.pw_gid; + Data.m_name = static_cast<oslSecurityImpl*>(Security)->m_pPasswd.pw_name; + } + else + Data.m_uid = uid_t(-1); + + Data.m_pProcImpl = static_cast<oslProcessImpl*>(malloc(sizeof(oslProcessImpl))); + Data.m_pProcImpl->m_pid = 0; + Data.m_pProcImpl->m_terminated = osl_createCondition(); + Data.m_pProcImpl->m_pnext = nullptr; + + if (ChildListMutex == nullptr) + ChildListMutex = osl_createMutex(); + + Data.m_started = osl_createCondition(); + + hThread = osl_createThread(ChildStatusProc, &Data); + + if (hThread != nullptr) + { + osl_waitCondition(Data.m_started, nullptr); + } + osl_destroyCondition(Data.m_started); + + for (i = 0; Data.m_pszArgs[i] != nullptr; i++) + free(const_cast<char *>(Data.m_pszArgs[i])); + + for (i = 0; Data.m_pszEnv[i] != nullptr; i++) + free(Data.m_pszEnv[i]); + + if ( Data.m_pszDir != nullptr ) + { + free(const_cast<char *>(Data.m_pszDir)); + } + + osl_destroyThread(hThread); + + if (Data.m_pProcImpl->m_pid != 0) + { + assert(hThread != nullptr); + + *pProcess = Data.m_pProcImpl; + + if (Options & osl_Process_WAIT) + osl_joinProcess(*pProcess); + + return osl_Process_E_None; + } + + osl_destroyCondition(Data.m_pProcImpl->m_terminated); + free(Data.m_pProcImpl); + + return osl_Process_E_Unknown; +} + +oslProcessError SAL_CALL osl_terminateProcess(oslProcess Process) +{ + if (Process == nullptr) + return osl_Process_E_Unknown; + + if (kill(static_cast<oslProcessImpl*>(Process)->m_pid, SIGKILL) != 0) + { + switch (errno) + { + case EPERM: + return osl_Process_E_NoPermission; + + case ESRCH: + return osl_Process_E_NotFound; + + default: + return osl_Process_E_Unknown; + } + } + + return osl_Process_E_None; +} + +oslProcess SAL_CALL osl_getProcess(oslProcessIdentifier Ident) +{ + oslProcessImpl *pProcImpl; + + if (kill(Ident, 0) != -1) + { + oslProcessImpl* pChild; + + if (ChildListMutex == nullptr) + ChildListMutex = osl_createMutex(); + + osl_acquireMutex(ChildListMutex); + + pChild = ChildList; + + /* check if it is one of our child processes */ + while (pChild != nullptr) + { + if (Ident == static_cast<sal_uInt32>(pChild->m_pid)) + break; + + pChild = pChild->m_pnext; + } + + pProcImpl = static_cast<oslProcessImpl*>(malloc(sizeof(oslProcessImpl))); + pProcImpl->m_pid = Ident; + pProcImpl->m_terminated = osl_createCondition(); + + if (pChild != nullptr) + { + /* process is a child so insert into list */ + pProcImpl->m_pnext = pChild->m_pnext; + pChild->m_pnext = pProcImpl; + + pProcImpl->m_status = pChild->m_status; + + if (osl_checkCondition(pChild->m_terminated)) + osl_setCondition(pProcImpl->m_terminated); + } + else + pProcImpl->m_pnext = nullptr; + + osl_releaseMutex(ChildListMutex); + } + else + pProcImpl = nullptr; + + return pProcImpl; +} + +void SAL_CALL osl_freeProcessHandle(oslProcess Process) +{ + if (Process == nullptr) + return; + + oslProcessImpl *pChild, *pPrev = nullptr; + + OSL_ASSERT(ChildListMutex != nullptr); + + if ( ChildListMutex == nullptr ) + { + return; + } + + osl_acquireMutex(ChildListMutex); + + pChild = ChildList; + + /* remove process from child list */ + while (pChild != nullptr) + { + if (pChild == static_cast<oslProcessImpl*>(Process)) + { + if (pPrev != nullptr) + pPrev->m_pnext = pChild->m_pnext; + else + ChildList = pChild->m_pnext; + + break; + } + + pPrev = pChild; + pChild = pChild->m_pnext; + } + + osl_releaseMutex(ChildListMutex); + + osl_destroyCondition(static_cast<oslProcessImpl*>(Process)->m_terminated); + + free(Process); +} + +#if defined(LINUX) +namespace { + +struct osl_procStat +{ + /* from 'stat' */ + pid_t pid; /* pid */ + char command[16]; /* 'argv[0]' */ /* mfe: it all right char comm[16] in kernel! */ + char state; /* state (running, stopped, ...) */ + pid_t ppid; /* parent pid */ + pid_t pgrp; /* parent group */ + int session; /* session ID */ + int tty; /* no of tty */ + pid_t tpgid; /* group of process owning the tty */ + unsigned long flags; /* flags dunno */ + unsigned long minflt; /* minor page faults */ + unsigned long cminflt; /* minor page faults with children */ + unsigned long majflt; /* major page faults */ + unsigned long cmajflt; /* major page faults with children */ + unsigned long utime; /* no of jiffies in user mode */ + unsigned long stime; /* no of jiffies in kernel mode */ + unsigned long cutime; /* no of jiffies in user mode with children */ + unsigned long cstime; /* no of jiffies in kernel mode with children */ + unsigned long priority; /* nice value + 15 (kernel scheduling prio)*/ + long nice; /* nice value */ + long timeout; /* no of jiffies of next process timeout */ + long itrealvalue; /* no jiffies before next SIGALRM */ + unsigned long starttime; /* process started this no of jiffies after boot */ + unsigned long vsize; /* virtual memory size (in bytes) */ + long rss; /* resident set size (in pages) */ + unsigned long rss_rlim; /* rss limit (in bytes) */ + unsigned long startcode; /* address above program text can run */ + unsigned long endcode; /* address below program text can run */ + unsigned long startstack; /* address of start of stack */ + unsigned long kstkesp; /* current value of 'esp' (stack pointer) */ + unsigned long kstkeip; /* current value of 'eip' (instruction pointer) */ + /* mfe: Linux > 2.1.7x have more signals (88) */ + char signal[24]; /* pending signals */ + char blocked[24]; /* blocked signals */ + char sigignore[24]; /* ignored signals */ + char sigcatch[24]; /* caught signals */ + unsigned long wchan; /* 'channel' the process is waiting in */ + unsigned long nswap; /* ? */ + unsigned long cnswap; /* ? */ + + /* from 'status' */ + int ruid; /* real uid */ + int euid; /* effective uid */ + int suid; /* saved uid */ + int fuid; /* file access uid */ + int rgid; /* real gid */ + int egid; /* effective gid */ + int sgid; /* saved gid */ + int fgid; /* file access gid */ + unsigned long vm_size; /* like vsize but on kb */ + unsigned long vm_lock; /* locked pages in kb */ + unsigned long vm_rss; /* like rss but in kb */ + unsigned long vm_data; /* data size */ + unsigned long vm_stack; /* stack size */ + unsigned long vm_exe; /* executable size */ + unsigned long vm_lib; /* library size */ +}; + +} + +static bool osl_getProcStat(pid_t pid, struct osl_procStat* procstat) +{ + int fd = 0; + bool bRet = false; + char name[PATH_MAX + 1]; + snprintf(name, sizeof(name), "/proc/%u/stat", pid); + + if ((fd = open(name,O_RDONLY)) >=0 ) + { + char* tmp=nullptr; + char prstatbuf[512]; + memset(prstatbuf,0,512); + bRet = safeRead(fd, prstatbuf, 511); + + close(fd); + + if (!bRet) + return false; + + tmp = strrchr(prstatbuf, ')'); + if(tmp) + { + *tmp = '\0'; + + memset(procstat->command, 0, sizeof(procstat->command)); + + sscanf(prstatbuf, "%d (%15c", &procstat->pid, procstat->command); + sscanf(tmp + 2, + "%c" + "%i %i %i %i %i" + "%lu %lu %lu %lu %lu" + "%lu %lu %lu %lu" + "%lu %li %li %li" + "%lu %lu %li %lu" + "%lu %lu %lu %lu %lu" + "%23s %23s %23s %23s" + "%lu %lu %lu", + &procstat->state, + &procstat->ppid, &procstat->pgrp, &procstat->session, &procstat->tty, &procstat->tpgid, + &procstat->flags, &procstat->minflt, &procstat->cminflt, &procstat->majflt, &procstat->cmajflt, + &procstat->utime, &procstat->stime, &procstat->cutime, &procstat->cstime, + &procstat->priority, &procstat->nice, &procstat->timeout, &procstat->itrealvalue, + &procstat->starttime, &procstat->vsize, &procstat->rss, &procstat->rss_rlim, + &procstat->startcode, &procstat->endcode, &procstat->startstack, &procstat->kstkesp, &procstat->kstkeip, + procstat->signal, procstat->blocked, procstat->sigignore, procstat->sigcatch, + &procstat->wchan, &procstat->nswap, &procstat->cnswap + ); + } + else + { + bRet = false; + } + } + return bRet; +} + +static bool osl_getProcStatus(pid_t pid, struct osl_procStat* procstat) +{ + int fd = 0; + char name[PATH_MAX + 1]; + bool bRet = false; + + snprintf(name, sizeof(name), "/proc/%u/status", pid); + + if ((fd = open(name,O_RDONLY)) >=0 ) + { + char* tmp=nullptr; + char prstatusbuf[512]; + memset(prstatusbuf,0,512); + bRet = safeRead(fd, prstatusbuf, 511); + + close(fd); + + if (!bRet) + return false; + + tmp = strstr(prstatusbuf,"Uid:"); + if(tmp) + { + sscanf(tmp,"Uid:\t%d\t%d\t%d\t%d", + &procstat->ruid, &procstat->euid, &procstat->suid, &procstat->fuid + ); + } + + tmp = strstr(prstatusbuf,"Gid:"); + if(tmp) + { + sscanf(tmp,"Gid:\t%d\t%d\t%d\t%d", + &procstat->rgid, &procstat->egid, &procstat->sgid, &procstat->fgid + ); + } + + tmp = strstr(prstatusbuf,"VmSize:"); + if(tmp) + { + sscanf(tmp, + "VmSize: %lu kB\n" + "VmLck: %lu kB\n" + "VmRSS: %lu kB\n" + "VmData: %lu kB\n" + "VmStk: %lu kB\n" + "VmExe: %lu kB\n" + "VmLib: %lu kB\n", + &procstat->vm_size, &procstat->vm_lock, &procstat->vm_rss, &procstat->vm_data, + &procstat->vm_stack, &procstat->vm_exe, &procstat->vm_lib + ); + } + + tmp = strstr(prstatusbuf,"SigPnd:"); + if(tmp) + { + sscanf(tmp, "SigPnd: %23s SigBlk: %23s SigIgn: %23s %*s %23s", + procstat->signal, procstat->blocked, procstat->sigignore, procstat->sigcatch + ); + } + } + return bRet; +} + +#endif + +oslProcessError SAL_CALL osl_getProcessInfo(oslProcess Process, oslProcessData Fields, oslProcessInfo* pInfo) +{ + pid_t pid; + + if (Process == nullptr) + pid = getpid(); + else + pid = static_cast<oslProcessImpl*>(Process)->m_pid; + + if (! pInfo || (pInfo->Size != sizeof(oslProcessInfo))) + return osl_Process_E_Unknown; + + pInfo->Fields = 0; + + if (Fields & osl_Process_IDENTIFIER) + { + pInfo->Ident = pid; + pInfo->Fields |= osl_Process_IDENTIFIER; + } + + if (Fields & osl_Process_EXITCODE) + { + if ((Process != nullptr) && + osl_checkCondition(static_cast<oslProcessImpl*>(Process)->m_terminated)) + { + pInfo->Code = static_cast<oslProcessImpl*>(Process)->m_status; + pInfo->Fields |= osl_Process_EXITCODE; + } + } + + if (Fields & (osl_Process_HEAPUSAGE | osl_Process_CPUTIMES)) + { + +#if defined(__sun) + + int fd; + char name[PATH_MAX + 1]; + + snprintf(name, sizeof(name), "/proc/%ld", (long)pid); + + if ((fd = open(name, O_RDONLY)) >= 0) + { + prstatus_t prstatus; + + if (ioctl(fd, PIOCSTATUS, &prstatus) >= 0) + { + if (Fields & osl_Process_CPUTIMES) + { + pInfo->UserTime.Seconds = prstatus.pr_utime.tv_sec; + pInfo->UserTime.Nanosec = prstatus.pr_utime.tv_nsec; + pInfo->SystemTime.Seconds = prstatus.pr_stime.tv_sec; + pInfo->SystemTime.Nanosec = prstatus.pr_stime.tv_nsec; + + pInfo->Fields |= osl_Process_CPUTIMES; + } + + if (Fields & osl_Process_HEAPUSAGE) + { + pInfo->HeapUsage = prstatus.pr_brksize; + + pInfo->Fields |= osl_Process_HEAPUSAGE; + } + + close(fd); + + return (pInfo->Fields == Fields) ? osl_Process_E_None : osl_Process_E_Unknown; + } + else + close(fd); + } + +#elif defined(LINUX) + + if ( (Fields & osl_Process_CPUTIMES) || (Fields & osl_Process_HEAPUSAGE) ) + { + struct osl_procStat procstat; + memset(&procstat,0,sizeof(procstat)); + + if ( (Fields & osl_Process_CPUTIMES) && osl_getProcStat(pid, &procstat) ) + { + /* + * mfe: + * We calculate only time of the process proper. + * Threads are processes, we do not consider their time here! + * (For this, cutime and cstime should be used, it seems not + * to work in 2.0.36) + */ + + long clktck; + unsigned long hz; + unsigned long userseconds; + unsigned long systemseconds; + + clktck = sysconf(_SC_CLK_TCK); + if (clktck <= 0) { + return osl_Process_E_Unknown; + } + hz = static_cast<unsigned long>(clktck); + + userseconds = procstat.utime/hz; + systemseconds = procstat.stime/hz; + + pInfo->UserTime.Seconds = userseconds; + pInfo->UserTime.Nanosec = procstat.utime - (userseconds * hz); + pInfo->SystemTime.Seconds = systemseconds; + pInfo->SystemTime.Nanosec = procstat.stime - (systemseconds * hz); + + pInfo->Fields |= osl_Process_CPUTIMES; + } + + if ( (Fields & osl_Process_HEAPUSAGE) && osl_getProcStatus(pid, &procstat) ) + { + /* + * mfe: + * vm_data (found in status) shows the size of the data segment + * it a rough approximation of the core heap size + */ + pInfo->HeapUsage = procstat.vm_data*1024; + + pInfo->Fields |= osl_Process_HEAPUSAGE; + } + } + + return (pInfo->Fields == Fields) ? osl_Process_E_None : osl_Process_E_Unknown; +#endif + + } + + return (pInfo->Fields == Fields) ? osl_Process_E_None : osl_Process_E_Unknown; +} + +/** Helper function for osl_joinProcessWithTimeout + */ + +static bool is_timeout(const struct timeval* tend) +{ + struct timeval tcurrent; + gettimeofday(&tcurrent, nullptr); + return (tcurrent.tv_sec >= tend->tv_sec); +} + +/* kill(pid, 0) is useful for checking if a + process is still alive, but remember that + kill even returns 0 if the process is already + a zombie. */ + +static bool is_process_dead(pid_t pid) +{ + return ((kill(pid, 0) == -1) && (ESRCH == errno)); +} + +oslProcessError SAL_CALL osl_joinProcessWithTimeout(oslProcess Process, const TimeValue* pTimeout) +{ + oslProcessImpl* pChild = ChildList; + oslProcessError osl_error = osl_Process_E_None; + + OSL_PRECOND(Process, "osl_joinProcess: Invalid parameter"); + OSL_ASSERT(ChildListMutex); + + if (Process == nullptr || ChildListMutex == nullptr) + return osl_Process_E_Unknown; + + osl_acquireMutex(ChildListMutex); + + /* check if process is a child of ours */ + while (pChild != nullptr) + { + if (pChild == static_cast<oslProcessImpl*>(Process)) + break; + + pChild = pChild->m_pnext; + } + + osl_releaseMutex(ChildListMutex); + + if (pChild != nullptr) + { + oslConditionResult cond_res = osl_waitCondition(pChild->m_terminated, pTimeout); + + if (cond_res == osl_cond_result_timeout) + osl_error = osl_Process_E_TimedOut; + else if (cond_res != osl_cond_result_ok) + osl_error = osl_Process_E_Unknown; + } + else /* alien process; StatusThread will not be able + to set the condition terminated */ + { + pid_t pid = static_cast<oslProcessImpl*>(Process)->m_pid; + + if (pTimeout) + { + bool timeout = false; + struct timeval tend; + + gettimeofday(&tend, nullptr); + + tend.tv_sec += pTimeout->Seconds; + + while (!is_process_dead(pid) && !(timeout = is_timeout(&tend))) + sleep(1); + + if (timeout) + osl_error = osl_Process_E_TimedOut; + } + else /* infinite */ + { + while (!is_process_dead(pid)) + sleep(1); + } + } + return osl_error; +} + +oslProcessError SAL_CALL osl_joinProcess(oslProcess Process) +{ + return osl_joinProcessWithTimeout(Process, nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/process_impl.cxx b/sal/osl/unx/process_impl.cxx new file mode 100644 index 000000000..5d7f7644f --- /dev/null +++ b/sal/osl/unx/process_impl.cxx @@ -0,0 +1,473 @@ +/* -*- 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 <osl/process.h> + +#include <limits.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/module.h> +#include <osl/thread.h> +#include <rtl/alloc.h> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.h> +#include <sal/log.hxx> + +#include "file_path_helper.hxx" + +#include "uunxapi.hxx" +#include "nlsupport.hxx" + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +#if defined(MACOSX) || defined(IOS) +#include <mach-o/dyld.h> + +namespace { + +oslProcessError bootstrap_getExecutableFile(rtl_uString ** ppFileURL) +{ + oslProcessError result = osl_Process_E_NotFound; + + char buffer[PATH_MAX]; + uint32_t buflen = sizeof(buffer); + + if (_NSGetExecutablePath (buffer, &buflen) == 0) + { + /* Determine absolute path. */ + char abspath[PATH_MAX]; + if (realpath (buffer, abspath) != nullptr) + { + /* Convert from utf8 to unicode. */ + rtl_uString * pAbsPath = nullptr; + rtl_string2UString ( + &pAbsPath, + abspath, rtl_str_getLength (abspath), + RTL_TEXTENCODING_UTF8, + OSTRING_TO_OUSTRING_CVTFLAGS); + + if (pAbsPath) + { + /* Convert from path to url. */ + if (osl_getFileURLFromSystemPath (pAbsPath, ppFileURL) == osl_File_E_None) + { + /* Success. */ + result = osl_Process_E_None; + } + rtl_uString_release (pAbsPath); + } + } + } + + return result; +} + +} + +#else +#include <dlfcn.h> + +namespace { + +oslProcessError bootstrap_getExecutableFile(rtl_uString ** ppFileURL) +{ + oslProcessError result = osl_Process_E_NotFound; + +#ifdef ANDROID + /* Now with just a single DSO, this one from lo-bootstrap.c is as good as + * any */ + void * addr = dlsym (RTLD_DEFAULT, "JNI_OnLoad"); +#else +#if defined __linux + // The below code looking for "main" with dlsym() will typically + // fail, as there is little reason for "main" to be exported, in + // the dlsym() sense, from an executable. But Linux has + // /proc/self/exe, try using that. + char buf[PATH_MAX]; + int rc = readlink("/proc/self/exe", buf, sizeof(buf)); + if (rc > 0 && rc < PATH_MAX) + { + buf[rc] = '\0'; + OUString path = OUString::fromUtf8(buf); + OUString fileURL; + if (osl::File::getFileURLFromSystemPath(path, fileURL) == osl::File::E_None) + { + rtl_uString_acquire(fileURL.pData); + *ppFileURL = fileURL.pData; + return osl_Process_E_None; + } + } +#endif + /* Determine address of "main()" function. */ + void * addr = dlsym (RTLD_DEFAULT, "main"); +#endif + if (addr != nullptr) + { + /* Determine module URL. */ + if (osl_getModuleURLFromAddress (addr, ppFileURL)) + { + /* Success. */ + result = osl_Process_E_None; + } + } + + return result; +} + +} + +#endif + +namespace { + +struct CommandArgs_Impl +{ + pthread_mutex_t m_mutex; + sal_uInt32 m_nCount; + rtl_uString ** m_ppArgs; +}; + +} + +static struct CommandArgs_Impl g_command_args = +{ + PTHREAD_MUTEX_INITIALIZER, + 0, + nullptr +}; + +oslProcessError SAL_CALL osl_getExecutableFile (rtl_uString ** ppustrFile) +{ + pthread_mutex_lock (&(g_command_args.m_mutex)); + if (g_command_args.m_nCount == 0) + { + pthread_mutex_unlock (&(g_command_args.m_mutex)); + return bootstrap_getExecutableFile(ppustrFile); + } + + /* CommandArgs set. Obtain argv[0]. */ + rtl_uString_assign (ppustrFile, g_command_args.m_ppArgs[0]); + pthread_mutex_unlock (&(g_command_args.m_mutex)); + return osl_Process_E_None; +} + +sal_uInt32 SAL_CALL osl_getCommandArgCount() +{ + sal_uInt32 result = 0; + + pthread_mutex_lock (&(g_command_args.m_mutex)); + SAL_INFO_IF( + g_command_args.m_nCount == 0, "sal.osl", + "osl_getCommandArgCount w/o prior call to osl_setCommandArgs"); + if (g_command_args.m_nCount > 0) + result = g_command_args.m_nCount - 1; + pthread_mutex_unlock (&(g_command_args.m_mutex)); + + return result; +} + +oslProcessError SAL_CALL osl_getCommandArg (sal_uInt32 nArg, rtl_uString ** strCommandArg) +{ + oslProcessError result = osl_Process_E_NotFound; + + pthread_mutex_lock (&(g_command_args.m_mutex)); + assert(g_command_args.m_nCount > 0); + if (g_command_args.m_nCount > (nArg + 1)) + { + rtl_uString_assign (strCommandArg, g_command_args.m_ppArgs[nArg + 1]); + result = osl_Process_E_None; + } + pthread_mutex_unlock (&(g_command_args.m_mutex)); + + return result; +} + +void SAL_CALL osl_setCommandArgs (int argc, char ** argv) +{ + assert(argc > 0); + pthread_mutex_lock (&(g_command_args.m_mutex)); + SAL_WARN_IF(g_command_args.m_nCount != 0, "sal.osl", "args already set"); + if (g_command_args.m_nCount == 0) + { + rtl_uString** ppArgs = static_cast<rtl_uString**>(rtl_allocateZeroMemory (argc * sizeof(rtl_uString*))); + if (ppArgs != nullptr) + { + rtl_TextEncoding encoding = osl_getThreadTextEncoding(); + for (int i = 0; i < argc; i++) + { + rtl_string2UString ( + &(ppArgs[i]), + argv[i], rtl_str_getLength (argv[i]), encoding, + OSTRING_TO_OUSTRING_CVTFLAGS); + } + if (ppArgs[0] != nullptr) + { +#if HAVE_FEATURE_MACOSX_SANDBOX + // If we are called with a relative path in argv[0] in a sandboxed process + // osl::realpath() fails. So just use bootstrap_getExecutableFile() instead. + // Somewhat silly to use argv[0] and tediously figure out the absolute path from it + // anyway. + bootstrap_getExecutableFile(&ppArgs[0]); + OUString pArg0(ppArgs[0]); + osl_getFileURLFromSystemPath (pArg0.pData, &(ppArgs[0])); +#else +#if !defined(ANDROID) && !defined(IOS) // No use searching PATH on Android or iOS + /* see @ osl_getExecutableFile(). */ + if (rtl_ustr_indexOfChar (rtl_uString_getStr(ppArgs[0]), '/') == -1) + { + const OUString PATH ("PATH"); + + rtl_uString * pSearchPath = nullptr; + osl_getEnvironment (PATH.pData, &pSearchPath); + if (pSearchPath) + { + rtl_uString * pSearchResult = nullptr; + osl_searchPath (ppArgs[0], pSearchPath, &pSearchResult); + if (pSearchResult) + { + rtl_uString_assign (&(ppArgs[0]), pSearchResult); + rtl_uString_release (pSearchResult); + } + rtl_uString_release (pSearchPath); + } + } +#endif + OUString pArg0; + if (osl::realpath (OUString::unacquired(&ppArgs[0]), pArg0)) + { + osl_getFileURLFromSystemPath (pArg0.pData, &(ppArgs[0])); + } +#endif // !HAVE_FEATURE_MACOSX_SANDBOX + } + g_command_args.m_nCount = argc; + g_command_args.m_ppArgs = ppArgs; + } + } + pthread_mutex_unlock (&(g_command_args.m_mutex)); +} + +oslProcessError SAL_CALL osl_getEnvironment(rtl_uString* pustrEnvVar, rtl_uString** ppustrValue) +{ + oslProcessError result = osl_Process_E_NotFound; + rtl_TextEncoding encoding = osl_getThreadTextEncoding(); + rtl_String* pstr_env_var = nullptr; + + OSL_PRECOND(pustrEnvVar, "osl_getEnvironment(): Invalid parameter"); + OSL_PRECOND(ppustrValue, "osl_getEnvironment(): Invalid parameter"); + + rtl_uString2String( + &pstr_env_var, + rtl_uString_getStr(pustrEnvVar), rtl_uString_getLength(pustrEnvVar), encoding, + OUSTRING_TO_OSTRING_CVTFLAGS); + if (pstr_env_var != nullptr) + { + const char* p_env_var = getenv (rtl_string_getStr (pstr_env_var)); + if (p_env_var != nullptr) + { + rtl_string2UString( + ppustrValue, + p_env_var, strlen(p_env_var), encoding, + OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(*ppustrValue != nullptr); + + result = osl_Process_E_None; + } + rtl_string_release(pstr_env_var); + } + + return result; +} + +oslProcessError SAL_CALL osl_setEnvironment(rtl_uString* pustrEnvVar, rtl_uString* pustrValue) +{ + oslProcessError result = osl_Process_E_Unknown; + rtl_TextEncoding encoding = osl_getThreadTextEncoding(); + rtl_String* pstr_env_var = nullptr; + rtl_String* pstr_val = nullptr; + + OSL_PRECOND(pustrEnvVar, "osl_setEnvironment(): Invalid parameter"); + OSL_PRECOND(pustrValue, "osl_setEnvironment(): Invalid parameter"); + + rtl_uString2String( + &pstr_env_var, + rtl_uString_getStr(pustrEnvVar), rtl_uString_getLength(pustrEnvVar), encoding, + OUSTRING_TO_OSTRING_CVTFLAGS); + + rtl_uString2String( + &pstr_val, + rtl_uString_getStr(pustrValue), rtl_uString_getLength(pustrValue), encoding, + OUSTRING_TO_OSTRING_CVTFLAGS); + + if (pstr_env_var != nullptr && pstr_val != nullptr) + { +#if defined (__sun) + rtl_String * pBuffer = NULL; + + sal_Int32 nCapacity = rtl_stringbuffer_newFromStringBuffer( &pBuffer, + rtl_string_getLength(pstr_env_var) + rtl_string_getLength(pstr_val) + 1, + pstr_env_var ); + rtl_stringbuffer_insert( &pBuffer, &nCapacity, pBuffer->length, "=", 1); + rtl_stringbuffer_insert( &pBuffer, &nCapacity, pBuffer->length, + rtl_string_getStr(pstr_val), rtl_string_getLength(pstr_val) ); + + rtl_string_acquire(pBuffer); // argument to putenv must leak on success + + if (putenv(rtl_string_getStr(pBuffer)) == 0) + result = osl_Process_E_None; + else + rtl_string_release(pBuffer); +#else + if (setenv(rtl_string_getStr(pstr_env_var), rtl_string_getStr(pstr_val), 1) == 0) + result = osl_Process_E_None; +#endif + } + + if (pstr_val) + rtl_string_release(pstr_val); + + if (pstr_env_var != nullptr) + rtl_string_release(pstr_env_var); + + return result; +} + +oslProcessError SAL_CALL osl_clearEnvironment(rtl_uString* pustrEnvVar) +{ + oslProcessError result = osl_Process_E_Unknown; + rtl_TextEncoding encoding = osl_getThreadTextEncoding(); + rtl_String* pstr_env_var = nullptr; + + OSL_PRECOND(pustrEnvVar, "osl_setEnvironment(): Invalid parameter"); + + rtl_uString2String( + &pstr_env_var, + rtl_uString_getStr(pustrEnvVar), rtl_uString_getLength(pustrEnvVar), encoding, + OUSTRING_TO_OSTRING_CVTFLAGS); + + if (pstr_env_var) + { +#if defined (__sun) + rtl_String * pBuffer = NULL; + + sal_Int32 nCapacity = rtl_stringbuffer_newFromStringBuffer( &pBuffer, + rtl_string_getLength(pstr_env_var) + 1, pstr_env_var ); + rtl_stringbuffer_insert( &pBuffer, &nCapacity, pBuffer->length, "=", 1); + + rtl_string_acquire(pBuffer); // argument to putenv must leak on success + + if (putenv(rtl_string_getStr(pBuffer)) == 0) + result = osl_Process_E_None; + else + rtl_string_release(pBuffer); +#elif (defined(MACOSX) || defined(NETBSD) || defined(FREEBSD)) + // MacOSX baseline is 10.4, which has an old-school void return + // for unsetenv. + // See: http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/10.4/man3/unsetenv.3.html?useVersion=10.4 + unsetenv(rtl_string_getStr(pstr_env_var)); + result = osl_Process_E_None; +#else + if (unsetenv(rtl_string_getStr(pstr_env_var)) == 0) + result = osl_Process_E_None; +#endif + rtl_string_release(pstr_env_var); + } + + return result; +} + +oslProcessError SAL_CALL osl_getProcessWorkingDir(rtl_uString **ppustrWorkingDir) +{ + oslProcessError result = osl_Process_E_Unknown; + char buffer[PATH_MAX]; + + OSL_PRECOND(ppustrWorkingDir, "osl_getProcessWorkingDir(): Invalid parameter"); + + if (getcwd (buffer, sizeof(buffer)) != nullptr) + { + rtl_uString* ustrTmp = nullptr; + + rtl_string2UString( + &ustrTmp, + buffer, strlen(buffer), osl_getThreadTextEncoding(), + OSTRING_TO_OUSTRING_CVTFLAGS); + if (ustrTmp != nullptr) + { + if (osl_getFileURLFromSystemPath (ustrTmp, ppustrWorkingDir) == osl_File_E_None) + result = osl_Process_E_None; + rtl_uString_release (ustrTmp); + } + } + + return result; +} + +namespace { + +struct ProcessLocale_Impl +{ + pthread_mutex_t m_mutex; + rtl_Locale * m_pLocale; +}; + +} + +static struct ProcessLocale_Impl g_process_locale = +{ + PTHREAD_MUTEX_INITIALIZER, + nullptr +}; + +oslProcessError SAL_CALL osl_getProcessLocale( rtl_Locale ** ppLocale ) +{ + oslProcessError result = osl_Process_E_Unknown; + OSL_PRECOND(ppLocale, "osl_getProcessLocale(): Invalid parameter."); + if (ppLocale) + { + pthread_mutex_lock(&(g_process_locale.m_mutex)); + + if (g_process_locale.m_pLocale == nullptr) + imp_getProcessLocale (&(g_process_locale.m_pLocale)); + *ppLocale = g_process_locale.m_pLocale; + result = osl_Process_E_None; + + pthread_mutex_unlock (&(g_process_locale.m_mutex)); + } + return result; +} + +oslProcessError SAL_CALL osl_setProcessLocale( rtl_Locale * pLocale ) +{ + OSL_PRECOND(pLocale, "osl_setProcessLocale(): Invalid parameter."); + + pthread_mutex_lock(&(g_process_locale.m_mutex)); + g_process_locale.m_pLocale = pLocale; + pthread_mutex_unlock (&(g_process_locale.m_mutex)); + + return osl_Process_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/profile.cxx b/sal/osl/unx/profile.cxx new file mode 100644 index 000000000..354fe626f --- /dev/null +++ b/sal/osl/unx/profile.cxx @@ -0,0 +1,1866 @@ +/* -*- 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 "system.hxx" +#include "readwrite_helper.hxx" +#include "file_url.hxx" +#include "unixerrnostring.hxx" + +#include <osl/diagnose.h> +#include <osl/profile.h> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/alloc.h> +#include <sal/log.hxx> + +#define LINES_INI 32 +#define LINES_ADD 10 +#define SECTIONS_INI 5 +#define SECTIONS_ADD 3 +#define ENTRIES_INI 5 +#define ENTRIES_ADD 3 + +#define STR_INI_BOOLYES "yes" +#define STR_INI_BOOLON "on" +#define STR_INI_BOOLONE "1" +#define STR_INI_BOOLNO "no" +#define STR_INI_BOOLOFF "off" +#define STR_INI_BOOLZERO "0" + +#define FLG_USER 0x00FF +#define FLG_AUTOOPEN 0x0100 +#define FLG_MODIFIED 0x0200 + +#define DEFAULT_PMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +typedef time_t osl_TStamp; + +namespace { + +enum osl_TLockMode +{ + un_lock, read_lock, write_lock +}; + +struct osl_TFile +{ + int m_Handle; + char* m_pReadPtr; + char m_ReadBuf[512]; + char* m_pWriteBuf; + sal_uInt32 m_nWriteBufLen; + sal_uInt32 m_nWriteBufFree; +}; + +struct osl_TProfileEntry +{ + sal_uInt32 m_Line; + sal_uInt32 m_Offset; + sal_uInt32 m_Len; +}; + +struct osl_TProfileSection +{ + sal_uInt32 m_Line; + sal_uInt32 m_Offset; + sal_uInt32 m_Len; + sal_uInt32 m_NoEntries; + sal_uInt32 m_MaxEntries; + osl_TProfileEntry* m_Entries; +}; + +/* Profile-data structure hidden behind oslProfile: */ +struct osl_TProfileImpl +{ + sal_uInt32 m_Flags; + osl_TFile* m_pFile; + osl_TStamp m_Stamp; + char m_FileName[PATH_MAX + 1]; + sal_uInt32 m_NoLines; + sal_uInt32 m_MaxLines; + sal_uInt32 m_NoSections; + sal_uInt32 m_MaxSections; + char** m_Lines; + osl_TProfileSection* m_Sections; + pthread_mutex_t m_AccessLock; + bool m_bIsValid; +}; + +} + +static osl_TFile* openFileImpl(const char* pszFilename, oslProfileOption ProfileFlags); +static osl_TStamp closeFileImpl(osl_TFile* pFile, oslProfileOption Flags); +static bool OslProfile_lockFile(const osl_TFile* pFile, osl_TLockMode eMode); +static bool OslProfile_rewindFile(osl_TFile* pFile, bool bTruncate); +static osl_TStamp OslProfile_getFileStamp(osl_TFile* pFile); + +static char* OslProfile_getLine(osl_TFile* pFile); +static bool OslProfile_putLine(osl_TFile* pFile, const char *pszLine); +static char* stripBlanks(char* String, sal_uInt32* pLen); +static char* addLine(osl_TProfileImpl* pProfile, const char* Line); +static char* insertLine(osl_TProfileImpl* pProfile, const char* Line, sal_uInt32 LineNo); +static void removeLine(osl_TProfileImpl* pProfile, sal_uInt32 LineNo); +static void setEntry(osl_TProfileImpl* pProfile, osl_TProfileSection* pSection, + sal_uInt32 NoEntry, sal_uInt32 Line, + char* Entry, sal_uInt32 Len); +static bool addEntry(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection, + int Line, char* Entry, sal_uInt32 Len); +static void removeEntry(osl_TProfileSection *pSection, sal_uInt32 NoEntry); +static bool addSection(osl_TProfileImpl* pProfile, int Line, const char* Section, sal_uInt32 Len); +static void removeSection(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection); +static osl_TProfileSection* findEntry(osl_TProfileImpl* pProfile, const char* Section, + const char* Entry, sal_uInt32 *pNoEntry); +static bool loadProfile(osl_TFile* pFile, osl_TProfileImpl* pProfile); +static bool storeProfile(osl_TProfileImpl* pProfile, bool bCleanup); +static osl_TProfileImpl* acquireProfile(oslProfile Profile, bool bWriteable); +static bool releaseProfile(osl_TProfileImpl* pProfile); + +static bool writeProfileImpl (osl_TFile* pFile); +static osl_TFile* osl_openTmpProfileImpl(osl_TProfileImpl*); +static bool osl_ProfileSwapProfileNames(osl_TProfileImpl*); +static void osl_ProfileGenerateExtension(const char* pszFileName, const char* pszExtension, char* pszTmpName, int BufferMaxLen); +static oslProfile osl_psz_openProfile(const char *pszProfileName, oslProfileOption Flags); + +oslProfile SAL_CALL osl_openProfile(rtl_uString *ustrProfileName, oslProfileOption Options) +{ + char profilePath[PATH_MAX] = ""; + return + (ustrProfileName == nullptr + || ustrProfileName->buffer[0] == 0 + || (FileURLToPath(profilePath, PATH_MAX, ustrProfileName) + == osl_File_E_None)) + ? osl_psz_openProfile(profilePath, Options) + : nullptr; +} + +static oslProfile osl_psz_openProfile(const char *pszProfileName, oslProfileOption Flags) +{ + osl_TFile* pFile; + osl_TProfileImpl* pProfile; + bool bRet = false; + + if ( ( pFile = openFileImpl(pszProfileName, Flags ) ) == nullptr ) + { + return nullptr; + } + + pProfile = static_cast<osl_TProfileImpl*>(calloc(1, sizeof(osl_TProfileImpl))); + + if ( pProfile == nullptr ) + { + closeFileImpl(pFile, Flags); + return nullptr; + } + + pProfile->m_Flags = Flags & FLG_USER; + + if ( Flags & ( osl_Profile_READLOCK | osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ) ) + { + pProfile->m_pFile = pFile; + } + + pthread_mutex_init(&(pProfile->m_AccessLock),PTHREAD_MUTEXATTR_DEFAULT); + pProfile->m_bIsValid = true; + + pProfile->m_Stamp = OslProfile_getFileStamp(pFile); + bRet=loadProfile(pFile, pProfile); + bRet &= realpath(pszProfileName, pProfile->m_FileName) != nullptr; + SAL_WARN_IF(!bRet, "sal.osl", "realpath(pszProfileName, pProfile->m_FileName) != NULL ==> false"); + + if (pProfile->m_pFile == nullptr) + closeFileImpl(pFile,pProfile->m_Flags); + + // coverity[leaked_storage] - pFile is not leaked + return pProfile; +} + +sal_Bool SAL_CALL osl_closeProfile(oslProfile Profile) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + osl_TProfileImpl* pTmpProfile; + + if ( Profile == nullptr ) + { + return false; + } + + pthread_mutex_lock(&(pProfile->m_AccessLock)); + + if ( !pProfile->m_bIsValid ) + { + SAL_WARN("sal.osl", "!pProfile->m_bIsValid"); + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + + return false; + } + + pProfile->m_bIsValid = false; + + if ( ! ( pProfile->m_Flags & osl_Profile_READLOCK ) && ( pProfile->m_Flags & FLG_MODIFIED ) ) + { + pTmpProfile = acquireProfile(Profile, true); + + if ( pTmpProfile != nullptr ) + { + bool bRet = storeProfile(pTmpProfile, true); + SAL_WARN_IF(!bRet, "sal.osl", "storeProfile(pTmpProfile, true) ==> false"); + } + } + else + { + pTmpProfile = acquireProfile(Profile, false); + } + + if ( pTmpProfile == nullptr ) + { + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + + SAL_INFO("sal.osl", "Out osl_closeProfile [pProfile==0]"); + return false; + } + + pProfile = pTmpProfile; + + if (pProfile->m_pFile != nullptr) + closeFileImpl(pProfile->m_pFile,pProfile->m_Flags); + + pProfile->m_pFile = nullptr; + pProfile->m_FileName[0] = '\0'; + + /* release whole profile data types memory */ + if ( pProfile->m_NoLines > 0) + { + unsigned int idx=0; + if ( pProfile->m_Lines != nullptr ) + { + for ( idx = 0 ; idx < pProfile->m_NoLines ; ++idx) + { + if ( pProfile->m_Lines[idx] != nullptr ) + { + free(pProfile->m_Lines[idx]); + pProfile->m_Lines[idx]=nullptr; + } + } + free(pProfile->m_Lines); + pProfile->m_Lines=nullptr; + } + if ( pProfile->m_Sections != nullptr ) + { + /*osl_TProfileSection* pSections=pProfile->m_Sections;*/ + for ( idx = 0 ; idx < pProfile->m_NoSections ; ++idx ) + { + if ( pProfile->m_Sections[idx].m_Entries != nullptr ) + { + free(pProfile->m_Sections[idx].m_Entries); + pProfile->m_Sections[idx].m_Entries=nullptr; + } + } + free(pProfile->m_Sections); + pProfile->m_Sections=nullptr; + } + } + + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + + pthread_mutex_destroy(&(pProfile->m_AccessLock)); + + free(pProfile); + + return true; +} + +sal_Bool SAL_CALL osl_flushProfile(oslProfile Profile) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + osl_TFile* pFile; + bool bRet = false; + + if ( pProfile == nullptr ) + { + return false; + } + + pthread_mutex_lock(&(pProfile->m_AccessLock)); + + if ( !pProfile->m_bIsValid ) + { + SAL_WARN_IF(!pProfile->m_bIsValid, "sal.osl", "!pProfile->m_bIsValid"); + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + return false; + } + + pFile = pProfile->m_pFile; + if ( pFile == nullptr || pFile->m_Handle < 0 ) + { + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + + return false; + } + + if ( pProfile->m_Flags & FLG_MODIFIED ) + { + bRet = storeProfile(pProfile, false); + SAL_WARN_IF(!bRet, "sal.osl", "storeProfile(pProfile, false) ==> false"); + } + + pthread_mutex_unlock(&(pProfile->m_AccessLock)); + return bRet; +} + +static bool writeProfileImpl(osl_TFile* pFile) +{ + if ( pFile == nullptr || pFile->m_Handle < 0 || pFile->m_pWriteBuf == nullptr ) + { + return false; + } + + SAL_WARN_IF( + (strlen(pFile->m_pWriteBuf) + != pFile->m_nWriteBufLen - pFile->m_nWriteBufFree), + "sal.osl", + strlen(pFile->m_pWriteBuf) << " != " + << (pFile->m_nWriteBufLen - pFile->m_nWriteBufFree)); + + if ( !safeWrite(pFile->m_Handle, pFile->m_pWriteBuf, pFile->m_nWriteBufLen - pFile->m_nWriteBufFree) ) + { + SAL_INFO("sal.osl", "write failed: " << UnixErrnoString(errno)); + return false; + } + + free(pFile->m_pWriteBuf); + pFile->m_pWriteBuf=nullptr; + pFile->m_nWriteBufLen=0; + pFile->m_nWriteBufFree=0; + + return true; +} + +sal_Bool SAL_CALL osl_readProfileString(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + char* pszString, + sal_uInt32 MaxLen, + const char* pszDefault) +{ + sal_uInt32 NoEntry; + char* pStr=nullptr; + osl_TProfileImpl* pProfile=nullptr; + osl_TProfileImpl* pTmpProfile=nullptr; + bool bRet = false; + + pTmpProfile = static_cast<osl_TProfileImpl*>(Profile); + + if ( pTmpProfile == nullptr ) + { + return false; + } + + pthread_mutex_lock(&(pTmpProfile->m_AccessLock)); + + if ( !pTmpProfile->m_bIsValid ) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + pProfile = acquireProfile(Profile, false); + + if ( pProfile == nullptr ) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry); + if ((pSec != nullptr) && + (NoEntry < pSec->m_NoEntries) && + ((pStr = strchr(pProfile->m_Lines[pSec->m_Entries[NoEntry].m_Line], + '=')) != nullptr)) + { + pStr++; + } + else + { + pStr=const_cast<char*>(pszDefault); + } + + if ( pStr != nullptr ) + { + pStr = stripBlanks(pStr, nullptr); + MaxLen = (MaxLen - 1 < strlen(pStr)) ? (MaxLen - 1) : strlen(pStr); + pStr = stripBlanks(pStr, &MaxLen); + strncpy(pszString, pStr, MaxLen); + pszString[MaxLen] = '\0'; + } + } + else + { /* not implemented */ } + + bRet=releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + if ( pStr == nullptr ) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return true; +} + +sal_Bool SAL_CALL osl_readProfileBool(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + sal_Bool Default) +{ + char Line[32]; + Line[0] = '\0'; + + if (osl_readProfileString(Profile, pszSection, pszEntry, Line, sizeof(Line), "")) + { + if ((strcasecmp(Line, STR_INI_BOOLYES) == 0) || + (strcasecmp(Line, STR_INI_BOOLON) == 0) || + (strcasecmp(Line, STR_INI_BOOLONE) == 0)) + Default = true; + else + if ((strcasecmp(Line, STR_INI_BOOLNO) == 0) || + (strcasecmp(Line, STR_INI_BOOLOFF) == 0) || + (strcasecmp(Line, STR_INI_BOOLZERO) == 0)) + Default = false; + } + + return Default; +} + +sal_uInt32 SAL_CALL osl_readProfileIdent(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + sal_uInt32 FirstId, + const char* Strings[], + sal_uInt32 Default) +{ + sal_uInt32 i; + char Line[256]; + Line[0] = '\0'; + + if (osl_readProfileString(Profile, pszSection, pszEntry, Line, sizeof(Line), "")) + { + i = 0; + while (Strings[i] != nullptr) + { + if (strcasecmp(Line, Strings[i]) == 0) + { + Default = i + FirstId; + break; + } + i++; + } + } + + return Default; +} + +sal_Bool SAL_CALL osl_writeProfileString(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + const char* pszString) +{ + bool bRet = false; + sal_uInt32 NoEntry; + char* pStr; + char* Line = nullptr; + osl_TProfileImpl* pProfile = nullptr; + osl_TProfileImpl* pTmpProfile = static_cast<osl_TProfileImpl*>(Profile); + + if ( pTmpProfile == nullptr ) + { + return false; + } + + pthread_mutex_lock(&(pTmpProfile->m_AccessLock)); + + if ( !pTmpProfile->m_bIsValid ) + { + SAL_WARN_IF(!pTmpProfile->m_bIsValid, "sal.osl", "!pTmpProfile->m_bIsValid"); + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + pProfile=acquireProfile(Profile, true); + + if (pProfile == nullptr) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + Line = static_cast<char*>(malloc(strlen(pszEntry)+strlen(pszString)+48)); + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec; + if ((pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry)) == nullptr) + { + Line[0] = '\0'; + addLine(pProfile, Line); + + Line[0] = '['; + strcpy(&Line[1], pszSection); + Line[1 + strlen(pszSection)] = ']'; + Line[2 + strlen(pszSection)] = '\0'; + + pStr = addLine(pProfile, Line); + if ((pStr == nullptr) || + (! addSection(pProfile, pProfile->m_NoLines - 1, &pStr[1], strlen(pszSection)))) + { + bRet=releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + free(Line); + return false; + } + + pSec = &pProfile->m_Sections[pProfile->m_NoSections - 1]; + NoEntry = pSec->m_NoEntries; + } + + Line[0] = '\0'; + strcpy(&Line[0], pszEntry); + Line[0 + strlen(pszEntry)] = '='; + strcpy(&Line[1 + strlen(pszEntry)], pszString); + + if (NoEntry >= pSec->m_NoEntries) + { + sal_uInt32 i; + if (pSec->m_NoEntries > 0) + i = pSec->m_Entries[pSec->m_NoEntries - 1].m_Line + 1; + else + i = pSec->m_Line + 1; + + pStr = insertLine(pProfile, Line, i); + if ((pStr == nullptr) || + (! addEntry(pProfile, pSec, i, pStr, strlen(pszEntry)))) + { + bRet=releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + free(Line); + + return false; + } + + pProfile->m_Flags |= FLG_MODIFIED; + } + else + { + sal_uInt32 i = pSec->m_Entries[NoEntry].m_Line; + free(pProfile->m_Lines[i]); + pProfile->m_Lines[i] = strdup(Line); + setEntry(pProfile, pSec, NoEntry, i, pProfile->m_Lines[i], strlen(pszEntry)); + + pProfile->m_Flags |= FLG_MODIFIED; + } + } + else { + /* not implemented */ + } + + bRet = releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + if ( Line!= nullptr ) + { + free(Line); + } + + return bRet; +} + +sal_Bool SAL_CALL osl_writeProfileBool(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + sal_Bool Value) +{ + bool bRet = false; + + if (Value) + bRet=osl_writeProfileString(Profile, pszSection, pszEntry, STR_INI_BOOLONE); + else + bRet=osl_writeProfileString(Profile, pszSection, pszEntry, STR_INI_BOOLZERO); + + return bRet; +} + +sal_Bool SAL_CALL osl_writeProfileIdent(oslProfile Profile, + const char* pszSection, + const char* pszEntry, + sal_uInt32 FirstId, + const char* Strings[], + sal_uInt32 Value) +{ + int i, n = 0; + bool bRet = false; + + while (Strings[n] != nullptr) + ++n; + + if ((i = Value - FirstId) >= n) + bRet = false; + else + bRet = osl_writeProfileString(Profile, pszSection, pszEntry, Strings[i]); + + return bRet; +} + +sal_Bool SAL_CALL osl_removeProfileEntry(oslProfile Profile, + const char *pszSection, + const char *pszEntry) +{ + sal_uInt32 NoEntry; + osl_TProfileImpl* pProfile = nullptr; + osl_TProfileImpl* pTmpProfile = nullptr; + bool bRet = false; + + pTmpProfile = static_cast<osl_TProfileImpl*>(Profile); + + if ( pTmpProfile == nullptr ) + { + return false; + } + + pthread_mutex_lock(&(pTmpProfile->m_AccessLock)); + + if ( !pTmpProfile->m_bIsValid ) + { + SAL_WARN_IF(!pTmpProfile->m_bIsValid, "sal.osl", "!pTmpProfile->m_bIsValid"); + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + return false; + } + + pProfile = acquireProfile(Profile, true); + + if (pProfile == nullptr) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry); + if ((pSec != nullptr) && + (NoEntry < pSec->m_NoEntries)) + { + removeLine(pProfile, pSec->m_Entries[NoEntry].m_Line); + removeEntry(pSec, NoEntry); + if (pSec->m_NoEntries == 0) + { + removeLine(pProfile, pSec->m_Line); + + /* remove any empty separation line */ + if ((pSec->m_Line > 0) && (pProfile->m_Lines[pSec->m_Line - 1][0] == '\0')) + removeLine(pProfile, pSec->m_Line - 1); + + removeSection(pProfile, pSec); + } + + pProfile->m_Flags |= FLG_MODIFIED; + } + } + else + { /* not implemented */ } + + bRet = releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return bRet; +} + +sal_uInt32 SAL_CALL osl_getProfileSectionEntries(oslProfile Profile, + const char *pszSection, + char* pszBuffer, + sal_uInt32 MaxLen) +{ + sal_uInt32 i, n = 0; + sal_uInt32 NoEntry; + osl_TProfileImpl* pProfile = nullptr; + osl_TProfileImpl* pTmpProfile = nullptr; + bool bRet = false; + + pTmpProfile = static_cast<osl_TProfileImpl*>(Profile); + + if ( pTmpProfile == nullptr ) + { + return 0; + + } + + pthread_mutex_lock(&(pTmpProfile->m_AccessLock)); + + if ( !pTmpProfile->m_bIsValid ) + { + SAL_WARN_IF(!pTmpProfile->m_bIsValid, "sal.osl", "!pTmpProfile->m_bIsValid"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return 0; + } + + pProfile = acquireProfile(Profile, false); + + if (pProfile == nullptr) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return 0; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec; + if ((pSec = findEntry(pProfile, pszSection, "", &NoEntry)) != nullptr) + { + if (MaxLen != 0) + { + for (i = 0; i < pSec->m_NoEntries; i++) + { + if ((n + pSec->m_Entries[i].m_Len + 1) < MaxLen) + { + strncpy(&pszBuffer[n], &pProfile->m_Lines[pSec->m_Entries[i].m_Line] + [pSec->m_Entries[i].m_Offset], pSec->m_Entries[i].m_Len); + n += pSec->m_Entries[i].m_Len; + pszBuffer[n++] = '\0'; + } + else + break; + + } + + pszBuffer[n++] = '\0'; + } + else + { + for (i = 0; i < pSec->m_NoEntries; i++) + n += pSec->m_Entries[i].m_Len + 1; + + n += 1; + } + } + else + n = 0; + } + else { + /* not implemented */ + } + + bRet=releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return n; +} + +sal_uInt32 SAL_CALL osl_getProfileSections(oslProfile Profile, + char* pszBuffer, + sal_uInt32 MaxLen) +{ + sal_uInt32 i, n = 0; + osl_TProfileImpl* pProfile = nullptr; + osl_TProfileImpl* pTmpProfile = nullptr; + bool bRet = false; + + pTmpProfile = static_cast<osl_TProfileImpl*>(Profile); + + if ( pTmpProfile == nullptr ) + { + return 0; + } + + pthread_mutex_lock(&(pTmpProfile->m_AccessLock)); + + if ( !pTmpProfile->m_bIsValid ) + { + SAL_WARN_IF(!pTmpProfile->m_bIsValid, "sal.osl", "!pTmpProfile->m_bIsValid"); + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return 0; + } + + pProfile = acquireProfile(Profile, false); + + if (pProfile == nullptr) + { + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return 0; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if (MaxLen != 0) + { + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if ((n + pSec->m_Len + 1) < MaxLen) + { + strncpy(&pszBuffer[n], &pProfile->m_Lines[pSec->m_Line][pSec->m_Offset], + pSec->m_Len); + n += pSec->m_Len; + pszBuffer[n++] = '\0'; + } + else + break; + } + + pszBuffer[n++] = '\0'; + } + else + { + for (i = 0; i < pProfile->m_NoSections; i++) + n += pProfile->m_Sections[i].m_Len + 1; + + n += 1; + } + } + else + { /* not implemented */ } + + bRet=releaseProfile(pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "releaseProfile(pProfile) ==> false"); + + pthread_mutex_unlock(&(pTmpProfile->m_AccessLock)); + + return n; +} + +static osl_TStamp OslProfile_getFileStamp(osl_TFile* pFile) +{ + struct stat status; + + if ( (pFile->m_Handle < 0) || (fstat(pFile->m_Handle, &status) < 0) ) + { + return 0; + } + + return status.st_mtime; +} + +static bool OslProfile_lockFile(const osl_TFile* pFile, osl_TLockMode eMode) +{ + struct flock lock; + static bool const bLockingDisabled = getenv( "STAR_PROFILE_LOCKING_DISABLED" ) != nullptr; + + if (pFile->m_Handle < 0) + { + return false; + } + + if ( bLockingDisabled ) + { + return true; + } + + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + switch (eMode) + { + case un_lock: + lock.l_type = F_UNLCK; + break; + + case read_lock: + lock.l_type = F_RDLCK; + break; + + case write_lock: + lock.l_type = F_WRLCK; + break; + } + +#ifndef MACOSX + if ( fcntl(pFile->m_Handle, F_SETLKW, &lock) == -1 ) +#else + /* Mac OSX will return ENOTSUP for webdav drives so we should ignore it */ + if ( fcntl(pFile->m_Handle, F_SETLKW, &lock) == -1 && errno != ENOTSUP ) +#endif + { + SAL_INFO("sal.osl", "fcntl failed: " << UnixErrnoString(errno)); + return false; + } + + return true; +} + +static osl_TFile* openFileImpl(const char* pszFilename, oslProfileOption ProfileFlags ) +{ + int Flags; + osl_TFile* pFile = static_cast<osl_TFile*>(calloc(1, sizeof(osl_TFile))); + bool bWriteable = false; + + if ( ProfileFlags & ( osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ) ) + { + bWriteable = true; + } + + if (! bWriteable) + { + pFile->m_Handle = open(pszFilename, O_RDONLY); + + if (pFile->m_Handle == -1) + { + int e = errno; + SAL_INFO("sal.file", "open(" << pszFilename << ",O_RDONLY): " << UnixErrnoString(e)); + } + else + SAL_INFO("sal.file", "open(" << pszFilename << ",O_RDONLY) => " << pFile->m_Handle); + + /* mfe: argghh!!! do not check if the file could be opened */ + /* default mode expects it that way!!! */ + } + else + { + if (((pFile->m_Handle = open(pszFilename, O_RDWR | O_CREAT | O_EXCL, DEFAULT_PMODE)) < 0) && + ((pFile->m_Handle = open(pszFilename, O_RDWR)) < 0)) + { + int e = errno; + SAL_INFO("sal.file", "open(" << pszFilename << ",...): " << UnixErrnoString(e)); + free(pFile); + return nullptr; + } + else + SAL_INFO("sal.file", "open(" << pszFilename << ",...) => " << pFile->m_Handle); + } + + /* set close-on-exec flag */ + if ((Flags = fcntl(pFile->m_Handle, F_GETFD, 0)) != -1) + { + Flags |= FD_CLOEXEC; + int e = fcntl(pFile->m_Handle, F_SETFD, Flags); + SAL_INFO_IF( + e != 0, "sal.osl", + "fcntl to set FD_CLOEXEC failed for " << pszFilename); + } + + pFile->m_pWriteBuf=nullptr; + pFile->m_nWriteBufFree=0; + pFile->m_nWriteBufLen=0; + + if ( ProfileFlags & (osl_Profile_WRITELOCK | osl_Profile_READLOCK ) ) + { + OslProfile_lockFile(pFile, bWriteable ? write_lock : read_lock); + } + + return pFile; +} + +static osl_TStamp closeFileImpl(osl_TFile* pFile, oslProfileOption Flags) +{ + osl_TStamp stamp = 0; + + if ( pFile == nullptr ) + { + return stamp; + } + + if ( pFile->m_Handle >= 0 ) + { + stamp = OslProfile_getFileStamp(pFile); + + if ( Flags & (osl_Profile_WRITELOCK | osl_Profile_READLOCK ) ) + { + OslProfile_lockFile(pFile, un_lock); + } + + close(pFile->m_Handle); + SAL_INFO("sal.file", "close(" << pFile->m_Handle << ")"); + pFile->m_Handle = -1; + } + + if ( pFile->m_pWriteBuf ) + { + free(pFile->m_pWriteBuf); + } + + free(pFile); + + return stamp; +} + +static bool OslProfile_rewindFile(osl_TFile* pFile, bool bTruncate) +{ + bool bRet = true; + + if (pFile->m_Handle >= 0) + { + pFile->m_pReadPtr = pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf); + + bRet = (lseek(pFile->m_Handle, SEEK_SET, 0) == 0); + + if (bTruncate) + { + bRet &= (ftruncate(pFile->m_Handle, 0) == 0); + } + + } + + return bRet; +} + +static char* OslProfile_getLine(osl_TFile* pFile) +{ + int Max, Free, nLineBytes = 0; + char* pChr; + char* pLine = nullptr; + char* pNewLine; + + if ( pFile == nullptr ) + { + return nullptr; + } + + if (pFile->m_Handle < 0) + return nullptr; + + do + { + int Bytes = sizeof(pFile->m_ReadBuf) - (pFile->m_pReadPtr - pFile->m_ReadBuf); + + if (Bytes <= 1) + { + /* refill buffer */ + memcpy(pFile->m_ReadBuf, pFile->m_pReadPtr, Bytes); + pFile->m_pReadPtr = pFile->m_ReadBuf; + + Free = sizeof(pFile->m_ReadBuf) - Bytes; + + if ((Max = read(pFile->m_Handle, &pFile->m_ReadBuf[Bytes], Free)) < 0) + { + SAL_INFO("sal.osl", "read failed: " << UnixErrnoString(errno)); + + if( pLine ) + free( pLine ); + pLine = nullptr; + break; + } + + if (Max < Free) + { + if ((Max == 0) && ! pLine) + break; + + pFile->m_ReadBuf[Bytes + Max] = '\0'; + } + } + + for (pChr = pFile->m_pReadPtr; + (*pChr != '\n') && (*pChr != '\r') && (*pChr != '\0') && + (pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf) - 1)); + pChr++); + + Max = pChr - pFile->m_pReadPtr; + pNewLine = static_cast<char*>(malloc( nLineBytes + Max + 1 )); + if( pLine ) + { + memcpy( pNewLine, pLine, nLineBytes ); + free( pLine ); + } + memcpy(pNewLine+nLineBytes, pFile->m_pReadPtr, Max); + nLineBytes += Max; + pNewLine[ nLineBytes ] = 0; + pLine = pNewLine; + + if (pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf) - 1)) + { + if (*pChr != '\0') + { + if ((pChr[0] == '\r') && (pChr[1] == '\n')) + pChr += 2; + else + pChr += 1; + } + + if ((pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf))) && + (*pChr == '\0')) + pChr = pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf); + + /* setting Max to -1 indicates terminating read loop */ + Max = -1; + } + + pFile->m_pReadPtr = pChr; + } + while (Max > 0) ; + + return pLine; +} + +static bool OslProfile_putLine(osl_TFile* pFile, const char *pszLine) +{ + unsigned int Len = strlen(pszLine); + + if ( pFile == nullptr || pFile->m_Handle < 0 ) + { + return false; + } + + if ( pFile->m_pWriteBuf == nullptr ) + { + pFile->m_pWriteBuf = static_cast<char*>(malloc(Len+3)); + pFile->m_nWriteBufLen = Len+3; + pFile->m_nWriteBufFree = Len+3; + } + else + { + if ( pFile->m_nWriteBufFree <= Len + 3 ) + { + char* pTmp; + + pTmp=static_cast<char*>(realloc(pFile->m_pWriteBuf,( ( pFile->m_nWriteBufLen + Len ) * 2) )); + if ( pTmp == nullptr ) + { + return false; + } + pFile->m_pWriteBuf = pTmp; + pFile->m_nWriteBufFree = pFile->m_nWriteBufFree + pFile->m_nWriteBufLen + ( 2 * Len ); + pFile->m_nWriteBufLen = ( pFile->m_nWriteBufLen + Len ) * 2; + memset( (pFile->m_pWriteBuf) + ( pFile->m_nWriteBufLen - pFile->m_nWriteBufFree ), 0, pFile->m_nWriteBufFree); + } + } + + memcpy(pFile->m_pWriteBuf + ( pFile->m_nWriteBufLen - pFile->m_nWriteBufFree ),pszLine,Len+1); + pFile->m_pWriteBuf[pFile->m_nWriteBufLen - pFile->m_nWriteBufFree + Len]='\n'; + pFile->m_pWriteBuf[pFile->m_nWriteBufLen - pFile->m_nWriteBufFree + Len + 1]='\0'; + + pFile->m_nWriteBufFree-=Len+1; + + return true; +} + +static char* stripBlanks(char* String, sal_uInt32* pLen) +{ + if ( ( pLen != nullptr ) && ( *pLen != 0 ) ) + { + while ((String[*pLen - 1] == ' ') || (String[*pLen - 1] == '\t')) + (*pLen)--; + + while ( (*String == ' ') || (*String == '\t') ) + { + String++; + (*pLen)--; + } + } + else + while ( (*String == ' ') || (*String == '\t') ) + String++; + + return String; +} + +static char* addLine(osl_TProfileImpl* pProfile, const char* Line) +{ + if (pProfile->m_NoLines >= pProfile->m_MaxLines) + { + if (pProfile->m_Lines == nullptr) + { + pProfile->m_MaxLines = LINES_INI; + pProfile->m_Lines = static_cast<char **>(calloc(pProfile->m_MaxLines, sizeof(char *))); + } + else + { + unsigned int idx=0; + unsigned int oldmax=pProfile->m_MaxLines; + + pProfile->m_MaxLines += LINES_ADD; + pProfile->m_Lines = static_cast<char **>(realloc(pProfile->m_Lines, + pProfile->m_MaxLines * sizeof(char *))); + for ( idx = oldmax ; idx < pProfile->m_MaxLines ; ++idx ) + { + pProfile->m_Lines[idx]=nullptr; + } + } + } + if (pProfile->m_Lines == nullptr) + { + pProfile->m_NoLines = 0; + pProfile->m_MaxLines = 0; + return nullptr; + } + + if ( pProfile->m_Lines[pProfile->m_NoLines] != nullptr ) + { + free(pProfile->m_Lines[pProfile->m_NoLines]); + } + pProfile->m_Lines[pProfile->m_NoLines++] = strdup(Line); + + return pProfile->m_Lines[pProfile->m_NoLines - 1]; +} + +static char* insertLine(osl_TProfileImpl* pProfile, const char* Line, sal_uInt32 LineNo) +{ + if (pProfile->m_NoLines >= pProfile->m_MaxLines) + { + if (pProfile->m_Lines == nullptr) + { + pProfile->m_MaxLines = LINES_INI; + pProfile->m_Lines = static_cast<char **>(calloc(pProfile->m_MaxLines, sizeof(char *))); + } + else + { + pProfile->m_MaxLines += LINES_ADD; + pProfile->m_Lines = static_cast<char **>(realloc(pProfile->m_Lines, + pProfile->m_MaxLines * sizeof(char *))); + + memset(&pProfile->m_Lines[pProfile->m_NoLines], + 0, + (pProfile->m_MaxLines - pProfile->m_NoLines - 1) * sizeof(char*)); + } + + if (pProfile->m_Lines == nullptr) + { + pProfile->m_NoLines = 0; + pProfile->m_MaxLines = 0; + return nullptr; + } + } + + LineNo = std::min(LineNo, pProfile->m_NoLines); + + if (LineNo < pProfile->m_NoLines) + { + sal_uInt32 i, n; + + memmove(&pProfile->m_Lines[LineNo + 1], &pProfile->m_Lines[LineNo], + (pProfile->m_NoLines - LineNo) * sizeof(char *)); + + /* adjust line references */ + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if (pSec->m_Line >= LineNo) + pSec->m_Line++; + + for (n = 0; n < pSec->m_NoEntries; n++) + if (pSec->m_Entries[n].m_Line >= LineNo) + pSec->m_Entries[n].m_Line++; + } + } + + pProfile->m_NoLines++; + + pProfile->m_Lines[LineNo] = strdup(Line); + + return pProfile->m_Lines[LineNo]; +} + +static void removeLine(osl_TProfileImpl* pProfile, sal_uInt32 LineNo) +{ + if (LineNo >= pProfile->m_NoLines) + return; + + free(pProfile->m_Lines[LineNo]); + pProfile->m_Lines[LineNo]=nullptr; + if (pProfile->m_NoLines - LineNo > 1) + { + sal_uInt32 i, n; + + memmove(&pProfile->m_Lines[LineNo], &pProfile->m_Lines[LineNo + 1], + (pProfile->m_NoLines - LineNo - 1) * sizeof(char *)); + + memset(&pProfile->m_Lines[pProfile->m_NoLines - 1], + 0, + (pProfile->m_MaxLines - pProfile->m_NoLines) * sizeof(char*)); + + /* adjust line references */ + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if (pSec->m_Line > LineNo) + pSec->m_Line--; + + for (n = 0; n < pSec->m_NoEntries; n++) + if (pSec->m_Entries[n].m_Line > LineNo) + pSec->m_Entries[n].m_Line--; + } + } + else + { + pProfile->m_Lines[LineNo] = nullptr; + } + + pProfile->m_NoLines--; +} + +static void setEntry(osl_TProfileImpl* pProfile, osl_TProfileSection* pSection, + sal_uInt32 NoEntry, sal_uInt32 Line, + char* Entry, sal_uInt32 Len) +{ + Entry = stripBlanks(Entry, &Len); + pSection->m_Entries[NoEntry].m_Line = Line; + pSection->m_Entries[NoEntry].m_Offset = Entry - pProfile->m_Lines[Line]; + pSection->m_Entries[NoEntry].m_Len = Len; +} + +static bool addEntry(osl_TProfileImpl* pProfile, + osl_TProfileSection *pSection, + int Line, char* Entry, + sal_uInt32 Len) +{ + if (pSection != nullptr) + { + if (pSection->m_NoEntries >= pSection->m_MaxEntries) + { + if (pSection->m_Entries == nullptr) + { + pSection->m_MaxEntries = ENTRIES_INI; + pSection->m_Entries = static_cast<osl_TProfileEntry *>(malloc( + pSection->m_MaxEntries * sizeof(osl_TProfileEntry))); + } + else + { + pSection->m_MaxEntries += ENTRIES_ADD; + pSection->m_Entries = static_cast<osl_TProfileEntry *>(realloc(pSection->m_Entries, + pSection->m_MaxEntries * sizeof(osl_TProfileEntry))); + } + + if (pSection->m_Entries == nullptr) + { + pSection->m_NoEntries = 0; + pSection->m_MaxEntries = 0; + return false; + } + } + + pSection->m_NoEntries++; + + Entry = stripBlanks(Entry, &Len); + setEntry(pProfile, pSection, pSection->m_NoEntries - 1, Line, + Entry, Len); + + return true; + } + + return false; +} + +static void removeEntry(osl_TProfileSection *pSection, sal_uInt32 NoEntry) +{ + if (NoEntry >= pSection->m_NoEntries) + return; + + if (pSection->m_NoEntries - NoEntry > 1) + { + memmove(&pSection->m_Entries[NoEntry], + &pSection->m_Entries[NoEntry + 1], + (pSection->m_NoEntries - NoEntry - 1) * sizeof(osl_TProfileEntry)); + pSection->m_Entries[pSection->m_NoEntries - 1].m_Line=0; + pSection->m_Entries[pSection->m_NoEntries - 1].m_Offset=0; + pSection->m_Entries[pSection->m_NoEntries - 1].m_Len=0; + } + + pSection->m_NoEntries--; + +} + +static bool addSection(osl_TProfileImpl* pProfile, int Line, const char* Section, sal_uInt32 Len) +{ + if (pProfile->m_NoSections >= pProfile->m_MaxSections) + { + if (pProfile->m_Sections == nullptr) + { + pProfile->m_MaxSections = SECTIONS_INI; + pProfile->m_Sections = static_cast<osl_TProfileSection *>(calloc(pProfile->m_MaxSections, sizeof(osl_TProfileSection))); + } + else + { + unsigned int idx=0; + unsigned int oldmax=pProfile->m_MaxSections; + + pProfile->m_MaxSections += SECTIONS_ADD; + pProfile->m_Sections = static_cast<osl_TProfileSection *>(realloc(pProfile->m_Sections, + pProfile->m_MaxSections * sizeof(osl_TProfileSection))); + for ( idx = oldmax ; idx < pProfile->m_MaxSections ; ++idx ) + { + pProfile->m_Sections[idx].m_Entries=nullptr; + } + } + + if (pProfile->m_Sections == nullptr) + { + pProfile->m_NoSections = 0; + pProfile->m_MaxSections = 0; + return false; + } + } + + pProfile->m_NoSections++; + + if ( pProfile->m_Sections[(pProfile->m_NoSections) - 1].m_Entries != nullptr ) + { + free(pProfile->m_Sections[(pProfile->m_NoSections) - 1].m_Entries); + } + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Entries = nullptr; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_NoEntries = 0; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_MaxEntries = 0; + + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Line = Line; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Offset = Section - pProfile->m_Lines[Line]; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Len = Len; + + return true; +} + +static void removeSection(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection) +{ + sal_uInt32 Section; + + if ((Section = pSection - pProfile->m_Sections) >= pProfile->m_NoSections) + return; + + free (pSection->m_Entries); + pSection->m_Entries=nullptr; + if (pProfile->m_NoSections - Section > 1) + { + memmove(&pProfile->m_Sections[Section], &pProfile->m_Sections[Section + 1], + (pProfile->m_NoSections - Section - 1) * sizeof(osl_TProfileSection)); + + memset(&pProfile->m_Sections[pProfile->m_NoSections - 1], + 0, + (pProfile->m_MaxSections - pProfile->m_NoSections) * sizeof(osl_TProfileSection)); + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Entries = nullptr; + } + else + { + pSection->m_Entries = nullptr; + } + + pProfile->m_NoSections--; +} + +static osl_TProfileSection* findEntry(osl_TProfileImpl* pProfile, + const char* Section, + const char* Entry, + sal_uInt32 *pNoEntry) +{ + static sal_uInt32 Sect = 0; + sal_uInt32 i, n; + sal_uInt32 Len; + osl_TProfileSection* pSec=nullptr; + + Len = strlen(Section); + + n = Sect; + + for (i = 0; i < pProfile->m_NoSections; i++) + { + n %= pProfile->m_NoSections; + pSec = &pProfile->m_Sections[n]; + if ((Len == pSec->m_Len) && + (strncasecmp(Section, &pProfile->m_Lines[pSec->m_Line][pSec->m_Offset], pSec->m_Len) + == 0)) + break; + n++; + } + + Sect = n; + + if (i < pProfile->m_NoSections) + { + Len = strlen(Entry); + + *pNoEntry = pSec->m_NoEntries; + + for (i = 0; i < pSec->m_NoEntries; i++) + { + const char* pStr = &pProfile->m_Lines[pSec->m_Entries[i].m_Line] + [pSec->m_Entries[i].m_Offset]; + if ((Len == pSec->m_Entries[i].m_Len) && + (strncasecmp(Entry, pStr, pSec->m_Entries[i].m_Len) + == 0)) + { + *pNoEntry = i; + break; + } + } + } + else + pSec = nullptr; + + return pSec; +} + +static bool loadProfile(osl_TFile* pFile, osl_TProfileImpl* pProfile) +{ + sal_uInt32 i; + char* pStr; + char* pChar; + + char* pLine; + + if ( !pFile ) + { + return false; + } + + if ( !pProfile ) + { + return false; + } + + pProfile->m_NoLines = 0; + pProfile->m_NoSections = 0; + + OSL_VERIFY(OslProfile_rewindFile(pFile, false)); + + while ( ( pLine=OslProfile_getLine(pFile) ) != nullptr ) + { + char* bWasAdded = addLine( pProfile, pLine ); + free( pLine ); + SAL_WARN_IF(!bWasAdded, "sal.osl", "addLine( pProfile, pLine ) ==> false"); + if ( ! bWasAdded ) + return false; + } + + for (i = 0; i < pProfile->m_NoLines; i++) + { + pStr = stripBlanks(pProfile->m_Lines[i], nullptr); + + if ((*pStr == '\0') || (*pStr == ';')) + continue; + + if ((*pStr != '[') || ((pChar = strrchr(pStr, ']')) == nullptr) || + ((pChar - pStr) <= 2)) + { + /* insert entry */ + + if (pProfile->m_NoSections < 1) + continue; + + if ((pChar = strchr(pStr, '=')) == nullptr) + pChar = pStr + strlen(pStr); + + if (! addEntry(pProfile, &pProfile->m_Sections[pProfile->m_NoSections - 1], + i, pStr, pChar - pStr)) + { + SAL_WARN("sal.osl", "Adding entry => false"); + continue; + } + + } + else + { + /* new section */ + + if (! addSection(pProfile, i, pStr + 1, pChar - pStr - 1)) + { + SAL_WARN("sal.osl", "Adding section => false"); + continue; + } + + } + } + + return true; +} + +static bool storeProfile(osl_TProfileImpl* pProfile, bool bCleanup) +{ + if (pProfile->m_Lines != nullptr) + { + if (pProfile->m_Flags & FLG_MODIFIED) + { + sal_uInt32 i; + + osl_TFile* pTmpFile = osl_openTmpProfileImpl(pProfile); + + if ( pTmpFile == nullptr ) + { + return false; + } + + OSL_VERIFY(OslProfile_rewindFile(pTmpFile, true)); + + for ( i = 0 ; i < pProfile->m_NoLines ; i++ ) + { + OSL_VERIFY(OslProfile_putLine(pTmpFile, pProfile->m_Lines[i])); + } + + if ( ! writeProfileImpl(pTmpFile) ) + { + if ( pTmpFile->m_pWriteBuf != nullptr ) + { + free(pTmpFile->m_pWriteBuf); + } + + pTmpFile->m_pWriteBuf=nullptr; + pTmpFile->m_nWriteBufLen=0; + pTmpFile->m_nWriteBufFree=0; + + closeFileImpl(pTmpFile,pProfile->m_Flags); + + return false; + } + + pProfile->m_Flags &= ~FLG_MODIFIED; + + closeFileImpl(pProfile->m_pFile,pProfile->m_Flags); + closeFileImpl(pTmpFile,pProfile->m_Flags); + + osl_ProfileSwapProfileNames(pProfile); + + pProfile->m_pFile = openFileImpl(pProfile->m_FileName,pProfile->m_Flags); + + } + + if (bCleanup) + { + while (pProfile->m_NoLines > 0) + removeLine(pProfile, pProfile->m_NoLines - 1); + + free(pProfile->m_Lines); + pProfile->m_Lines = nullptr; + pProfile->m_NoLines = 0; + pProfile->m_MaxLines = 0; + + while (pProfile->m_NoSections > 0) + removeSection(pProfile, &pProfile->m_Sections[pProfile->m_NoSections - 1]); + + free(pProfile->m_Sections); + pProfile->m_Sections = nullptr; + pProfile->m_NoSections = 0; + pProfile->m_MaxSections = 0; + } + } + + return true; +} + +static osl_TFile* osl_openTmpProfileImpl(osl_TProfileImpl* pProfile) +{ + osl_TFile* pFile=nullptr; + char const * const pszExtension = "tmp"; + char pszTmpName[PATH_MAX]; + oslProfileOption PFlags=0; + + pszTmpName[0] = '\0'; + + /* generate tmp profilename */ + osl_ProfileGenerateExtension(pProfile->m_FileName, pszExtension, pszTmpName, PATH_MAX); + + if ( pszTmpName[0] == 0 ) + { + return nullptr; + } + + if ( ! ( pProfile->m_Flags & osl_Profile_READLOCK ) ) + { + PFlags |= osl_Profile_WRITELOCK; + } + + /* open this file */ + pFile = openFileImpl(pszTmpName,pProfile->m_Flags | PFlags); + + /* return new pFile */ + return pFile; +} + +static bool osl_ProfileSwapProfileNames(osl_TProfileImpl* pProfile) +{ + char pszBakFile[PATH_MAX]; + char pszTmpFile[PATH_MAX]; + + pszBakFile[0] = '\0'; + pszTmpFile[0] = '\0'; + + osl_ProfileGenerateExtension(pProfile->m_FileName, "bak", pszBakFile, PATH_MAX); + osl_ProfileGenerateExtension(pProfile->m_FileName, "tmp", pszTmpFile, PATH_MAX); + + /* unlink bak */ + unlink( pszBakFile ); + + // Rename ini -> bak, then tmp -> ini: + bool result = rename( pProfile->m_FileName, pszBakFile ) == 0; + if (!result) + { + int e = errno; + SAL_INFO("sal.file", "rename(" << pProfile->m_FileName << "," << pszBakFile << "): " << UnixErrnoString(e)); + } + else + { + SAL_INFO("sal.file", "rename(" << pProfile->m_FileName << "," << pszBakFile << "): OK"); + result = rename( pszTmpFile, pProfile->m_FileName ) == 0; + if (!result) + { + int e = errno; + SAL_INFO("sal.file", "rename(" << pszTmpFile << "," << pProfile->m_FileName << "): " << UnixErrnoString(e)); + } + else + { + SAL_INFO("sal.file", "rename(" << pszTmpFile << "," << pProfile->m_FileName << "): OK"); + } + } + return result; +} + +static void osl_ProfileGenerateExtension(const char* pszFileName, const char* pszExtension, char* pszTmpName, int BufferMaxLen) +{ + char* cursor = pszTmpName; + int len; + + /* concatenate filename + "." + extension, limited to the size of the + * output buffer; in case of overrun, data is truncated at the end... + * and the result is always 0-terminated. + */ + len = strlen(pszFileName); + if(len < BufferMaxLen) + { + memcpy(cursor, pszFileName, len); + cursor += len; + BufferMaxLen -= len; + } + else + { + memcpy(cursor, pszFileName, BufferMaxLen - 1); + cursor += BufferMaxLen - 1; + BufferMaxLen = 1; + } + if(BufferMaxLen > 1) + { + *cursor++ = '.'; + BufferMaxLen -= 1; + } + len = strlen(pszExtension); + if(len < BufferMaxLen) + { + memcpy(cursor, pszExtension, len); + cursor += len; + } + else + { + memcpy(cursor, pszExtension, BufferMaxLen - 1); + cursor += BufferMaxLen - 1; + } + *cursor = 0; +} + +static osl_TProfileImpl* acquireProfile(oslProfile Profile, bool bWriteable) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + oslProfileOption PFlags=0; + + if ( bWriteable ) + { + PFlags = osl_Profile_DEFAULT | osl_Profile_WRITELOCK; + } + else + { + PFlags = osl_Profile_DEFAULT; + } + + if (pProfile == nullptr) + { + if ( ( pProfile = static_cast<osl_TProfileImpl*>(osl_openProfile(nullptr, PFlags )) ) != nullptr ) + { + pProfile->m_Flags |= FLG_AUTOOPEN; + } + } + else + { + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if (! (pProfile->m_Flags & (osl_Profile_READLOCK | osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ))) + { + osl_TStamp Stamp; + + if (! (pProfile->m_pFile = openFileImpl(pProfile->m_FileName, pProfile->m_Flags | PFlags ))) + return nullptr; + + Stamp = OslProfile_getFileStamp(pProfile->m_pFile); + + if (memcmp(&Stamp, &(pProfile->m_Stamp), sizeof(osl_TStamp))) + { + pProfile->m_Stamp = Stamp; + bool bRet = loadProfile(pProfile->m_pFile, pProfile); + SAL_WARN_IF(!bRet, "sal.osl", "loadProfile(pProfile->m_pFile, pProfile) ==> false"); + } + } + else + { + /* A readlock file could not be written */ + if ((pProfile->m_Flags & osl_Profile_READLOCK) && bWriteable) + { + return nullptr; + } + } + } + } + + return pProfile; +} + +static bool releaseProfile(osl_TProfileImpl* pProfile) +{ + if ( pProfile == nullptr ) + { + return false; + } + + if (pProfile->m_Flags & FLG_AUTOOPEN) + { + return osl_closeProfile(static_cast<oslProfile>(pProfile)); + } + + if (! (pProfile->m_Flags & (osl_Profile_READLOCK | osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ))) + { + if (pProfile->m_Flags & FLG_MODIFIED) + { + bool bRet = storeProfile(pProfile, false); + SAL_WARN_IF(!bRet, "sal.osl", "storeProfile(pProfile, false) ==> false"); + } + + closeFileImpl(pProfile->m_pFile,pProfile->m_Flags); + pProfile->m_pFile = nullptr; + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/random.cxx b/sal/osl/unx/random.cxx new file mode 100644 index 000000000..260aa61ba --- /dev/null +++ b/sal/osl/unx/random.cxx @@ -0,0 +1,46 @@ +/* -*- 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/. + */ + +#include "system.hxx" +#include <oslrandom.h> + +#include <assert.h> + +int osl_get_system_random_data(char* buffer, size_t desired_len) +{ + int fd; + + assert(buffer); + fd = open("/dev/urandom", O_RDONLY); + if (fd != -1) + { + while (desired_len) + { + ssize_t nb_read; + if ((nb_read = read(fd, buffer, desired_len)) == -1) + { + if (errno != EINTR) + { + close(fd); + return false; + } + } + else + { + buffer += nb_read; + desired_len -= nb_read; + } + } + close(fd); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/readwrite_helper.cxx b/sal/osl/unx/readwrite_helper.cxx new file mode 100644 index 000000000..49c44c2c0 --- /dev/null +++ b/sal/osl/unx/readwrite_helper.cxx @@ -0,0 +1,79 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <limits> + +#include "readwrite_helper.hxx" + +#include "system.hxx" + +namespace { + +std::size_t cap_ssize_t(std::size_t value) { + return std::min(value, std::size_t(std::numeric_limits<ssize_t>::max())); +} + +} + +bool safeWrite(int fd, void* data, std::size_t dataSize) +{ + auto nToWrite = dataSize; + unsigned char* dataToWrite = static_cast<unsigned char *>(data); + + while ( nToWrite ) { + auto nWritten = write(fd, dataToWrite, cap_ssize_t(nToWrite)); + if ( nWritten < 0 ) { + if ( errno == EINTR ) + continue; + + return false; + + } + + assert(nWritten > 0); + nToWrite -= nWritten; + dataToWrite += nWritten; + } + + return true; +} + +bool safeRead( int fd, void* buffer, std::size_t count ) +{ + auto nToRead = count; + unsigned char* bufferForReading = static_cast<unsigned char *>(buffer); + + while ( nToRead ) { + auto nRead = read(fd, bufferForReading, cap_ssize_t(nToRead)); + if ( nRead < 0 ) { + // We were interrupted before reading, retry. + if (errno == EINTR) + continue; + + return false; + } + + // If we reach the EOF, we consider this a partial transfer and thus + // an error. + if ( nRead == 0 ) + return false; + + nToRead -= nRead; + bufferForReading += nRead; + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/readwrite_helper.hxx b/sal/osl/unx/readwrite_helper.hxx new file mode 100644 index 000000000..133ccd016 --- /dev/null +++ b/sal/osl/unx/readwrite_helper.hxx @@ -0,0 +1,26 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_UNX_READWRITE_HELPER_HXX +#define INCLUDED_SAL_OSL_UNX_READWRITE_HELPER_HXX + +#include <sal/config.h> + +#include <cstddef> + +bool safeWrite(int fd, void* data, std::size_t dataSize); + +// This function *will* read |count| bytes from |fd|, busy looping +// if needed. Don't use it when you don't know if you can request enough +// data. It will return sal_False for any partial transfer or error. +bool safeRead(int fd, void* buffer, std::size_t count); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/salinit.cxx b/sal/osl/unx/salinit.cxx new file mode 100644 index 000000000..c55cb2667 --- /dev/null +++ b/sal/osl/unx/salinit.cxx @@ -0,0 +1,98 @@ +/* -*- 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 <sal/config.h> + +#if defined MACOSX +#include <cassert> +#include <limits> +#include <unistd.h> +#include <sys/stat.h> +#endif + +#include <config_global.h> +#include <osl/process.h> +#include <sal/main.h> +#include <sal/types.h> + +#include "saltime.hxx" +#include "soffice.hxx" +#include <salusesyslog.hxx> + +#if HAVE_SYSLOG_H +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#endif + +extern "C" { + +void sal_detail_initialize(int argc, char ** argv) { + if (argc == sal::detail::InitializeSoffice) + { + sal::detail::setSoffice(); + return; + } +#if defined MACOSX && !HAVE_FEATURE_MACOSX_SANDBOX + // On macOS when not sandboxed, soffice can restart itself via exec (see + // restartOnMac in desktop/source/app/app.cxx), which leaves all file + // descriptors open, which in turn can have unwanted effects (see + // <https://bugs.libreoffice.org/show_bug.cgi?id=50603> "Unable to update + // LibreOffice without resetting user profile"). But closing fds in + // restartOnMac before calling exec does not work, as additional threads + // might still be running then, which can still use those fds and cause + // crashes. Therefore, the simplest solution is to close fds at process + // start (as early as possible, so that no other threads have been created + // yet that might already have opened some fds); this is done for all kinds + // of processes here, not just soffice, but hopefully none of our processes + // rely on being spawned with certain fds already open. Unfortunately, Mac + // macOS appears to have no better interface to close all fds (like + // closefrom): + long openMax = sysconf(_SC_OPEN_MAX); + // When LibreOffice restarts itself on macOS 11 beta on arm64, for + // some reason sysconf(_SC_OPEN_MAX) returns 0x7FFFFFFFFFFFFFFF, + // so use a sanity limit here. + if (openMax == -1 || openMax == std::numeric_limits<long>::max()) { + openMax = 100000; + } + assert(openMax >= 0 && openMax <= std::numeric_limits< int >::max()); + for (int fd = 3; fd < int(openMax); ++fd) { + struct stat s; + if (fstat(fd, &s) != -1 && S_ISREG(s.st_mode)) + close(fd); + } +#endif + sal_initGlobalTimer(); +#if HAVE_SYSLOG_H + const char *use_syslog = getenv("SAL_LOG_SYSLOG"); + sal_use_syslog = use_syslog != nullptr && !strcmp(use_syslog, "1"); + if (sal_use_syslog) + openlog("libreoffice", 0, LOG_USER); +#endif + + osl_setCommandArgs(argc, argv); +} + +void sal_detail_deinitialize() {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/saltime.hxx b/sal/osl/unx/saltime.hxx new file mode 100644 index 000000000..03e8047e0 --- /dev/null +++ b/sal/osl/unx/saltime.hxx @@ -0,0 +1,29 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SALTIME_HXX +#define INCLUDED_SAL_OSL_UNX_SALTIME_HXX + +#include <sal/config.h> + +void sal_initGlobalTimer(); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/secimpl.hxx b/sal/osl/unx/secimpl.hxx new file mode 100644 index 000000000..1577d6609 --- /dev/null +++ b/sal/osl/unx/secimpl.hxx @@ -0,0 +1,35 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SECIMPL_HXX +#define INCLUDED_SAL_OSL_UNX_SECIMPL_HXX + +#include <pwd.h> + +struct oslSecurityImpl +{ + struct passwd m_pPasswd; + char m_buffer[1]; /* should be a C99 flexible array member */ +}; + +bool osl_psz_getUserIdent(oslSecurity Security, char* pszIdent, sal_uInt32 nMax); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/security.cxx b/sal/osl/unx/security.cxx new file mode 100644 index 000000000..f541c9000 --- /dev/null +++ b/sal/osl/unx/security.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <cassert> +#include <cstddef> +#include <cstring> +#include <limits> + +#ifdef IOS +#include <premac.h> +#import <Foundation/Foundation.h> +#include <postmac.h> +#endif + +#include "system.hxx" + +#include <o3tl/safeint.hxx> +#include <osl/security.h> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> + +#include <osl/thread.h> +#include <osl/file.h> + +#if defined LINUX || defined __sun +#include <crypt.h> +#endif + +#if defined HAIKU +#include <fs_info.h> +#include <FindDirectory.h> +#endif + +#include "secimpl.hxx" + +#ifdef ANDROID +#define getpwuid_r(uid, pwd, buf, buflen, result) (*(result) = getpwuid(uid), (*(result) ? (memcpy (buf, *(result), sizeof (struct passwd)), 0) : errno)) +#endif + +static bool osl_psz_getHomeDir(oslSecurity Security, OString* pszDirectory); +static bool osl_psz_getConfigDir(oslSecurity Security, OString* pszDirectory); + +static bool sysconf_SC_GETPW_R_SIZE_MAX(std::size_t * value) { +#if defined _SC_GETPW_R_SIZE_MAX + long m; + errno = 0; + m = sysconf(_SC_GETPW_R_SIZE_MAX); + if (m == -1) { + /* _SC_GETPW_R_SIZE_MAX has no limit; some platforms like certain + FreeBSD versions support sysconf(_SC_GETPW_R_SIZE_MAX) in a broken + way and always set EINVAL, so be resilient here: */ + return false; + } + SAL_WARN_IF( m < 0 || o3tl::make_unsigned(m) >= std::numeric_limits<std::size_t>::max(), "sal.osl", + "m < 0 || (unsigned long) m >= std::numeric_limits<std::size_t>::max()"); + *value = static_cast<std::size_t>(m); + return true; +#else + /* some platforms like macOS 1.3 do not define _SC_GETPW_R_SIZE_MAX: */ + return false; +#endif +} + +static oslSecurityImpl * growSecurityImpl( + oslSecurityImpl * impl, std::size_t * bufSize) +{ + std::size_t n = 0; + oslSecurityImpl * p = nullptr; + if (impl == nullptr) { + if (!sysconf_SC_GETPW_R_SIZE_MAX(&n)) { + /* choose something sensible (the callers of growSecurityImpl will + detect it if the allocated buffer is too small: */ + n = 1024; + } + } else if (*bufSize <= std::numeric_limits<std::size_t>::max() / 2) { + n = 2 * *bufSize; + } + if (n != 0) { + if (n <= std::numeric_limits<std::size_t>::max() + - offsetof(oslSecurityImpl, m_buffer)) + { + *bufSize = n; + n += offsetof(oslSecurityImpl, m_buffer); + } else { + *bufSize = std::numeric_limits<std::size_t>::max() + - offsetof(oslSecurityImpl, m_buffer); + n = std::numeric_limits<std::size_t>::max(); + } + p = static_cast<oslSecurityImpl *>(realloc(impl, n)); + memset (p, 0, n); + } + if (p == nullptr) { + free(impl); + } + return p; +} + +static void deleteSecurityImpl(oslSecurityImpl * impl) { + free(impl); +} + +oslSecurity SAL_CALL osl_getCurrentSecurity() +{ + std::size_t n = 0; + oslSecurityImpl * p = nullptr; + for (;;) { + struct passwd * found; + p = growSecurityImpl(p, &n); + if (p == nullptr) { + return nullptr; + } +#if (defined(IOS) && defined(X86_64)) || defined(EMSCRIPTEN) + // getpwuid_r() does not work in the iOS simulator + (void) found; + char * buffer = p->m_buffer; + assert(n >= 100); + strcpy(buffer, "mobile"); + p->m_pPasswd.pw_name = buffer; + buffer += strlen(buffer) + 1; + strcpy(buffer, "*"); + p->m_pPasswd.pw_passwd = buffer; + buffer += strlen(buffer) + 1; + p->m_pPasswd.pw_uid = geteuid(); + p->m_pPasswd.pw_gid = getegid(); +#if !defined(EMSCRIPTEN) + p->m_pPasswd.pw_change = 0; + strcpy(buffer, ""); + p->m_pPasswd.pw_class = buffer; + buffer += strlen(buffer) + 1; + p->m_pPasswd.pw_expire = 0; +#endif + strcpy(buffer, "Mobile User"); + p->m_pPasswd.pw_gecos = buffer; + buffer += strlen(buffer) + 1; + strcpy(buffer, "/var/mobile"); // ??? + p->m_pPasswd.pw_dir = buffer; + buffer += strlen(buffer) + 1; + strcpy(buffer, ""); + p->m_pPasswd.pw_shell = buffer; + buffer += strlen(buffer) + 1; + return p; +#else + switch (getpwuid_r(getuid(), &p->m_pPasswd, p->m_buffer, n, &found)) { + case ERANGE: + break; + case 0: + if (found != nullptr) { + return p; + } + [[fallthrough]]; + default: + deleteSecurityImpl(p); + return nullptr; + } +#endif + } +} + +oslSecurityError SAL_CALL osl_loginUser( + SAL_UNUSED_PARAMETER rtl_uString *, + SAL_UNUSED_PARAMETER rtl_uString *, + SAL_UNUSED_PARAMETER oslSecurity * + ) +{ + return osl_Security_E_None; +} + +oslSecurityError SAL_CALL osl_loginUserOnFileServer( + SAL_UNUSED_PARAMETER rtl_uString *, + SAL_UNUSED_PARAMETER rtl_uString *, + SAL_UNUSED_PARAMETER rtl_uString *, + SAL_UNUSED_PARAMETER oslSecurity * + ) +{ + return osl_Security_E_UserUnknown; +} + +sal_Bool SAL_CALL osl_getUserIdent(oslSecurity Security, rtl_uString **ustrIdent) +{ + bool bRet = false; + char pszIdent[1024]; + + pszIdent[0] = '\0'; + + bRet = osl_psz_getUserIdent(Security,pszIdent,sizeof(pszIdent)); + + rtl_string2UString( ustrIdent, pszIdent, rtl_str_getLength( pszIdent ), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS ); + SAL_WARN_IF(*ustrIdent == nullptr, "sal.osl", "*ustrIdent == NULL"); + + return bRet; +} + +bool osl_psz_getUserIdent(oslSecurity Security, char *pszIdent, sal_uInt32 nMax) +{ + char buffer[32]; + sal_Int32 nChr; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl *>(Security); + + if (pSecImpl == nullptr) + return false; + + nChr = snprintf(buffer, sizeof(buffer), "%u", pSecImpl->m_pPasswd.pw_uid); + if ( nChr < 0 || sal::static_int_cast<sal_uInt32>(nChr) >= sizeof(buffer) + || sal::static_int_cast<sal_uInt32>(nChr) >= nMax ) + return false; /* leave *pszIdent unmodified in case of failure */ + + memcpy(pszIdent, buffer, nChr+1); + return true; +} + +sal_Bool SAL_CALL osl_getUserName(oslSecurity Security, rtl_uString **ustrName) +{ + bool bRet = false; + char * pszName; + sal_Int32 len; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl *>(Security); + + if (pSecImpl != nullptr && pSecImpl->m_pPasswd.pw_name != nullptr) { + pszName = pSecImpl->m_pPasswd.pw_name; + auto const n = std::strlen(pszName); + if (n <= o3tl::make_unsigned(std::numeric_limits<sal_Int32>::max())) { + len = n; + bRet = true; + } + } + + if (!bRet) { + pszName = nullptr; + len = 0; + } + + rtl_string2UString( ustrName, pszName, len, osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS ); + SAL_WARN_IF(*ustrName == nullptr, "sal.osl", "ustrName == NULL"); + + return bRet; +} + +sal_Bool SAL_CALL osl_getShortUserName(oslSecurity Security, rtl_uString **ustrName) +{ + return osl_getUserName(Security, ustrName); // No domain name on unix +} + +sal_Bool SAL_CALL osl_getHomeDir(oslSecurity Security, rtl_uString **pustrDirectory) +{ + bool bRet = false; + OString pszDirectory; + + bRet = osl_psz_getHomeDir(Security,&pszDirectory); + + if ( bRet ) + { + rtl_string2UString( pustrDirectory, pszDirectory.getStr(), pszDirectory.getLength(), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS ); + SAL_WARN_IF(*pustrDirectory == nullptr, "sal.osl", "*pustrDirectory == NULL"); + osl_getFileURLFromSystemPath( *pustrDirectory, pustrDirectory ); + } + + return bRet; +} + +static bool osl_psz_getHomeDir(oslSecurity Security, OString* pszDirectory) +{ + assert(pszDirectory != nullptr); + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl *>(Security); + + if (pSecImpl == nullptr) + return false; + +#ifdef HAIKU + dev_t volume = dev_for_path("/boot"); + char homeDir[B_PATH_NAME_LENGTH + B_FILE_NAME_LENGTH]; + status_t result = find_directory(B_USER_DIRECTORY, volume, false, homeDir, + sizeof(homeDir)); + if (result == B_OK) { + static_assert( + B_PATH_NAME_LENGTH + B_FILE_NAME_LENGTH <= std::numeric_limits<sal_Int32>::max()); + *pszDirectory = OString(homeDir, std::strlen(homeDir)); + return true; + } + return false; +#endif + +#ifdef ANDROID +{ + OUString pValue; + + if (rtl::Bootstrap::get("HOME", pValue)) + { + auto const pStrValue = OUStringToOString(pValue, RTL_TEXTENCODING_UTF8); + if (!pStrValue.isEmpty()) + { + *pszDirectory = pStrValue; + return true; + } + } +} +#endif + +#ifdef IOS + { + // Let's pretend the app-specific "Documents" directory is the home directory for now + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *userDirectory = [paths objectAtIndex:0]; + auto const len = [userDirectory length]; + if (len <= std::numeric_limits<sal_Int32>::max()) + { + *pszDirectory = OString([userDirectory UTF8String], len); + return sal_True; + } + } +#endif + + /* if current user, check also environment for HOME */ + if (getuid() == pSecImpl->m_pPasswd.pw_uid) + { + char *pStr = nullptr; +#ifdef __sun + char buffer[8192]; + + struct passwd pwd; + struct passwd *ppwd; + +#ifdef _POSIX_PTHREAD_SEMANTICS + if ( 0 != getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &ppwd ) ) + ppwd = NULL; +#else + ppwd = getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer) ); +#endif + + if ( ppwd ) + pStr = ppwd->pw_dir; +#else + pStr = getenv("HOME"); +#endif + + if (pStr != nullptr && pStr[0] != '\0' && access(pStr, 0) == 0) + { + auto const len = std::strlen(pStr); + if (len > o3tl::make_unsigned(std::numeric_limits<sal_Int32>::max())) { + return false; + } + *pszDirectory = OString(pStr, len); + return true; + } + } + if (pSecImpl->m_pPasswd.pw_dir != nullptr) + { + auto const len = std::strlen(pSecImpl->m_pPasswd.pw_dir); + if (len > o3tl::make_unsigned(std::numeric_limits<sal_Int32>::max())) { + return false; + } + *pszDirectory = OString(pSecImpl->m_pPasswd.pw_dir, len); + } + else + return false; + + return true; +} + +sal_Bool SAL_CALL osl_getConfigDir(oslSecurity Security, rtl_uString **pustrDirectory) +{ + bool bRet = false; + OString pszDirectory; + + bRet = osl_psz_getConfigDir(Security,&pszDirectory); + + if ( bRet ) + { + rtl_string2UString( pustrDirectory, pszDirectory.getStr(), pszDirectory.getLength(), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS ); + SAL_WARN_IF(*pustrDirectory == nullptr, "sal.osl", "*pustrDirectory == NULL"); + osl_getFileURLFromSystemPath( *pustrDirectory, pustrDirectory ); + } + + return bRet; +} + +#if defined HAIKU + +static bool osl_psz_getConfigDir(oslSecurity Security, OString* pszDirectory) +{ + assert(pszDirectory != nullptr); + (void) Security; + dev_t volume = dev_for_path("/boot"); + char configDir[B_PATH_NAME_LENGTH + B_FILE_NAME_LENGTH]; + status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, volume, false, + configDir, sizeof(configDir)); + if (result == B_OK) { + auto const len = strlen(configDir); + if (len <= sal_uInt32(std::numeric_limits<sal_Int32>::max())) { + *pszDirectory = OString(configDir, len); + return true; + } + } + return false; +} + +#elif !defined(MACOSX) && !defined(IOS) + +static bool osl_psz_getConfigDir(oslSecurity Security, OString* pszDirectory) +{ + assert(pszDirectory != nullptr); + + char *pStr = getenv("XDG_CONFIG_HOME"); + + if (pStr == nullptr || pStr[0] == '\0' || access(pStr, 0) != 0) + { + // a default equal to $HOME/.config should be used. + OString home; + if (!osl_psz_getHomeDir(Security, &home)) + return false; + auto const config = OString(home + "/.config"); + + // try to create dir if not present + bool dirOK = true; + if (mkdir(config.getStr(), S_IRWXU) != 0) + { + int e = errno; + if (e != EEXIST) + { + SAL_WARN( + "sal.osl", + "mkdir(" << config << "): errno=" << e); + dirOK = false; + } + } + if (dirOK) + { + // check file type and permissions + struct stat st; + if (stat(config.getStr(), &st) != 0) + { + SAL_INFO("sal.osl","Could not stat $HOME/.config"); + dirOK = false; + } + else + { + if (!S_ISDIR(st.st_mode)) + { + SAL_INFO("sal.osl", "$HOME/.config is not a directory"); + dirOK = false; + } + if (!(st.st_mode & S_IRUSR && st.st_mode & S_IWUSR && st.st_mode & S_IXUSR)) + { + SAL_INFO("sal.osl", "$HOME/.config has bad permissions"); + dirOK = false; + } + } + } + + // if !dirOK, resort to HOME + if (dirOK) + home = config; + *pszDirectory = home; + } + else + { + auto const len = std::strlen(pStr); + if (len > o3tl::make_unsigned(std::numeric_limits<sal_Int32>::max())) { + return false; + } + *pszDirectory = OString(pStr, len); + } + + return true; +} + +#else + +/* + * FIXME: rewrite to use more flexible + * NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) + * as soon as we can bump the baseline to Tiger (for NSApplicationSupportDirectory) and have + * support for Objective-C in the build environment + */ + +static bool osl_psz_getConfigDir(oslSecurity Security, OString* pszDirectory) +{ + assert(pszDirectory != nullptr); + + OString home; + if( osl_psz_getHomeDir(Security, &home) ) + { + *pszDirectory = home + "/Library/Application Support"; /* Used on iOS, too */ + return true; + } + + return false; +} + +#endif + +sal_Bool SAL_CALL osl_isAdministrator(oslSecurity Security) +{ + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl *>(Security); + + if (pSecImpl == nullptr) + return false; + + if (pSecImpl->m_pPasswd.pw_uid != 0) + return false; + + return true; +} + +void SAL_CALL osl_freeSecurityHandle(oslSecurity Security) +{ + deleteSecurityImpl(static_cast<oslSecurityImpl *>(Security)); +} + +sal_Bool SAL_CALL osl_loadUserProfile(SAL_UNUSED_PARAMETER oslSecurity) +{ + return false; +} + +void SAL_CALL osl_unloadUserProfile(SAL_UNUSED_PARAMETER oslSecurity) {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/signal.cxx b/sal/osl/unx/signal.cxx new file mode 100644 index 000000000..91cf59ff1 --- /dev/null +++ b/sal/osl/unx/signal.cxx @@ -0,0 +1,452 @@ +/* -*- 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 <signalshared.hxx> + +#include <config_features.h> + +#include "soffice.hxx" +/* system headers */ +#include "system.hxx" + +#include "backtrace.h" + +#define MAX_STACK_FRAMES 256 + +#include <osl/diagnose.h> +#include <osl/signal.h> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <rtl/bootstrap.h> +#include <rtl/digest.h> + +#include "file_path_helper.hxx" +#define ACT_IGNORE 1 +#define ACT_EXIT 2 +#define ACT_SYSTEM 3 +#define ACT_HIDE 4 +#define ACT_ABORT 5 + +#if defined HAVE_VALGRIND_HEADERS +#include <valgrind/memcheck.h> +#endif + +namespace +{ +extern "C" using Handler1 = void (*)(int); +extern "C" using Handler2 = void (*)(int, siginfo_t *, void *); +struct SignalAction +{ + int Signal; + int Action; + Handler1 Handler; + bool siginfo; // Handler's type is Handler2 +} Signals[] = +{ + { SIGHUP, ACT_HIDE, SIG_DFL, false }, /* hangup */ + { SIGINT, ACT_EXIT, SIG_DFL, false }, /* interrupt (rubout) */ + { SIGQUIT, ACT_EXIT, SIG_DFL, false }, /* quit (ASCII FS) */ + { SIGILL, ACT_SYSTEM, SIG_DFL, false }, /* illegal instruction (not reset when caught) */ +/* changed from ACT_ABOUT to ACT_SYSTEM to try and get collector to run*/ + { SIGTRAP, ACT_ABORT, SIG_DFL, false }, /* trace trap (not reset when caught) */ +#if ( SIGIOT != SIGABRT ) + { SIGIOT, ACT_ABORT, SIG_DFL, false }, /* IOT instruction */ +#endif +#if defined(FORCE_DEFAULT_SIGNAL) + { SIGABRT, ACT_SYSTEM, SIG_DFL, false }, /* used by abort, replace SIGIOT in the future */ +#else + { SIGABRT, ACT_ABORT, SIG_DFL, false }, /* used by abort, replace SIGIOT in the future */ +#endif +#ifdef SIGEMT + { SIGEMT, ACT_SYSTEM, SIG_DFL, false }, /* EMT instruction */ +/* changed from ACT_ABORT to ACT_SYSTEM to remove handler*/ +/* SIGEMT may also be used by the profiler - so it is probably not a good +plan to have the new handler use this signal*/ +#endif + { SIGFPE, ACT_ABORT, SIG_DFL, false }, /* floating point exception */ + { SIGKILL, ACT_SYSTEM, SIG_DFL, false }, /* kill (cannot be caught or ignored) */ + { SIGBUS, ACT_ABORT, SIG_DFL, false }, /* bus error */ +#if defined(FORCE_DEFAULT_SIGNAL) + { SIGSEGV, ACT_SYSTEM, SIG_DFL, false }, /* segmentation violation */ +#else + { SIGSEGV, ACT_ABORT, SIG_DFL, false }, /* segmentation violation */ +#endif +#ifdef SIGSYS + { SIGSYS, ACT_ABORT, SIG_DFL, false }, /* bad argument to system call */ +#endif + { SIGPIPE, ACT_HIDE, SIG_DFL, false }, /* write on a pipe with no one to read it */ +#if defined(FORCE_DEFAULT_SIGNAL) + { SIGALRM, ACT_SYSTEM, SIG_DFL, false }, /* alarm clock */ +#else + { SIGALRM, ACT_EXIT, SIG_DFL, false }, /* alarm clock */ +#endif + { SIGTERM, ACT_EXIT, SIG_DFL, false }, /* software termination signal from kill */ + { SIGUSR1, ACT_SYSTEM, SIG_DFL, false }, /* user defined signal 1 */ + { SIGUSR2, ACT_SYSTEM, SIG_DFL, false }, /* user defined signal 2 */ + { SIGCHLD, ACT_SYSTEM, SIG_DFL, false }, /* child status change */ +#ifdef SIGPWR + { SIGPWR, ACT_IGNORE, SIG_DFL, false }, /* power-fail restart */ +#endif + { SIGWINCH, ACT_IGNORE, SIG_DFL, false }, /* window size change */ + { SIGURG, ACT_EXIT, SIG_DFL, false }, /* urgent socket condition */ +#ifdef SIGPOLL + { SIGPOLL, ACT_EXIT, SIG_DFL, false }, /* pollable event occurred */ +#endif + { SIGSTOP, ACT_SYSTEM, SIG_DFL, false }, /* stop (cannot be caught or ignored) */ + { SIGTSTP, ACT_SYSTEM, SIG_DFL, false }, /* user stop requested from tty */ + { SIGCONT, ACT_SYSTEM, SIG_DFL, false }, /* stopped process has been continued */ + { SIGTTIN, ACT_SYSTEM, SIG_DFL, false }, /* background tty read attempted */ + { SIGTTOU, ACT_SYSTEM, SIG_DFL, false }, /* background tty write attempted */ + { SIGVTALRM, ACT_EXIT, SIG_DFL, false }, /* virtual timer expired */ + { SIGPROF, ACT_SYSTEM, SIG_DFL, false }, /* profiling timer expired */ +/*Change from ACT_EXIT to ACT_SYSTEM for SIGPROF is so that profiling signals do +not get taken by the new handler - the new handler does not pass on context +information which causes 'collect' to crash. This is a way of avoiding +what looks like a bug in the new handler*/ + { SIGXCPU, ACT_ABORT, SIG_DFL, false }, /* exceeded cpu limit */ + { SIGXFSZ, ACT_ABORT, SIG_DFL, false } /* exceeded file size limit */ +}; +const int NoSignals = SAL_N_ELEMENTS(Signals); + +bool bSetSEGVHandler = false; +bool bSetWINCHHandler = false; +bool bSetILLHandler = false; + +void signalHandlerFunction(int, siginfo_t *, void *); + +#if HAVE_FEATURE_BREAKPAD +bool is_unset_signal(int signal) +{ +#ifdef DBG_UTIL + return (!bSetSEGVHandler && signal == SIGSEGV) || + (!bSetWINCHHandler && signal == SIGWINCH) || + (!bSetILLHandler && signal == SIGILL); +#else + (void) signal; + return false; +#endif +} +#endif + +} + +bool onInitSignal() +{ + if (sal::detail::isSoffice()) + { + // WORKAROUND FOR SEGV HANDLER CONFLICT + // + // the java jit needs SIGSEGV for proper work + // and we need SIGSEGV for the office crashguard + // + // TEMPORARY SOLUTION: + // the office sets the signal handler during startup + // java can than overwrite it, if needed + bSetSEGVHandler = true; + + // WORKAROUND FOR WINCH HANDLER (SEE ABOVE) + bSetWINCHHandler = true; + + // WORKAROUND FOR ILLEGAL INSTRUCTION HANDLER (SEE ABOVE) + bSetILLHandler = true; + } + +#ifdef DBG_UTIL + bSetSEGVHandler = bSetWINCHHandler = bSetILLHandler = false; +#endif + + struct sigaction act; + act.sa_sigaction = signalHandlerFunction; + act.sa_flags = SA_RESTART | SA_SIGINFO; + + sigfillset(&(act.sa_mask)); + + /* Initialize the rest of the signals */ + for (SignalAction & rSignal : Signals) + { +#if defined HAVE_VALGRIND_HEADERS + if (rSignal.Signal == SIGUSR2 && RUNNING_ON_VALGRIND) + rSignal.Action = ACT_IGNORE; +#endif + + /* hack: stomcatd is attaching JavaVM which does not work with an sigaction(SEGV) */ + if ((bSetSEGVHandler || rSignal.Signal != SIGSEGV) + && (bSetWINCHHandler || rSignal.Signal != SIGWINCH) + && (bSetILLHandler || rSignal.Signal != SIGILL)) + { + if (rSignal.Action != ACT_SYSTEM) + { + if (rSignal.Action == ACT_HIDE) + { + struct sigaction ign; + + ign.sa_handler = SIG_IGN; + ign.sa_flags = 0; + sigemptyset(&ign.sa_mask); + + struct sigaction oact; + if (sigaction(rSignal.Signal, &ign, &oact) == 0) { + rSignal.siginfo = (oact.sa_flags & SA_SIGINFO) != 0; + if (rSignal.siginfo) { + rSignal.Handler = reinterpret_cast<Handler1>( + oact.sa_sigaction); + } else { + rSignal.Handler = oact.sa_handler; + } + } else { + rSignal.Handler = SIG_DFL; + rSignal.siginfo = false; + } + } + else + { + struct sigaction oact; + if (sigaction(rSignal.Signal, &act, &oact) == 0) { + rSignal.siginfo = (oact.sa_flags & SA_SIGINFO) != 0; + if (rSignal.siginfo) { + rSignal.Handler = reinterpret_cast<Handler1>( + oact.sa_sigaction); + } else { + rSignal.Handler = oact.sa_handler; + } + } else { + rSignal.Handler = SIG_DFL; + rSignal.siginfo = false; + } + } + } + } + } + + /* Clear signal mask inherited from parent process (on macOS, upon a + crash soffice re-execs itself from within the signal handler, so the + second soffice would have the guilty signal blocked and would freeze upon + encountering a similar crash again): */ + sigset_t unset; + if (sigemptyset(&unset) < 0 || + pthread_sigmask(SIG_SETMASK, &unset, nullptr) < 0) + { + SAL_WARN("sal.osl", "sigemptyset or pthread_sigmask failed"); + } + + return true; +} + +bool onDeInitSignal() +{ + struct sigaction act; + + sigemptyset(&(act.sa_mask)); + + /* Initialize the rest of the signals */ + for (int i = NoSignals - 1; i >= 0; i--) + if (Signals[i].Action != ACT_SYSTEM + && ((bSetSEGVHandler || Signals[i].Signal != SIGSEGV) + && (bSetWINCHHandler || Signals[i].Signal != SIGWINCH) + && (bSetILLHandler || Signals[i].Signal != SIGILL))) + { + if (Signals[i].siginfo) { + act.sa_sigaction = reinterpret_cast<Handler2>( + Signals[i].Handler); + act.sa_flags = SA_SIGINFO; + } else { + act.sa_handler = Signals[i].Handler; + act.sa_flags = 0; + } + + sigaction(Signals[i].Signal, &act, nullptr); + } + + return false; +} + +namespace +{ +void printStack(int sig) +{ + void *buffer[MAX_STACK_FRAMES]; + int size = backtrace( buffer, SAL_N_ELEMENTS(buffer) ); + + fprintf( stderr, "\n\nFatal exception: Signal %d\n", sig ); + +#if ! HAVE_FEATURE_BACKTRACE && defined( MACOSX ) && !defined( INTEL ) + fprintf( stderr, "Please turn on Enable Crash Reporting and\nAutomatic Display of Crashlogs in the Console application\n" ); +#endif + + if ( size > 0 ) + { + fputs( "Stack:\n", stderr ); + backtrace_symbols_fd( buffer, size, fileno(stderr) ); + } +} + +void callSystemHandler(int signal, siginfo_t * info, void * context) +{ + int i; + + for (i = 0; i < NoSignals; i++) + { + if (Signals[i].Signal == signal) + break; + } + + if (i >= NoSignals) + return; + + if ((Signals[i].Handler == SIG_DFL) || + (Signals[i].Handler == SIG_IGN) || + (Signals[i].Handler == SIG_ERR)) + { + switch (Signals[i].Action) + { + case ACT_EXIT: /* terminate */ + /* prevent dumping core on exit() */ + _exit(255); + break; + + case ACT_ABORT: /* terminate with core dump */ + struct sigaction act; + act.sa_handler = SIG_DFL; + act.sa_flags = 0; + sigemptyset(&(act.sa_mask)); + sigaction(SIGABRT, &act, nullptr); + printStack( signal ); + abort(); + break; + + case ACT_IGNORE: /* ignore */ + break; + + default: /* should never happen */ + OSL_ASSERT(false); + } + } + else if (Signals[i].siginfo) { + (*reinterpret_cast<Handler2>(Signals[i].Handler))( + signal, info, context); + } else { + (*Signals[i].Handler)(signal); + } +} + +#if defined HAVE_VALGRIND_HEADERS +void DUMPCURRENTALLOCS() +{ + VALGRIND_PRINTF( "=== start memcheck dump of active allocations ===\n" ); + +#if __GNUC__ && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + + VALGRIND_DO_LEAK_CHECK; + +#if __GNUC__ && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + + VALGRIND_PRINTF( "=== end memcheck dump of active allocations ===\n" ); +} +#endif + +void signalHandlerFunction(int signal, siginfo_t * info, void * context) +{ + oslSignalInfo Info; + + Info.UserSignal = signal; + Info.UserData = nullptr; + + switch (signal) + { + case SIGBUS: + case SIGILL: + case SIGSEGV: + case SIGIOT: +#if ( SIGIOT != SIGABRT ) + case SIGABRT: +#endif + Info.Signal = osl_Signal_AccessViolation; + break; + + case -1: + Info.Signal = osl_Signal_IntegerDivideByZero; + break; + + case SIGFPE: + Info.Signal = osl_Signal_FloatDivideByZero; + break; + + case SIGINT: + case SIGTERM: + case SIGQUIT: + Info.Signal = osl_Signal_Terminate; + break; + +#if defined HAVE_VALGRIND_HEADERS + case SIGUSR2: + if (RUNNING_ON_VALGRIND) + DUMPCURRENTALLOCS(); + Info.Signal = osl_Signal_System; + break; +#endif + + default: + Info.Signal = osl_Signal_System; + break; + } + +#if HAVE_FEATURE_BREAKPAD + if ((Info.Signal == osl_Signal_AccessViolation || + Info.Signal == osl_Signal_IntegerDivideByZero || + Info.Signal == osl_Signal_FloatDivideByZero) && !is_unset_signal(signal)) + { + callSystemHandler(signal, info, context); + } +#endif + + switch (callSignalHandler(&Info)) + { + case osl_Signal_ActCallNextHdl: + callSystemHandler(signal, info, context); + break; + + case osl_Signal_ActAbortApp: + struct sigaction act; + act.sa_handler = SIG_DFL; + act.sa_flags = 0; + sigemptyset(&(act.sa_mask)); + sigaction(SIGABRT, &act, nullptr); + printStack( signal ); + abort(); + break; + + case osl_Signal_ActKillApp: + /* prevent dumping core on exit() */ + _exit(255); + break; + default: + break; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/socket.cxx b/sal/osl/unx/socket.cxx new file mode 100644 index 000000000..535c1727c --- /dev/null +++ b/sal/osl/unx/socket.cxx @@ -0,0 +1,2046 @@ +/* -*- 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 <utility> + +#include "system.hxx" + +#include <osl/socket.h> +#include <osl/mutex.h> +#include <osl/signal.h> + +#include <rtl/alloc.h> +#include <rtl/byteseq.h> +#include <rtl/ustring.hxx> +#include <assert.h> +#include <sal/types.h> +#include <sal/log.hxx> + +#include "sockimpl.hxx" +#include "unixerrnostring.hxx" +#include <oslsocket.hxx> + +#include <poll.h> + +/* defines for shutdown */ +#define SD_RECEIVE 0 +#define SD_SEND 1 +#define SD_BOTH 2 + +/* + oslSocketAddr is a pointer to a Berkeley struct sockaddr. + I refrained from using sockaddr_in because of possible further + extensions of this socket-interface (IP-NG?). + The intention was to hide all Berkeley data-structures from + direct access past the osl-interface. + + The current implementation is internet (IP) centered. All + the constructor-functions (osl_create...) take parameters + that will probably make sense only in the IP-environment + (e.g. because of using the dotted-address-format). + + If the interface will be extended to host other protocol- + families, I expect no externally visible changes in the + existing functions. You'll probably need only new + constructor-functions who take the different address + formats into consideration (maybe a long dotted address + or whatever). +*/ + +/* _Note_ that I rely on the fact that oslSocketAddr and struct sockaddr */ +/* are the same! I don't like it very much but see no other easy way to */ +/* conceal the struct sockaddr from the eyes of the user. */ + +#define OSL_INVALID_SOCKET -1 +#define OSL_SOCKET_ERROR -1 + +/* Buffer size for getnameinfo */ +#define MAX_HOSTBUFFER_SIZE 2048 + +const unsigned long FamilyMap[]= { + AF_INET, /* osl_Socket_FamilyInet */ + AF_IPX, /* osl_Socket_FamilyIpx */ + 0 /* osl_Socket_FamilyInvalid */ +}; + +static oslAddrFamily osl_AddrFamilyFromNative(sal_uInt32 nativeType) +{ + oslAddrFamily i= oslAddrFamily(0); + + while(i != osl_Socket_FamilyInvalid) + { + if(FamilyMap[i] == nativeType) + return i; + i = static_cast<oslAddrFamily>( i + 1 ); + } + + return i; +} + +#define FAMILY_FROM_NATIVE(y) osl_AddrFamilyFromNative(y) +#define FAMILY_TO_NATIVE(x) static_cast<short>(FamilyMap[x]) + +const sal_uInt32 ProtocolMap[]= { + 0, /* osl_Socket_ProtocolIp */ + NSPROTO_IPX, /* osl_Socket_ProtocolIpx */ + NSPROTO_SPX, /* osl_Socket_ProtocolSpx */ + NSPROTO_SPXII, /* osl_Socket_ProtocolSpxII */ + 0 /* osl_Socket_ProtocolInvalid */ +}; + +#define PROTOCOL_TO_NATIVE(x) ProtocolMap[x] + +const sal_uInt32 TypeMap[]= { + SOCK_STREAM, /* osl_Socket_TypeStream */ + SOCK_DGRAM, /* osl_Socket_TypeDgram */ + SOCK_RAW, /* osl_Socket_TypeRaw */ + SOCK_RDM, /* osl_Socket_TypeRdm */ + SOCK_SEQPACKET, /* osl_Socket_TypeSeqPacket */ + 0 /* osl_Socket_TypeInvalid */ +}; + +static oslSocketType osl_SocketTypeFromNative(sal_uInt32 nativeType) +{ + oslSocketType i= oslSocketType(0); + + while(i != osl_Socket_TypeInvalid) + { + if(TypeMap[i] == nativeType) + return i; + i = static_cast<oslSocketType>(i + 1); + } + + return i; +} + +#define TYPE_TO_NATIVE(x) TypeMap[x] +#define TYPE_FROM_NATIVE(y) osl_SocketTypeFromNative(y) + +const sal_uInt32 OptionMap[]= { + SO_DEBUG, /* osl_Socket_OptionDebug */ + SO_ACCEPTCONN, /* osl_Socket_OptionAcceptConn */ + SO_REUSEADDR, /* osl_Socket_OptionReuseAddr */ + SO_KEEPALIVE, /* osl_Socket_OptionKeepAlive */ + SO_DONTROUTE, /* osl_Socket_OptionDontRoute */ + SO_BROADCAST, /* osl_Socket_OptionBroadcast */ + SO_USELOOPBACK, /* osl_Socket_OptionUseLoopback */ + SO_LINGER, /* osl_Socket_OptionLinger */ + SO_OOBINLINE, /* osl_Socket_OptionOOBinLine */ + SO_SNDBUF, /* osl_Socket_OptionSndBuf */ + SO_RCVBUF, /* osl_Socket_OptionRcvBuf */ + SO_SNDLOWAT, /* osl_Socket_OptionSndLowat */ + SO_RCVLOWAT, /* osl_Socket_OptionRcvLowat */ + SO_SNDTIMEO, /* osl_Socket_OptionSndTimeo */ + SO_RCVTIMEO, /* osl_Socket_OptionRcvTimeo */ + SO_ERROR, /* osl_Socket_OptionError */ + SO_TYPE, /* osl_Socket_OptionType */ + TCP_NODELAY, /* osl_Socket_OptionTcpNoDelay */ + 0 /* osl_Socket_OptionInvalid */ +}; + +#define OPTION_TO_NATIVE(x) OptionMap[x] + +const sal_uInt32 OptionLevelMap[]= { + SOL_SOCKET, /* osl_Socket_LevelSocket */ + IPPROTO_TCP, /* osl_Socket_LevelTcp */ + 0 /* osl_Socket_LevelInvalid */ +}; + +#define OPTION_LEVEL_TO_NATIVE(x) OptionLevelMap[x] + +const sal_uInt32 SocketMsgFlagMap[]= { + 0, /* osl_Socket_MsgNormal */ + MSG_OOB, /* osl_Socket_MsgOOB */ + MSG_PEEK, /* osl_Socket_MsgPeek */ + MSG_DONTROUTE, /* osl_Socket_MsgDontRoute */ + MSG_MAXIOVLEN, /* osl_Socket_MsgMaxIOVLen */ + 0 /* osl_Socket_MsgInvalid */ +}; + +#define MSG_FLAG_TO_NATIVE(x) SocketMsgFlagMap[x] + +const sal_uInt32 SocketDirection[]= { + SD_RECEIVE, /* osl_Socket_DirRead */ + SD_SEND, /* osl_Socket_DirWrite */ + SD_BOTH, /* osl_Socket_DirReadWrite */ + 0 /* osl_Socket_DirInvalid */ +}; + +#define DIRECTION_TO_NATIVE(x) SocketDirection[x] + +const struct +{ + int errcode; + oslSocketError error; +} SocketError[]= { + { 0, osl_Socket_E_None }, /* no error */ + { ENOTSOCK, osl_Socket_E_NotSocket }, /* Socket operation on non-socket */ + { EDESTADDRREQ, osl_Socket_E_DestAddrReq }, /* Destination address required */ + { EMSGSIZE, osl_Socket_E_MsgSize }, /* Message too long */ + { EPROTOTYPE, osl_Socket_E_Prototype }, /* Protocol wrong type for socket */ + { ENOPROTOOPT, osl_Socket_E_NoProtocol }, /* Protocol not available */ + { EPROTONOSUPPORT, osl_Socket_E_ProtocolNoSupport }, /* Protocol not supported */ +#ifdef ESOCKTNOSUPPORT + { ESOCKTNOSUPPORT, osl_Socket_E_TypeNoSupport }, /* Socket type not supported */ +#endif + { EOPNOTSUPP, osl_Socket_E_OpNotSupport }, /* Operation not supported on socket */ + { EPFNOSUPPORT, osl_Socket_E_PfNoSupport }, /* Protocol family not supported */ + { EAFNOSUPPORT, osl_Socket_E_AfNoSupport }, /* Address family not supported by + protocol family */ + { EADDRINUSE, osl_Socket_E_AddrInUse }, /* Address already in use */ + { EADDRNOTAVAIL, osl_Socket_E_AddrNotAvail }, /* Can't assign requested address */ + { ENETDOWN, osl_Socket_E_NetDown }, /* Network is down */ + { ENETUNREACH, osl_Socket_E_NetUnreachable }, /* Network is unreachable */ + { ENETRESET, osl_Socket_E_NetReset }, /* Network dropped connection because + of reset */ + { ECONNABORTED, osl_Socket_E_ConnAborted }, /* Software caused connection abort */ + { ECONNRESET, osl_Socket_E_ConnReset }, /* Connection reset by peer */ + { ENOBUFS, osl_Socket_E_NoBufferSpace }, /* No buffer space available */ + { EISCONN, osl_Socket_E_IsConnected }, /* Socket is already connected */ + { ENOTCONN, osl_Socket_E_NotConnected }, /* Socket is not connected */ + { ESHUTDOWN, osl_Socket_E_Shutdown }, /* Can't send after socket shutdown */ +#ifdef ETOOMANYREFS + { ETOOMANYREFS, osl_Socket_E_TooManyRefs }, /* Too many references: can't splice */ +#endif + { ETIMEDOUT, osl_Socket_E_TimedOut }, /* Connection timed out */ + { ECONNREFUSED, osl_Socket_E_ConnRefused }, /* Connection refused */ + { EHOSTDOWN, osl_Socket_E_HostDown }, /* Host is down */ + { EHOSTUNREACH, osl_Socket_E_HostUnreachable }, /* No route to host */ + { EWOULDBLOCK, osl_Socket_E_WouldBlock }, /* call would block on non-blocking socket */ + { EALREADY, osl_Socket_E_Already }, /* operation already in progress */ + { EINPROGRESS, osl_Socket_E_InProgress }, /* operation now in progress */ + { EAGAIN, osl_Socket_E_WouldBlock }, /* same as EWOULDBLOCK */ + { -1, osl_Socket_E_InvalidError } +}; + +static oslSocketError osl_SocketErrorFromNative(int nativeType) +{ + int i = 0; + + while ((SocketError[i].error != osl_Socket_E_InvalidError) && + (SocketError[i].errcode != nativeType)) i++; + + return SocketError[i].error; +} + +#define ERROR_FROM_NATIVE(y) osl_SocketErrorFromNative(y) + +static oslSocketAddr osl_psz_createInetSocketAddr ( + const char* pszDottedAddr, sal_Int32 Port); + +static oslHostAddr osl_psz_createHostAddr ( + const char *pszHostname, const oslSocketAddr Addr); + +static oslHostAddr osl_psz_createHostAddrByName ( + const char *pszHostname); + +static const char* osl_psz_getHostnameOfHostAddr ( + const oslHostAddr Addr); + +static oslSocketAddr osl_psz_resolveHostname ( + const char* pszHostname); + +static sal_Int32 osl_psz_getServicePort ( + const char* pszServicename, const char* pszProtocol); + +static void osl_psz_getLastSocketErrorDescription ( + oslSocket Socket, char* pBuffer, sal_uInt32 BufferSize); + +namespace { + +int convertToMs(TimeValue const * value) { + return value->Seconds * 1000 + value->Nanosec / 1000000; //TODO: overflow +} + +} + +static oslSocket createSocketImpl() +{ + oslSocket pSocket; + + pSocket = static_cast<oslSocket>(calloc(1, sizeof(struct oslSocketImpl))); + + pSocket->m_Socket = OSL_INVALID_SOCKET; + pSocket->m_nLastError = 0; + pSocket->m_nRefCount = 1; + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pSocket->m_bIsAccepting = false; +#endif + + return pSocket; +} + +static void destroySocketImpl(oslSocket Socket) +{ + if ( Socket != nullptr) + free(Socket); +} + +static oslSocketAddr createSocketAddr() +{ + oslSocketAddr pAddr = static_cast<oslSocketAddr>(rtl_allocateZeroMemory( sizeof( struct oslSocketAddrImpl ))); + return pAddr; +} + +static oslSocketAddr createSocketAddrWithFamily( + oslAddrFamily family, sal_Int32 port, sal_uInt32 nAddr ) +{ + oslSocketAddr pAddr; + + SAL_WARN_IF( family != osl_Socket_FamilyInet, "sal.osl", "creating socket for non-IP address family" ); + + pAddr = createSocketAddr(); + switch( family ) + { + case osl_Socket_FamilyInet: + { + struct sockaddr_in* pInetAddr= reinterpret_cast<sockaddr_in*>(&pAddr->m_sockaddr); + + pInetAddr->sin_family = FAMILY_TO_NATIVE(osl_Socket_FamilyInet); + pInetAddr->sin_addr.s_addr = nAddr; + pInetAddr->sin_port = static_cast<sal_uInt16>(port&0xffff); + break; + } + default: + pAddr->m_sockaddr.sa_family = FAMILY_TO_NATIVE(family); + } + return pAddr; +} + +static oslSocketAddr createSocketAddrFromSystem( struct sockaddr *pSystemSockAddr ) +{ + oslSocketAddr pAddr = createSocketAddr(); + memcpy( &(pAddr->m_sockaddr), pSystemSockAddr, sizeof( struct sockaddr ) ); + return pAddr; +} + +static void destroySocketAddr( oslSocketAddr addr ) +{ + free( addr ); +} + +oslSocketAddr SAL_CALL osl_createEmptySocketAddr(oslAddrFamily Family) +{ + oslSocketAddr pAddr = nullptr; + + /* is it an internet-Addr? */ + if (Family == osl_Socket_FamilyInet) + { + pAddr = createSocketAddrWithFamily(Family, 0 , htonl(INADDR_ANY) ); + } + else + { + pAddr = createSocketAddrWithFamily( Family , 0 , 0 ); + } + + return pAddr; +} + +oslSocketAddr SAL_CALL osl_copySocketAddr(oslSocketAddr Addr) +{ + oslSocketAddr pCopy = nullptr; + if (Addr) + { + pCopy = createSocketAddr(); + + if (pCopy) + memcpy(&(pCopy->m_sockaddr),&(Addr->m_sockaddr), sizeof(struct sockaddr)); + } + return pCopy; +} + +sal_Bool SAL_CALL osl_isEqualSocketAddr ( + oslSocketAddr Addr1, + oslSocketAddr Addr2) +{ + struct sockaddr* pAddr1 = nullptr; + struct sockaddr* pAddr2 = nullptr; + + assert(Addr1 && Addr2); + pAddr1 = &(Addr1->m_sockaddr); + pAddr2 = &(Addr2->m_sockaddr); + + if (pAddr1 == pAddr2) + { + return true; + } + + if (pAddr1->sa_family == pAddr2->sa_family) + { + switch (pAddr1->sa_family) + { + case AF_INET: + { + struct sockaddr_in* pInetAddr1= reinterpret_cast<sockaddr_in*>(pAddr1); + struct sockaddr_in* pInetAddr2= reinterpret_cast<sockaddr_in*>(pAddr2); + + if ((pInetAddr1->sin_family == pInetAddr2->sin_family) && + (pInetAddr1->sin_addr.s_addr == pInetAddr2->sin_addr.s_addr) && + (pInetAddr1->sin_port == pInetAddr2->sin_port)) + return true; + [[fallthrough]]; + } + + default: + { + return (memcmp(pAddr1, pAddr2, sizeof(struct sockaddr)) == 0); + } + } + } + + return false; +} + +oslSocketAddr SAL_CALL osl_createInetBroadcastAddr ( + rtl_uString *strDottedAddr, + sal_Int32 Port) +{ + sal_uInt32 nAddr = OSL_INADDR_NONE; + oslSocketAddr pAddr; + + if (strDottedAddr && strDottedAddr->length) + { + /* Dotted host address for limited broadcast */ + rtl_String *pDottedAddr = nullptr; + + rtl_uString2String ( + &pDottedAddr, strDottedAddr->buffer, strDottedAddr->length, + RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS); + + in_addr buf; + if (inet_pton (AF_INET, pDottedAddr->buffer, &buf) == 1) { + nAddr = buf.s_addr; + } + rtl_string_release (pDottedAddr); + } + + if (nAddr != OSL_INADDR_NONE) + { + /* Limited broadcast */ + nAddr = ntohl(nAddr); + if (IN_CLASSA(nAddr)) + { + nAddr &= IN_CLASSA_NET; + nAddr |= IN_CLASSA_HOST; + } + else if (IN_CLASSB(nAddr)) + { + nAddr &= IN_CLASSB_NET; + nAddr |= IN_CLASSB_HOST; + } + else if (IN_CLASSC(nAddr)) + { + nAddr &= IN_CLASSC_NET; + nAddr |= IN_CLASSC_HOST; + } + else + { + /* No broadcast in class D */ + return nullptr; + } + nAddr = htonl(nAddr); + } + + pAddr = createSocketAddrWithFamily( osl_Socket_FamilyInet, htons(Port), nAddr ); + return pAddr; +} + +oslSocketAddr SAL_CALL osl_createInetSocketAddr ( + rtl_uString *ustrDottedAddr, + sal_Int32 Port) +{ + rtl_String* strDottedAddr=nullptr; + oslSocketAddr Addr; + char* pszDottedAddr=nullptr; + + if ( ustrDottedAddr != nullptr ) + { + rtl_uString2String( &strDottedAddr, + rtl_uString_getStr(ustrDottedAddr), + rtl_uString_getLength(ustrDottedAddr), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS); + pszDottedAddr = rtl_string_getStr(strDottedAddr); + } + + Addr = pszDottedAddr ? osl_psz_createInetSocketAddr(pszDottedAddr, Port) : nullptr; + + if ( strDottedAddr != nullptr ) + { + rtl_string_release(strDottedAddr); + } + + return Addr; +} + +oslSocketAddr osl_psz_createInetSocketAddr ( + const char* pszDottedAddr, + sal_Int32 Port) +{ + oslSocketAddr pAddr = nullptr; + in_addr buf; + if(inet_pton(AF_INET, pszDottedAddr, &buf) == 1) + { + /* valid dotted addr */ + pAddr = createSocketAddrWithFamily( osl_Socket_FamilyInet, htons(Port) , buf.s_addr ); + } + return pAddr; +} + +oslSocketResult SAL_CALL osl_setAddrOfSocketAddr( oslSocketAddr pAddr, sal_Sequence *pByteSeq ) +{ + oslSocketResult res = osl_Socket_Error; + + SAL_WARN_IF( !pAddr, "sal.osl", "setting address of undefined socket address" ); + SAL_WARN_IF( !pByteSeq, "sal.osl", "setting undefined address for socket address" ); + + if( pAddr && pByteSeq ) + { + struct sockaddr_in * pSystemInetAddr; + + assert( pAddr->m_sockaddr.sa_family == FAMILY_TO_NATIVE( osl_Socket_FamilyInet ) ); + assert( pByteSeq->nElements == 4 ); + + pSystemInetAddr = reinterpret_cast<sockaddr_in *>(&pAddr->m_sockaddr); + memcpy( &(pSystemInetAddr->sin_addr) , pByteSeq->elements , 4 ); + res = osl_Socket_Ok; + } + return res; +} + +oslSocketResult SAL_CALL osl_getAddrOfSocketAddr( oslSocketAddr pAddr, sal_Sequence **ppByteSeq ) +{ + oslSocketResult res = osl_Socket_Error; + + SAL_WARN_IF( !pAddr, "sal.osl", "getting address of undefined socket address" ); + SAL_WARN_IF( !ppByteSeq, "sal.osl", "getting address to undefined address pointer" ); + + if( pAddr && ppByteSeq ) + { + struct sockaddr_in * pSystemInetAddr = reinterpret_cast<sockaddr_in *>(&pAddr->m_sockaddr); + rtl_byte_sequence_constructFromArray( ppByteSeq, reinterpret_cast<sal_Int8 *>(&pSystemInetAddr->sin_addr), 4); + res = osl_Socket_Ok; + } + return res; +} + +/** try to figure out a full-qualified hostname, by adding the current domain + as given by the domainname program to the given hostname. + This function MUST NOT call gethostbyname since pHostName already points + to data returned by gethostname and would be garbled: use gethostname_r + instead! + */ + +namespace { + +struct oslAddrInfo +{ + addrinfo* pAddrInfoList = nullptr; + int nError; + + oslAddrInfo(const char* name, bool isInet = false) + { + addrinfo aHints; + memset(&aHints, 0, sizeof(addrinfo)); + if (isInet) + aHints.ai_family = AF_INET; + aHints.ai_flags = AI_CANONNAME; + + nError = getaddrinfo(name, nullptr, &aHints, &pAddrInfoList); + } + + ~oslAddrInfo() + { + if (nError == 0) + freeaddrinfo(pAddrInfoList); + } + + const char* getHostName() const + { + assert(pAddrInfoList); + return pAddrInfoList->ai_canonname; + } +}; + +} +static bool isFullQualifiedDomainName (const char *pHostName) +{ + /* a FQDN (aka 'hostname.domain.top_level_domain' ) + * is a name which contains a dot '.' in it ( would + * match as well for 'hostname.' but is good enough + * for now )*/ + return strchr( pHostName, int('.') ) != nullptr; +} + +static char* getFullQualifiedDomainName (const char *pHostName) +{ + char *pFullQualifiedName = nullptr; + + if (isFullQualifiedDomainName(pHostName)) + { + pFullQualifiedName = strdup(pHostName); + } + else + { + oslAddrInfo aAddrInfo(pHostName); + if (aAddrInfo.nError == 0) + pFullQualifiedName = strdup(aAddrInfo.getHostName()); + } + + return pFullQualifiedName; +} + +struct oslHostAddrImpl +{ + char *pHostName; + oslSocketAddr pSockAddr; +}; + +static oslHostAddr addrinfoToHostAddr (const addrinfo* ai) +{ + if (!ai || !ai->ai_canonname || !ai->ai_addr) + return nullptr; + + char* cn = getFullQualifiedDomainName(ai->ai_canonname); + SAL_WARN_IF( !cn, "sal.osl", "couldn't get full qualified domain name" ); + if (cn == nullptr) + return nullptr; + + oslSocketAddr pSockAddr = createSocketAddr(); + SAL_WARN_IF( !pSockAddr, "sal.osl", "insufficient memory" ); + if (pSockAddr == nullptr) + { + free(cn); + return nullptr; + } + + if (ai->ai_family == FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + { + memcpy ( + &(pSockAddr->m_sockaddr), + ai->ai_addr, + ai->ai_addrlen); + } + else + { + /* unknown address family */ + /* future extensions for new families might be implemented here */ + + SAL_WARN( "sal.osl", "unknown address family" ); + + destroySocketAddr( pSockAddr ); + free (cn); + return nullptr; + } + + oslHostAddr pAddr = static_cast<oslHostAddr>(malloc(sizeof(struct oslHostAddrImpl))); + SAL_WARN_IF( !pAddr, "sal.osl", "allocation error" ); + if (pAddr == nullptr) + { + destroySocketAddr( pSockAddr ); + free (cn); + return nullptr; + } + + pAddr->pHostName= cn; + pAddr->pSockAddr= pSockAddr; + + return pAddr; +} + +oslHostAddr SAL_CALL osl_createHostAddr ( + rtl_uString *ustrHostname, + const oslSocketAddr Addr) +{ + oslHostAddr HostAddr; + rtl_String* strHostname=nullptr; + char* pszHostName=nullptr; + + if ( ustrHostname != nullptr ) + { + rtl_uString2String( &strHostname, + rtl_uString_getStr(ustrHostname), + rtl_uString_getLength(ustrHostname), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS ); + pszHostName = rtl_string_getStr(strHostname); + } + + HostAddr = osl_psz_createHostAddr(pszHostName,Addr); + + if ( strHostname != nullptr ) + { + rtl_string_release(strHostname); + } + + return HostAddr; +} + +oslHostAddr osl_psz_createHostAddr ( + const char *pszHostname, + const oslSocketAddr pAddr) +{ + oslHostAddr pHostAddr; + char *cn; + + SAL_WARN_IF( !pszHostname, "sal.osl", "undefined hostname" ); + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + if ((pszHostname == nullptr) || (pAddr == nullptr)) + return nullptr; + + cn = strdup(pszHostname); + SAL_WARN_IF( !cn, "sal.osl", "insufficient memory" ); + if (cn == nullptr) + return nullptr; + + pHostAddr= static_cast<oslHostAddr>(malloc(sizeof(struct oslHostAddrImpl))); + SAL_WARN_IF( !pHostAddr, "sal.osl", "allocation error" ); + if (pHostAddr == nullptr) + { + free (cn); + return nullptr; + } + + pHostAddr->pHostName= cn; + pHostAddr->pSockAddr= osl_copySocketAddr( pAddr ); + + return pHostAddr; +} + +oslHostAddr SAL_CALL osl_createHostAddrByName(rtl_uString *ustrHostname) +{ + oslHostAddr HostAddr; + rtl_String* strHostname=nullptr; + char* pszHostName=nullptr; + + if ( ustrHostname != nullptr ) + { + rtl_uString2String( &strHostname, + rtl_uString_getStr(ustrHostname), + rtl_uString_getLength(ustrHostname), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS ); + pszHostName=rtl_string_getStr(strHostname); + } + + HostAddr = osl_psz_createHostAddrByName(pszHostName); + + if ( strHostname != nullptr ) + { + rtl_string_release(strHostname); + } + + return HostAddr; +} + +oslHostAddr osl_psz_createHostAddrByName (const char *pszHostname) +{ + oslAddrInfo aAddrInfo(pszHostname, /* isInet */ true); + + return addrinfoToHostAddr (aAddrInfo.pAddrInfoList); +} + +oslHostAddr SAL_CALL osl_createHostAddrByAddr (const oslSocketAddr pAddr) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if (pAddr == nullptr) + return nullptr; + + if (pAddr->m_sockaddr.sa_family == FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + { + const struct sockaddr_in *sin = reinterpret_cast<sockaddr_in *>(&pAddr->m_sockaddr); + if (sin->sin_addr.s_addr == htonl(INADDR_ANY)) + return nullptr; + + char host[MAX_HOSTBUFFER_SIZE]; + int res = getnameinfo(&pAddr->m_sockaddr, sizeof(struct sockaddr_in), + host, sizeof(host), nullptr, 0, NI_NAMEREQD); + if (res != 0) + return nullptr; + + char *cn = getFullQualifiedDomainName(host); + SAL_WARN_IF( !cn, "sal.osl", "couldn't get full qualified domain name" ); + if (cn == nullptr) + return nullptr; + + oslSocketAddr pSockAddr = createSocketAddr(); + SAL_WARN_IF( !pSockAddr, "sal.osl", "insufficient memory" ); + if (pSockAddr == nullptr) + { + free(cn); + return nullptr; + } + + memcpy(&pSockAddr->m_sockaddr, &pAddr->m_sockaddr, sizeof(pAddr->m_sockaddr)); + + oslHostAddr pHostAddr = static_cast<oslHostAddr>(malloc(sizeof(struct oslHostAddrImpl))); + SAL_WARN_IF( !pAddr, "sal.osl", "allocation error" ); + if (pHostAddr == nullptr) + { + destroySocketAddr(pSockAddr); + free(cn); + return nullptr; + } + + pHostAddr->pHostName = cn; + pHostAddr->pSockAddr = pSockAddr; + + return pHostAddr; + } + + return nullptr; +} + +oslHostAddr SAL_CALL osl_copyHostAddr (const oslHostAddr pAddr) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if (pAddr) + return osl_psz_createHostAddr (pAddr->pHostName, pAddr->pSockAddr); + return nullptr; +} + +void SAL_CALL osl_getHostnameOfHostAddr ( + const oslHostAddr Addr, + rtl_uString **ustrHostname) +{ + const char* pHostname = osl_psz_getHostnameOfHostAddr(Addr); + + rtl_uString_newFromAscii (ustrHostname, pHostname); +} + +const char* osl_psz_getHostnameOfHostAddr (const oslHostAddr pAddr) +{ + if (pAddr) + return pAddr->pHostName; + return nullptr; +} + +oslSocketAddr SAL_CALL osl_getSocketAddrOfHostAddr (const oslHostAddr pAddr) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if (pAddr) + return pAddr->pSockAddr; + return nullptr; +} + +void SAL_CALL osl_destroyHostAddr (oslHostAddr pAddr) +{ + if (pAddr) + { + if (pAddr->pHostName) + free (pAddr->pHostName); + if (pAddr->pSockAddr) + osl_destroySocketAddr (pAddr->pSockAddr); + free (pAddr); + } +} + +namespace +{ +oslSocketResult lcl_getLocalHostname(rtl_uString **ustrLocalHostname, bool bUseFQDN) +{ + static auto const init = [bUseFQDN]() -> std::pair<oslSocketResult, OUString> { + char LocalHostname[256] = ""; + +#ifdef SYSV + struct utsname uts; + + if (uname(&uts) < 0) + return {osl_Socket_Error, OUString()}; + + if ((strlen(uts.nodename) + 1) > nBufLen) + return {osl_Socket_Error, OUString()}; + + strncpy(LocalHostname, uts.nodename, sizeof( LocalHostname )); +#else /* BSD compatible */ + if (gethostname(LocalHostname, sizeof(LocalHostname)-1) != 0) + return {osl_Socket_Error, OUString()}; +#endif /* SYSV */ + LocalHostname[sizeof(LocalHostname)-1] = 0; + + /* check if we have an FQDN */ + if (bUseFQDN && strchr(LocalHostname, '.') == nullptr) + { + oslHostAddr Addr; + + /* no, determine it via dns */ + Addr = osl_psz_createHostAddrByName(LocalHostname); + + const char *pStr; + if ((pStr = osl_psz_getHostnameOfHostAddr(Addr)) != nullptr) + { + strncpy(LocalHostname, pStr, sizeof( LocalHostname )); + LocalHostname[sizeof(LocalHostname)-1] = 0; + } + osl_destroyHostAddr(Addr); + } + + if (LocalHostname[0] != '\0') + { + return {osl_Socket_Ok, OUString::createFromAscii(LocalHostname)}; + } + + return {osl_Socket_Error, OUString()}; + }(); + + rtl_uString_assign(ustrLocalHostname,init.second.pData); + + return init.first; +} +} + +oslSocketResult SAL_CALL osl_getLocalHostname(rtl_uString **ustrLocalHostname) +{ + return lcl_getLocalHostname(ustrLocalHostname, false); +} + +oslSocketResult osl_getLocalHostnameFQDN(rtl_uString **ustrLocalHostname) +{ + return lcl_getLocalHostname(ustrLocalHostname, true); +} + +oslSocketAddr SAL_CALL osl_resolveHostname(rtl_uString *ustrHostname) +{ + oslSocketAddr Addr; + rtl_String* strHostname=nullptr; + char* pszHostName=nullptr; + + if ( ustrHostname != nullptr ) + { + rtl_uString2String( &strHostname, + rtl_uString_getStr(ustrHostname), + rtl_uString_getLength(ustrHostname), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS ); + pszHostName = rtl_string_getStr(strHostname); + } + + Addr = osl_psz_resolveHostname(pszHostName); + + if ( strHostname != nullptr ) + { + rtl_string_release(strHostname); + } + + return Addr; +} + +oslSocketAddr osl_psz_resolveHostname(const char* pszHostname) +{ + struct oslHostAddrImpl *pAddr = osl_psz_createHostAddrByName(pszHostname); + + if (pAddr) + { + oslSocketAddr SockAddr = osl_copySocketAddr(pAddr->pSockAddr); + + osl_destroyHostAddr(pAddr); + + return SockAddr; + } + + return nullptr; +} + +sal_Int32 SAL_CALL osl_getServicePort(rtl_uString *ustrServicename, rtl_uString *ustrProtocol) +{ + sal_Int32 nPort; + rtl_String* strServicename=nullptr; + rtl_String* strProtocol=nullptr; + char* pszServiceName=nullptr; + char* pszProtocol=nullptr; + + if ( ustrServicename != nullptr ) + { + rtl_uString2String( &strServicename, + rtl_uString_getStr(ustrServicename), + rtl_uString_getLength(ustrServicename), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS ); + pszServiceName = rtl_string_getStr(strServicename); + } + + if ( ustrProtocol != nullptr ) + { + rtl_uString2String( &strProtocol, + rtl_uString_getStr(ustrProtocol), + rtl_uString_getLength(ustrProtocol), + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS ); + pszProtocol = rtl_string_getStr(strProtocol); + } + + nPort = osl_psz_getServicePort(pszServiceName,pszProtocol); + + if ( strServicename != nullptr ) + { + rtl_string_release(strServicename); + } + + if ( strProtocol != nullptr ) + { + rtl_string_release(strProtocol); + } + + return nPort; +} + +sal_Int32 osl_psz_getServicePort(const char* pszServicename, + const char* pszProtocol) +{ + struct servent* ps; + + ps= getservbyname(pszServicename, pszProtocol); + + if (ps != nullptr) + return ntohs(ps->s_port); + + return OSL_INVALID_PORT; +} + +void SAL_CALL osl_destroySocketAddr(oslSocketAddr pAddr) +{ + destroySocketAddr( pAddr ); +} + +oslAddrFamily SAL_CALL osl_getFamilyOfSocketAddr(oslSocketAddr pAddr) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if (pAddr) + return FAMILY_FROM_NATIVE(pAddr->m_sockaddr.sa_family); + return osl_Socket_FamilyInvalid; +} + +sal_Int32 SAL_CALL osl_getInetPortOfSocketAddr(oslSocketAddr pAddr) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if( pAddr ) + { + struct sockaddr_in* pSystemInetAddr= reinterpret_cast<sockaddr_in*>(&pAddr->m_sockaddr); + + if ( pSystemInetAddr->sin_family == FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + return ntohs(pSystemInetAddr->sin_port); + } + return OSL_INVALID_PORT; +} + +sal_Bool SAL_CALL osl_setInetPortOfSocketAddr(oslSocketAddr pAddr, sal_Int32 Port) +{ + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + + if( pAddr ) + { + struct sockaddr_in* pSystemInetAddr= reinterpret_cast<sockaddr_in*>(&pAddr->m_sockaddr); + if ( pSystemInetAddr->sin_family == FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + { + pSystemInetAddr->sin_port= htons(static_cast<short>(Port)); + return true; + } + } + + /* this is not a inet-addr => can't set port */ + return false; +} + +oslSocketResult SAL_CALL osl_getHostnameOfSocketAddr(oslSocketAddr Addr, rtl_uString **ustrHostname) +{ + oslHostAddr pHostAddr= osl_createHostAddrByAddr(Addr); + + if (!pHostAddr) + { + return osl_Socket_Error; + } + + rtl_uString_newFromAscii(ustrHostname,pHostAddr->pHostName); + + osl_destroyHostAddr(pHostAddr); + + return osl_Socket_Ok; +} + +oslSocketResult SAL_CALL osl_getDottedInetAddrOfSocketAddr(oslSocketAddr Addr, rtl_uString **ustrDottedInetAddr) +{ + if( !Addr ) + { + return osl_Socket_Error; + } + + struct sockaddr_in* pSystemInetAddr = reinterpret_cast<sockaddr_in *>(&Addr->m_sockaddr); + + if (pSystemInetAddr->sin_family != FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + { + return osl_Socket_Error; + } + + char buf[INET_ADDRSTRLEN]; + auto const text = inet_ntop(AF_INET, &pSystemInetAddr->sin_addr, buf, INET_ADDRSTRLEN); + assert(text != nullptr); + rtl_uString_newFromAscii(ustrDottedInetAddr,text); + + return osl_Socket_Ok; + +} + +oslSocket SAL_CALL osl_createSocket( + oslAddrFamily Family, + oslSocketType Type, + oslProtocol Protocol) +{ + oslSocket pSocket; + + /* alloc memory */ + pSocket= createSocketImpl(); + + /* create socket */ + pSocket->m_Socket= socket(FAMILY_TO_NATIVE(Family), + TYPE_TO_NATIVE(Type), + PROTOCOL_TO_NATIVE(Protocol)); + + /* creation failed => free memory */ + if(pSocket->m_Socket == OSL_INVALID_SOCKET) + { + int nErrno = errno; + SAL_WARN( "sal.osl", "socket creation failed: " << UnixErrnoString(nErrno) ); + + destroySocketImpl(pSocket); + pSocket= nullptr; + } + else + { + sal_Int32 nFlags=0; + /* set close-on-exec flag */ + if ((nFlags = fcntl(pSocket->m_Socket, F_GETFD, 0)) != -1) + { + nFlags |= FD_CLOEXEC; + if (fcntl(pSocket->m_Socket, F_SETFD, nFlags) == -1) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "failed changing socket flags: " << UnixErrnoString(nErrno) ); + } + } + else + { + pSocket->m_nLastError=errno; + } + } + + return pSocket; +} + +void SAL_CALL osl_acquireSocket(oslSocket pSocket) +{ + osl_atomic_increment(&(pSocket->m_nRefCount)); +} + +void SAL_CALL osl_releaseSocket(oslSocket pSocket) +{ + if (pSocket && osl_atomic_decrement(&(pSocket->m_nRefCount)) == 0) + { +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + if (pSocket->m_bIsAccepting) + { + SAL_WARN( "sal.osl", "attempt to destroy socket while accepting" ); + return; + } +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + osl_closeSocket(pSocket); + destroySocketImpl(pSocket); + } +} + +void SAL_CALL osl_closeSocket(oslSocket pSocket) +{ + /* socket already invalid */ + if (!pSocket) + return; + + pSocket->m_nLastError=0; + sal_Int32 nFD = pSocket->m_Socket; + + if (nFD == OSL_INVALID_SOCKET) + return; + + pSocket->m_Socket = OSL_INVALID_SOCKET; + + sal_Int32 nRet=0; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pSocket->m_bIsInShutdown = true; + + if (pSocket->m_bIsAccepting) + { + union { + struct sockaddr aSockAddr; + struct sockaddr_in aSockAddrIn; + } s; + socklen_t nSockLen = sizeof(s.aSockAddr); + + nRet = getsockname(nFD, &s.aSockAddr, &nSockLen); + if (nRet < 0) + { + int nErrno = errno; + SAL_WARN( "sal.osl", "getsockname call failed: " << UnixErrnoString(nErrno) ); + } + + if (s.aSockAddr.sa_family == AF_INET) + { + if (s.aSockAddrIn.sin_addr.s_addr == htonl(INADDR_ANY)) + { + s.aSockAddrIn.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + int nConnFD = socket(AF_INET, SOCK_STREAM, 0); + if (nConnFD < 0) + { + int nErrno = errno; + SAL_WARN( "sal.osl", "socket call failed: " << UnixErrnoString(nErrno) ); + } + else + { + nRet = connect(nConnFD, &s.aSockAddr, sizeof(s.aSockAddr)); + if (nRet < 0) + { + int nErrno = errno; + SAL_WARN( "sal.osl", "connect call failed: " << UnixErrnoString(nErrno) ); + } + close(nConnFD); + } + } + pSocket->m_bIsAccepting = false; + } +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + + nRet=close(nFD); + if (nRet != 0) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "closeSocket close failed: " << UnixErrnoString(nErrno) ); + } + + pSocket->m_Socket = OSL_INVALID_SOCKET; +} + +/* Note from function creator: I rely on the fact that oslSocketAddr and struct sockaddr + are the same! I don't like it very much but see no other easy way to conceal + the struct sockaddr from the eyes of the user. */ +oslSocketAddr SAL_CALL osl_getLocalAddrOfSocket(oslSocket pSocket) +{ + socklen_t AddrLen; + struct sockaddr Addr; + oslSocketAddr pAddr; + + if (pSocket == nullptr) /* ENOTSOCK */ + return nullptr; + + AddrLen= sizeof(struct sockaddr); + + if (getsockname(pSocket->m_Socket, &Addr, &AddrLen) == OSL_SOCKET_ERROR) + return nullptr; + + pAddr = createSocketAddrFromSystem( &Addr ); + return pAddr; +} + +oslSocketAddr SAL_CALL osl_getPeerAddrOfSocket(oslSocket pSocket) +{ + socklen_t AddrLen; + struct sockaddr Addr; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return nullptr; + } + + pSocket->m_nLastError=0; + AddrLen= sizeof(struct sockaddr); + + if(getpeername(pSocket->m_Socket, &Addr, &AddrLen) == OSL_SOCKET_ERROR) + { + pSocket->m_nLastError=errno; + return nullptr; + } + return createSocketAddrFromSystem( &Addr ); +} + +sal_Bool SAL_CALL osl_bindAddrToSocket(oslSocket pSocket, + oslSocketAddr pAddr) +{ + int nRet; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + SAL_WARN_IF( !pAddr, "sal.osl", "undefined address" ); + if ( pSocket == nullptr || pAddr == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + nRet = bind(pSocket->m_Socket, &(pAddr->m_sockaddr), sizeof(struct sockaddr)); + + if ( nRet == OSL_SOCKET_ERROR) + { + pSocket->m_nLastError=errno; + return false; + } + + return true; +} + +sal_Bool SAL_CALL osl_listenOnSocket(oslSocket pSocket, + sal_Int32 MaxPendingConnections) +{ + int nRet; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + nRet = listen(pSocket->m_Socket, + MaxPendingConnections == -1 ? + SOMAXCONN : + MaxPendingConnections); + if ( nRet == OSL_SOCKET_ERROR) + { + pSocket->m_nLastError=errno; + return false; + } + + return true; +} + +oslSocketResult SAL_CALL osl_connectSocketTo(oslSocket pSocket, + oslSocketAddr pAddr, + const TimeValue* pTimeout) +{ + int ReadyHandles; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + + if ( pSocket == nullptr ) + { + return osl_Socket_Error; + } + + pSocket->m_nLastError=0; + + if (osl_isNonBlockingMode(pSocket)) + { + if (connect(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) != OSL_SOCKET_ERROR) + return osl_Socket_Ok; + + if (errno == EWOULDBLOCK || errno == EINPROGRESS) + { + pSocket->m_nLastError=EINPROGRESS; + return osl_Socket_InProgress; + } + + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "connection failed: " << UnixErrnoString(nErrno) ); + return osl_Socket_Error; + } + + /* set socket temporarily to non-blocking */ + if( !osl_enableNonBlockingMode(pSocket, true) ) + SAL_WARN( "sal.osl", "failed to enable non-blocking mode" ); + + /* initiate connect */ + if(connect(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) != OSL_SOCKET_ERROR) + { + /* immediate connection */ + osl_enableNonBlockingMode(pSocket, false); + + return osl_Socket_Ok; + } + + /* really an error or just delayed? */ + if (errno != EINPROGRESS) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "connection failed: " << UnixErrnoString(nErrno) ); + + osl_enableNonBlockingMode(pSocket, false); + return osl_Socket_Error; + } + + /* prepare poll set for socket */ + pollfd Set = {pSocket->m_Socket, POLLPRI | POLLOUT, 0}; + + /* poll */ + ReadyHandles= poll(&Set, 1, + pTimeout ? convertToMs(pTimeout) : -1); + + if (ReadyHandles > 0) /* connected */ + { + if ( (Set.revents & POLLOUT) != 0 ) + { + int nErrorCode = 0; + socklen_t nErrorSize = sizeof( nErrorCode ); + + int nSockOpt; + + nSockOpt = getsockopt ( pSocket->m_Socket, SOL_SOCKET, SO_ERROR, + &nErrorCode, &nErrorSize ); + if ( (nSockOpt == 0) && (nErrorCode == 0)) + { + osl_enableNonBlockingMode(pSocket, false); + return osl_Socket_Ok; + } + else + { + pSocket->m_nLastError = (nSockOpt == 0) ? nErrorCode : errno; + return osl_Socket_Error; + } + } + else + { + return osl_Socket_Error; + } + } + else if (ReadyHandles < 0) /* error */ + { + if (errno == EBADF) /* most probably interrupted by close() */ + { + /* do not access pSockImpl because it is about to be or */ + /* already destroyed */ + return osl_Socket_Interrupted; + } + pSocket->m_nLastError=errno; + return osl_Socket_Error; + } + else /* timeout */ + { + pSocket->m_nLastError=errno; + return osl_Socket_TimedOut; + } +} + +oslSocket SAL_CALL osl_acceptConnectionOnSocket(oslSocket pSocket, + oslSocketAddr* ppAddr) +{ + struct sockaddr Addr; + int Connection, Flags; + oslSocket pConnectionSockImpl; + + socklen_t AddrLen = sizeof(struct sockaddr); + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return nullptr; + } + + pSocket->m_nLastError=0; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pSocket->m_bIsAccepting = true; +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + + if( ppAddr && *ppAddr ) + { + osl_destroySocketAddr( *ppAddr ); + *ppAddr = nullptr; + } + + /* prevent Linux EINTR behaviour */ + do + { + Connection = accept(pSocket->m_Socket, &Addr, &AddrLen); + } while (Connection == -1 && errno == EINTR); + + /* accept failed? */ + if( Connection == OSL_SOCKET_ERROR ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "accept connection failed: " << UnixErrnoString(nErrno) ); + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pSocket->m_bIsAccepting = false; +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + return nullptr; + } + + assert(AddrLen == sizeof(struct sockaddr)); + +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + if ( pSocket->m_bIsInShutdown ) + { + close(Connection); + SAL_WARN( "sal.osl", "close while accept" ); + return nullptr; + } +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + + if(ppAddr) + { + *ppAddr= createSocketAddrFromSystem(&Addr); + } + + /* alloc memory */ + pConnectionSockImpl= createSocketImpl(); + + /* set close-on-exec flag */ + if ((Flags = fcntl(Connection, F_GETFD, 0)) != -1) + { + Flags |= FD_CLOEXEC; + if (fcntl(Connection, F_SETFD, Flags) == -1) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "fcntl failed: " << UnixErrnoString(nErrno) ); + } + + } + + pConnectionSockImpl->m_Socket = Connection; + pConnectionSockImpl->m_nLastError = 0; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + pConnectionSockImpl->m_bIsAccepting = false; + + pSocket->m_bIsAccepting = false; +#endif /* CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT */ + return pConnectionSockImpl; +} + +sal_Int32 SAL_CALL osl_receiveSocket(oslSocket pSocket, + void* pBuffer, + sal_uInt32 BytesToRead, + oslSocketMsgFlag Flag) +{ + int nRead; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return -1; + } + + pSocket->m_nLastError=0; + + do + { + nRead = recv(pSocket->m_Socket, + pBuffer, + BytesToRead, + MSG_FLAG_TO_NATIVE(Flag)); + } while ( nRead < 0 && errno == EINTR ); + + if ( nRead < 0 ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "receive socket [" << nRead << "] failed: " << UnixErrnoString(nErrno) ); + } + else if ( nRead == 0 ) + { + SAL_WARN( "sal.osl", "receive socket [" << nRead << "] failed: EOL" ); + } + + return nRead; +} + +sal_Int32 SAL_CALL osl_receiveFromSocket(oslSocket pSocket, + oslSocketAddr pSenderAddr, + void* pBuffer, + sal_uInt32 BufferSize, + oslSocketMsgFlag Flag) +{ + int nRead; + struct sockaddr *pSystemSockAddr = nullptr; + socklen_t AddrLen = 0; + if( pSenderAddr ) + { + AddrLen = sizeof( struct sockaddr ); + pSystemSockAddr = &(pSenderAddr->m_sockaddr); + } + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return -1; + } + + pSocket->m_nLastError=0; + + nRead = recvfrom(pSocket->m_Socket, + pBuffer, + BufferSize, + MSG_FLAG_TO_NATIVE(Flag), + pSystemSockAddr, + &AddrLen); + + if ( nRead < 0 ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "receive socket [" << nRead << "] failed: " << UnixErrnoString(nErrno) ); + } + else if ( nRead == 0 ) + { + SAL_WARN( "sal.osl", "receive socket [" << nRead << "] failed: EOL" ); + } + + return nRead; +} + +sal_Int32 SAL_CALL osl_sendSocket(oslSocket pSocket, + const void* pBuffer, + sal_uInt32 BytesToSend, + oslSocketMsgFlag Flag) +{ + int nWritten; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return -1; + } + + pSocket->m_nLastError=0; + + do + { + nWritten = send(pSocket->m_Socket, + pBuffer, + BytesToSend, + MSG_FLAG_TO_NATIVE(Flag)); + } while ( nWritten < 0 && errno == EINTR ); + + if ( nWritten < 0 ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "send socket [" << nWritten << "] failed: " << UnixErrnoString(nErrno) ); + } + else if ( nWritten == 0 ) + { + SAL_WARN( "sal.osl", "send socket [" << nWritten << "] failed: EOL" ); + } + + return nWritten; +} + +sal_Int32 SAL_CALL osl_sendToSocket(oslSocket pSocket, + oslSocketAddr ReceiverAddr, + const void* pBuffer, + sal_uInt32 BytesToSend, + oslSocketMsgFlag Flag) +{ + int nWritten; + + struct sockaddr *pSystemSockAddr = nullptr; + int AddrLen = 0; + if( ReceiverAddr ) + { + pSystemSockAddr = &(ReceiverAddr->m_sockaddr); + AddrLen = sizeof( struct sockaddr ); + } + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return -1; + } + + pSocket->m_nLastError=0; + + /* ReceiverAddr might be 0 when used on a connected socket. */ + /* Then sendto should behave like send. */ + + nWritten = sendto(pSocket->m_Socket, + pBuffer, + BytesToSend, + MSG_FLAG_TO_NATIVE(Flag), + pSystemSockAddr, + AddrLen); + + if ( nWritten < 0 ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "send socket [" << nWritten << "] failed: " << UnixErrnoString(nErrno) ); + } + else if ( nWritten == 0 ) + { + SAL_WARN( "sal.osl", "send socket [" << nWritten << "] failed: EOL" ); + } + + return nWritten; +} + +sal_Int32 SAL_CALL osl_readSocket ( + oslSocket pSocket, void *pBuffer, sal_Int32 n ) +{ + sal_uInt8 * Ptr = static_cast<sal_uInt8 *>(pBuffer); + sal_uInt32 BytesRead= 0; + sal_uInt32 BytesToRead= n; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + + /* loop until all desired bytes were read or an error occurred */ + while (BytesToRead > 0) + { + sal_Int32 RetVal; + RetVal= osl_receiveSocket(pSocket, + Ptr, + BytesToRead, + osl_Socket_MsgNormal); + + /* error occurred? */ + if(RetVal <= 0) + { + break; + } + + BytesToRead -= RetVal; + BytesRead += RetVal; + Ptr += RetVal; + } + + return BytesRead; +} + +sal_Int32 SAL_CALL osl_writeSocket( + oslSocket pSocket, const void *pBuffer, sal_Int32 n ) +{ + /* loop until all desired bytes were send or an error occurred */ + sal_uInt32 BytesSend= 0; + sal_uInt32 BytesToSend= n; + sal_uInt8 const *Ptr = static_cast<sal_uInt8 const *>(pBuffer); + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + + while (BytesToSend > 0) + { + sal_Int32 RetVal; + + RetVal= osl_sendSocket( pSocket,Ptr,BytesToSend,osl_Socket_MsgNormal); + + /* error occurred? */ + if(RetVal <= 0) + { + break; + } + + BytesToSend -= RetVal; + BytesSend += RetVal; + Ptr += RetVal; + + } + return BytesSend; +} + +static bool socket_poll ( + oslSocket pSocket, + const TimeValue* pTimeout, + short nEvent) +{ + struct pollfd fds; + int timeout; + int result; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if (pSocket == nullptr) + return false; /* EINVAL */ + + pSocket->m_nLastError = 0; + + fds.fd = pSocket->m_Socket; + fds.events = nEvent; + fds.revents = 0; + + timeout = -1; + if (pTimeout) + { + timeout = convertToMs(pTimeout); + } + + result = poll (&fds, 1, timeout); + if (result < 0) + { + pSocket->m_nLastError = errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "poll failed: " << UnixErrnoString(nErrno) ); + return false; + } + if (result == 0) + { + /* Timeout */ + return false; + } + + return ((fds.revents & nEvent) == nEvent); +} + +sal_Bool SAL_CALL osl_isReceiveReady ( + oslSocket pSocket, const TimeValue* pTimeout) +{ + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if (pSocket == nullptr) + { + /* ENOTSOCK */ + return false; + } + + return socket_poll (pSocket, pTimeout, POLLIN); +} + +sal_Bool SAL_CALL osl_isSendReady ( + oslSocket pSocket, const TimeValue* pTimeout) +{ + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if (pSocket == nullptr) + { + /* ENOTSOCK */ + return false; + } + + return socket_poll (pSocket, pTimeout, POLLOUT); +} + +sal_Bool SAL_CALL osl_isExceptionPending ( + oslSocket pSocket, const TimeValue* pTimeout) +{ + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if (pSocket == nullptr) + { + /* ENOTSOCK */ + return false; + } + + return socket_poll (pSocket, pTimeout, POLLPRI); +} + +sal_Bool SAL_CALL osl_shutdownSocket(oslSocket pSocket, + oslSocketDirection Direction) +{ + int nRet; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + nRet=shutdown(pSocket->m_Socket, DIRECTION_TO_NATIVE(Direction)); + if (nRet != 0 ) + { + pSocket->m_nLastError=errno; + int nErrno = errno; + SAL_WARN( "sal.osl", "shutdown failed: " << UnixErrnoString(nErrno) ); + } + return (nRet==0); +} + +sal_Int32 SAL_CALL osl_getSocketOption(oslSocket pSocket, + oslSocketOptionLevel Level, + oslSocketOption Option, + void* pBuffer, + sal_uInt32 BufferLen) +{ + socklen_t nOptLen = static_cast<socklen_t>(BufferLen); + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return -1; + } + + pSocket->m_nLastError=0; + + if(getsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(Level), + OPTION_TO_NATIVE(Option), + pBuffer, + &nOptLen) == -1) + { + pSocket->m_nLastError=errno; + return -1; + } + + return nOptLen; +} + +sal_Bool SAL_CALL osl_setSocketOption(oslSocket pSocket, + oslSocketOptionLevel Level, + oslSocketOption Option, + void* pBuffer, + sal_uInt32 BufferLen) +{ + int nRet; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + nRet = setsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(Level), + OPTION_TO_NATIVE(Option), + pBuffer, + BufferLen); + + if ( nRet < 0 ) + { + pSocket->m_nLastError=errno; + return false; + } + + return true; +} + +sal_Bool SAL_CALL osl_enableNonBlockingMode(oslSocket pSocket, + sal_Bool On) +{ + int flags; + int nRet; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + flags = fcntl(pSocket->m_Socket, F_GETFL, 0); + + if (On) + flags |= O_NONBLOCK; + else + flags &= ~(O_NONBLOCK); + + nRet = fcntl(pSocket->m_Socket, F_SETFL, flags); + + if ( nRet < 0 ) + { + pSocket->m_nLastError=errno; + return false; + } + + return true; +} + +sal_Bool SAL_CALL osl_isNonBlockingMode(oslSocket pSocket) +{ + int flags; + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return false; + } + + pSocket->m_nLastError=0; + + flags = fcntl(pSocket->m_Socket, F_GETFL, 0); + + if (flags == -1 || !(flags & O_NONBLOCK)) + return false; + + return true; +} + +oslSocketType SAL_CALL osl_getSocketType(oslSocket pSocket) +{ + int Type=0; + socklen_t TypeSize= sizeof(Type); + + SAL_WARN_IF( !pSocket, "sal.osl", "undefined socket" ); + if ( pSocket == nullptr ) + { + return osl_Socket_TypeInvalid; + } + + pSocket->m_nLastError=0; + + if(getsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(osl_Socket_LevelSocket), + OPTION_TO_NATIVE(osl_Socket_OptionType), + &Type, + &TypeSize) == -1) + { + /* error */ + pSocket->m_nLastError=errno; + return osl_Socket_TypeInvalid; + } + + return TYPE_FROM_NATIVE(Type); + +} + +void SAL_CALL osl_getLastSocketErrorDescription(oslSocket Socket, rtl_uString **ustrError) +{ + char pszError[1024]; + + pszError[0] = '\0'; + + osl_psz_getLastSocketErrorDescription(Socket,pszError,sizeof(pszError)); + + rtl_uString_newFromAscii(ustrError,pszError); +} + +void osl_psz_getLastSocketErrorDescription(oslSocket pSocket, char* pBuffer, sal_uInt32 BufferSize) +{ + /* make sure pBuffer will be a zero-terminated string even when strncpy has to cut */ + pBuffer[BufferSize-1]= '\0'; + + if ( pSocket == nullptr ) + { + strncpy(pBuffer, strerror(EINVAL), BufferSize-1); + return; + } + + strncpy(pBuffer, strerror(pSocket->m_nLastError), BufferSize-1); +} + +oslSocketError SAL_CALL osl_getLastSocketError(oslSocket pSocket) +{ + if ( pSocket == nullptr ) + { + return ERROR_FROM_NATIVE(EINVAL); + } + + return ERROR_FROM_NATIVE(pSocket->m_nLastError); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/sockimpl.hxx b/sal/osl/unx/sockimpl.hxx new file mode 100644 index 000000000..1a9bc1f9e --- /dev/null +++ b/sal/osl/unx/sockimpl.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SOCKIMPL_HXX +#define INCLUDED_SAL_OSL_UNX_SOCKIMPL_HXX + +#include <osl/pipe.h> +#include <osl/socket.h> +#include <osl/interlck.h> + +#include "system.hxx" + +#if defined(LINUX) || defined(FREEBSD) || defined(NETBSD) +#define CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT 1 +#endif + +struct oslSocketImpl { + int m_Socket; + int m_nLastError; + oslInterlockedCount m_nRefCount; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + bool m_bIsAccepting; + bool m_bIsInShutdown; +#endif +}; + +struct oslSocketAddrImpl +{ + struct sockaddr m_sockaddr; +}; + +struct oslPipeImpl { + int m_Socket; + char m_Name[sizeof sockaddr_un::sun_path]; + oslInterlockedCount m_nRefCount; + bool m_bClosed; +#if defined(CLOSESOCKET_DOESNT_WAKE_UP_ACCEPT) + bool m_bIsAccepting; + bool m_bIsInShutdown; +#endif +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/soffice.cxx b/sal/osl/unx/soffice.cxx new file mode 100644 index 000000000..bcead388b --- /dev/null +++ b/sal/osl/unx/soffice.cxx @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <atomic> + +#include "soffice.hxx" + +namespace +{ +std::atomic<bool> flag(false); +} + +void sal::detail::setSoffice() { flag = true; } + +bool sal::detail::isSoffice() { return flag; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/unx/soffice.hxx b/sal/osl/unx/soffice.hxx new file mode 100644 index 000000000..da1b684af --- /dev/null +++ b/sal/osl/unx/soffice.hxx @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SOFFICE_HXX +#define INCLUDED_SAL_OSL_UNX_SOFFICE_HXX + +#include <sal/config.h> + +// Used to communicate special sal::detail::InitializeSoffice sal_detail_initialize call: + +namespace sal::detail +{ +void setSoffice(); + +bool isSoffice(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/unx/system.cxx b/sal/osl/unx/system.cxx new file mode 100644 index 000000000..875f41016 --- /dev/null +++ b/sal/osl/unx/system.cxx @@ -0,0 +1,167 @@ +/* -*- 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 <unistd.h> +#include <string.h> + +#include <config_features.h> + +#include "system.hxx" + +#ifdef NO_PTHREAD_RTL + +#if defined(MACOSX) + +#include <premac.h> +#include <Foundation/Foundation.h> +#include <postmac.h> + +/* + * Add support for resolving Mac native alias files (not the same as unix alias files) + * (what are "unix alias files"?) + * returns 0 on success. + */ +int macxp_resolveAlias(char *path, int buflen) +{ +#if HAVE_FEATURE_MACOSX_SANDBOX + /* Avoid unnecessary messages in the system.log: + * + * soffice(57342) deny file-read-data /Users/tml/Documents/b.odt/..namedfork/rsrc + * etc. + * + * Just don't bother with resolving aliases. I doubt its usefulness anyway. + */ + (void) path; + (void) buflen; + return 0; +#else + CFStringRef cfpath; + CFURLRef cfurl; + CFErrorRef cferror; + CFDataRef cfbookmark; + + // Don't even try anything for files inside the app bundle. Just a + // waste of time. + + static const char * const appBundle = [[[NSBundle mainBundle] bundlePath] UTF8String]; + + const size_t appBundleLen = strlen(appBundle); + if (strncmp(path, appBundle, appBundleLen) == 0 && path[appBundleLen] == '/') + return 0; + + char *unprocessedPath = path; + + if ( *unprocessedPath == '/' ) + unprocessedPath++; + + int nRet = 0; + while ( !nRet && unprocessedPath && *unprocessedPath ) + { + unprocessedPath = strchr( unprocessedPath, '/' ); + if ( unprocessedPath ) + *unprocessedPath = '\0'; + + cfpath = CFStringCreateWithCString( nullptr, path, kCFStringEncodingUTF8 ); + cfurl = CFURLCreateWithFileSystemPath( nullptr, cfpath, kCFURLPOSIXPathStyle, false ); + CFRelease( cfpath ); + cferror = nullptr; + cfbookmark = CFURLCreateBookmarkDataFromFile( nullptr, cfurl, &cferror ); + CFRelease( cfurl ); + + if ( cfbookmark == nullptr ) + { + if(cferror) + { + CFRelease( cferror ); + } + } + else + { + Boolean isStale; + cfurl = CFURLCreateByResolvingBookmarkData( nullptr, cfbookmark, kCFBookmarkResolutionWithoutUIMask, + nullptr, nullptr, &isStale, &cferror ); + CFRelease( cfbookmark ); + if ( cfurl == nullptr ) + { + CFRelease( cferror ); + } + else + { + cfpath = CFURLCopyFileSystemPath( cfurl, kCFURLPOSIXPathStyle ); + CFRelease( cfurl ); + if ( cfpath != nullptr ) + { + char tmpPath[ PATH_MAX ]; + if ( CFStringGetCString( cfpath, tmpPath, PATH_MAX, kCFStringEncodingUTF8 ) ) + { + int nLen = strlen( tmpPath ) + ( unprocessedPath ? strlen( unprocessedPath + 1 ) + 1 : 0 ); + if ( nLen < buflen && nLen < PATH_MAX ) + { + if ( unprocessedPath ) + { + int nTmpPathLen = strlen( tmpPath ); + strcat( tmpPath, "/" ); + strcat( tmpPath, unprocessedPath + 1 ); + strcpy( path, tmpPath); + unprocessedPath = path + nTmpPathLen; + } + else if ( !unprocessedPath ) + { + strcpy( path, tmpPath ); + } + } + else + { + errno = ENAMETOOLONG; + nRet = -1; + } + } + CFRelease( cfpath ); + } + } + } + + if ( unprocessedPath ) + *unprocessedPath++ = '/'; + } + + return nRet; +#endif +} + +#endif /* defined MACOSX */ + +#endif /* NO_PTHREAD_RTL */ + +//might be useful on other platforms, but doesn't compiler under MACOSX anyway +#if defined(__GNUC__) && defined(LINUX) +//force the __data_start symbol to exist in any executables that link against +//libuno_sal so that dlopening of the libgcj provided libjvm.so on some +//platforms where it needs that symbol will succeed. e.g. Debian mips/lenny +//with gcc 4.3. With this in place the smoketest succeeds with libgcj provided +//java. Quite possibly also required/helpful for s390x/s390 and maybe some +//others. Without it the dlopen of libjvm.so will fail with __data_start +//not found +extern int __data_start[] __attribute__((weak)); +extern int data_start[] __attribute__((weak)); +extern int _end[] __attribute__((weak)); +static void *dummy[] __attribute__((used)) = {__data_start, data_start, _end}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/system.hxx b/sal/osl/unx/system.hxx new file mode 100644 index 000000000..8b1f1dccc --- /dev/null +++ b/sal/osl/unx/system.hxx @@ -0,0 +1,406 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SYSTEM_HXX +#define INCLUDED_SAL_OSL_UNX_SYSTEM_HXX + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> + +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <signal.h> +#include <utime.h> + +#include <pwd.h> + +#include <netdb.h> + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <sys/types.h> + +/* Make sockets of type AF_UNIX use underlying FS rights */ +#if defined(__sun) && !defined(_XOPEN_SOURCE) +# define _XOPEN_SOURCE 500 +# include <sys/socket.h> +# undef _XOPEN_SOURCE +#else +# include <sys/socket.h> +#endif + +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifdef SYSV +# include <sys/utsname.h> +#endif + +#ifdef LINUX +# ifndef __USE_GNU +# define __USE_GNU +# endif + +# include <shadow.h> +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <dlfcn.h> +# include <endian.h> +# include <sys/time.h> +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +# define pthread_testcancel() +# define NO_PTHREAD_PRIORITY +# define PTHREAD_SIGACTION pthread_sigaction + +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif + +#endif + +#ifdef HAIKU +# include <shadow.h> +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <dlfcn.h> +# include <endian.h> +# include <sys/time.h> +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +# define pthread_testcancel() +# define NO_PTHREAD_PRIORITY +# define NO_PTHREAD_RTL +# define PTHREAD_SIGACTION pthread_sigaction + +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif +# define SIGIOT SIGABRT +# define SOCK_RDM 0 +// hack: Haiku defines SOL_SOCKET as -1, but this makes GCC complain about +// narrowing conversion +# undef SOL_SOCKET +# define SOL_SOCKET (sal_uInt32) -1 + +#endif + +#if defined(ANDROID) +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <dlfcn.h> +# include <endian.h> +# include <sys/time.h> +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +# define pthread_testcancel() +# define NO_PTHREAD_PRIORITY +#endif + +#ifdef NETBSD +# define NO_PTHREAD_RTL +#endif + +#ifdef FREEBSD +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif +# include <pthread.h> +# include <sys/sem.h> +# include <dlfcn.h> +# include <sys/filio.h> +# include <sys/ioctl.h> +# include <sys/param.h> +# include <sys/time.h> +# include <sys/uio.h> +# include <sys/exec.h> +# include <vm/vm.h> +# include <vm/vm_param.h> +# include <vm/pmap.h> +# include <vm/swap_pager.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# define IORESOURCE_TRANSFER_BSD +# include <machine/endian.h> +# define NO_PTHREAD_RTL +#endif + +#ifdef OPENBSD +# define ETIME ETIMEDOUT +# include <pthread.h> +# include <sys/sem.h> +# include <dlfcn.h> +# include <sys/filio.h> +# include <sys/ioctl.h> +# include <sys/param.h> +# include <sys/time.h> +# include <sys/uio.h> +# include <sys/exec.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# define IORESOURCE_TRANSFER_BSD +# include <machine/endian.h> +# define PTR_SIZE_T(s) ((size_t *)&(s)) +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +# define pthread_testcancel() +# define NO_PTHREAD_PRIORITY +# define NO_PTHREAD_RTL +# define PTHREAD_SIGACTION pthread_sigaction +#endif + +#if defined(DRAGONFLY) || defined(NETBSD) +# define ETIME ETIMEDOUT +# include <pthread.h> +# include <sys/sem.h> +# include <dlfcn.h> +# include <sys/filio.h> +# include <sys/ioctl.h> +# include <sys/param.h> +# include <sys/time.h> +# include <sys/uio.h> +# include <sys/exec.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <machine/endian.h> +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +#endif + +#ifdef AIX +# define AF_IPX -1 +# include <strings.h> +# include <pthread.h> +# include <dlfcn.h> +# include <sys/time.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <sys/machine.h> +# define SLEEP_TIMESPEC(timespec) nsleep(×pec, 0) +# define LIBPATH "LIBPATH" +#endif + +#ifdef __sun +# include <shadow.h> +# include <sys/un.h> +# include <stropts.h> +# include <pthread.h> +# include <netinet/tcp.h> +# include <sys/filio.h> +# include <dlfcn.h> +# include <sys/isa_defs.h> +# define IORESOURCE_TRANSFER_SYSV +# define IOCHANNEL_TRANSFER_BSD +# define LIBPATH "LD_LIBRARY_PATH" +#endif + +#ifdef MACOSX +#define TimeValue CFTimeValue // Do not conflict with TimeValue in sal/inc/osl/time.h +#include <Carbon/Carbon.h> +#undef TimeValue +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif +# include <dlfcn.h> +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <machine/endian.h> +# include <sys/time.h> +# include <mach-o/dyld.h> +# define IOCHANNEL_TRANSFER_BSD_RENO +# define NO_PTHREAD_RTL +/* for NSGetArgc/Argv/Environ */ +# include <crt_externs.h> +int macxp_resolveAlias(char *path, int buflen); +#endif + +#ifdef IOS +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif +# include <dlfcn.h> +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <machine/endian.h> +# include <sys/time.h> +# define IOCHANNEL_TRANSFER_BSD_RENO +# define NO_PTHREAD_RTL +#endif + +#ifdef EMSCRIPTEN +# ifndef ETIME +# define ETIME ETIMEDOUT +# endif +# include <pthread.h> +# include <sys/file.h> +# include <sys/ioctl.h> +# include <sys/uio.h> +# include <sys/un.h> +# include <netinet/tcp.h> +# include <dlfcn.h> +# include <endian.h> +# include <sys/time.h> +# define IORESOURCE_TRANSFER_BSD +# define IOCHANNEL_TRANSFER_BSD_RENO +# define pthread_testcancel() +# define NO_PTHREAD_PRIORITY +# define INIT_GROUPS(name, gid) false +#endif + +#if !defined(_WIN32) && \ + !defined(LINUX) && !defined(NETBSD) && !defined(FREEBSD) && \ + !defined(AIX) && \ + !defined(__sun) && !defined(MACOSX) && \ + !defined(OPENBSD) && !defined(DRAGONFLY) && \ + !defined(IOS) && !defined(ANDROID) && \ + !defined(HAIKU) && !defined(EMSCRIPTEN) +# error "Target platform not specified!" +#endif + +#ifndef PTR_FD_SET +# define PTR_FD_SET(s) (&(s)) +#endif + +#ifndef NORMALIZE_TIMESPEC +# define NORMALIZE_TIMESPEC(timespec) \ + timespec . tv_sec += timespec . tv_nsec / 1000000000; \ + timespec . tv_nsec %= 1000000000; +#endif + +#ifndef SET_TIMESPEC +# define SET_TIMESPEC(timespec, sec, nsec) \ + timespec . tv_sec = (sec); \ + timespec . tv_nsec = (nsec); \ + NORMALIZE_TIMESPEC(timespec); +#endif + +#ifndef SLEEP_TIMESPEC +# define SLEEP_TIMESPEC(timespec) nanosleep(×pec, nullptr) +#endif + +#ifndef INIT_GROUPS +# define INIT_GROUPS(name, gid) ((setgid((gid)) == 0) && (initgroups((name), (gid)) == 0)) +#endif + +#ifndef PTHREAD_NONE +# define PTHREAD_NONE _pthread_none_ +# ifndef PTHREAD_NONE_INIT +# define PTHREAD_NONE_INIT ((pthread_t)-1) +# endif +#endif + +#ifndef PTHREAD_ATTR_DEFAULT +# define PTHREAD_ATTR_DEFAULT nullptr +#endif +#ifndef PTHREAD_MUTEXATTR_DEFAULT +# define PTHREAD_MUTEXATTR_DEFAULT nullptr +#endif +#ifndef PTHREAD_CONDATTR_DEFAULT +# define PTHREAD_CONDATTR_DEFAULT nullptr +#endif + +#ifndef PTHREAD_SIGACTION +# define PTHREAD_SIGACTION sigaction +#endif + +#ifndef STAT_PARENT +# define STAT_PARENT lstat +#endif + +/* socket options which might not be defined on all unx flavors */ +#ifndef SO_ACCEPTCONN +# define SO_ACCEPTCONN 0 +#endif +#ifndef SO_SNDLOWAT +# define SO_SNDLOWAT 0 +#endif +#ifndef SO_RCVLOWAT +# define SO_RCVLOWAT 0 +#endif +#ifndef SO_SNDTIMEO +# define SO_SNDTIMEO 0 +#endif +#ifndef SO_RCVTIMEO +# define SO_RCVTIMEO 0 +#endif +#ifndef SO_USELOOPBACK +# define SO_USELOOPBACK 0 +#endif +#ifndef MSG_MAXIOVLEN +# define MSG_MAXIOVLEN 0 +#endif + +/* BEGIN HACK */ +/* dummy define and declarations for IPX should be replaced by */ +/* original ipx headers when these are available for this platform */ + +#ifndef SA_FAMILY_DECL +# define SA_FAMILY_DECL short sa_family +#endif + +#define NSPROTO_IPX 1000 +#define NSPROTO_SPX 1256 +#define NSPROTO_SPXII 1257 + +/* END HACK */ + +#ifdef NO_PTHREAD_RTL +#if !defined FREEBSD +#if !defined NETBSD +struct passwd *getpwent_r(struct passwd *pwd, char *buffer, int buflen); +#endif +extern struct spwd *getspnam_r(const char *name, struct spwd *result, + char *buffer, int buflen); + +#if !defined MACOSX +struct tm *localtime_r(const time_t *timep, struct tm *buffer); +struct tm *gmtime_r(const time_t *timep, struct tm *buffer); +#endif +#endif /* !defined(FREEBSD) */ +#endif + +#endif // INCLUDED_SAL_OSL_UNX_SYSTEM_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/system.mm b/sal/osl/unx/system.mm new file mode 100644 index 000000000..7495a3e73 --- /dev/null +++ b/sal/osl/unx/system.mm @@ -0,0 +1 @@ +#include "system.cxx" diff --git a/sal/osl/unx/tempfile.cxx b/sal/osl/unx/tempfile.cxx new file mode 100644 index 000000000..29a4d453f --- /dev/null +++ b/sal/osl/unx/tempfile.cxx @@ -0,0 +1,340 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "system.hxx" +#include <osl/file.h> +#include <osl/thread.h> +#include <rtl/ustrbuf.h> +#include <osl/diagnose.h> +#include <sal/macros.h> + +#include "file_url.hxx" +#include "file_impl.hxx" + +#include <cassert> + +oslFileError SAL_CALL osl_getTempDirURL( rtl_uString** pustrTempDir ) +{ + oslFileError error; + /* described in environ(7) */ + const char *pValue = getenv( "TMPDIR" ); + rtl_uString *ustrTempPath = nullptr; + + if ( !pValue ) + pValue = getenv( "TEMP" ); + + if ( !pValue ) + pValue = getenv( "TMP" ); + + if ( !pValue ) + pValue = "/tmp"; + + auto nLen = strlen(pValue); + while (nLen > 1 && pValue[nLen - 1] == '/') // Allow path consisting of single "/" + --nLen; + rtl_string2UString( &ustrTempPath, pValue, nLen, osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS ); + assert(ustrTempPath); + error = osl_getFileURLFromSystemPath( ustrTempPath, pustrTempDir ); + rtl_uString_release( ustrTempPath ); + + return error; +} + +/****************************************************************** + * Generates a random unique file name. We're using the scheme + * from the standard c-lib function mkstemp to generate a more + * or less random unique file name + * + * @param rand_name + * receives the random name + ******************************************************************/ + +const char LETTERS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; +const int COUNT_OF_LETTERS = SAL_N_ELEMENTS(LETTERS) - 1; + +#define RAND_NAME_LENGTH 6 + +static void osl_gen_random_name_impl_(rtl_uString** rand_name) +{ + static uint64_t value; + + char buffer[RAND_NAME_LENGTH]; + struct timeval tv; + uint64_t v; + int i; + + gettimeofday(&tv, nullptr); + + value += (static_cast<uint64_t>(tv.tv_usec) << 16) ^ tv.tv_sec ^ getpid(); + + v = value; + + for (i = 0; i < RAND_NAME_LENGTH; i++) + { + buffer[i] = LETTERS[v % COUNT_OF_LETTERS]; + v /= COUNT_OF_LETTERS; + } + + rtl_string2UString( + rand_name, + buffer, + RAND_NAME_LENGTH, + RTL_TEXTENCODING_ASCII_US, + OSTRING_TO_OUSTRING_CVTFLAGS); + + assert(*rand_name); +} + +/***************************************************************** + * Helper function + * Either use the directory provided or the result of + * osl_getTempDirUrl and return it as system path and file url + ****************************************************************/ + +static oslFileError osl_setup_base_directory_impl_( + rtl_uString* pustrDirectoryURL, + rtl_uString** ppustr_base_dir) +{ + rtl_uString* dir_url = nullptr; + rtl_uString* dir = nullptr; + oslFileError error = osl_File_E_None; + + if (pustrDirectoryURL) + rtl_uString_assign(&dir_url, pustrDirectoryURL); + else + error = osl_getTempDirURL(&dir_url); + + if (error == osl_File_E_None) + { + error = getSystemPathFromFileURL_Ex(dir_url, &dir); + rtl_uString_release(dir_url); + } + + if (error == osl_File_E_None) + { + rtl_uString_assign(ppustr_base_dir, dir); + rtl_uString_release(dir); + } + + return error; +} + +/***************************************************************** + * osl_setup_createTempFile_impl + * validate input parameter, setup variables + ****************************************************************/ + + static oslFileError osl_setup_createTempFile_impl_( + rtl_uString* pustrDirectoryURL, + oslFileHandle* pHandle, + rtl_uString** ppustrTempFileURL, + rtl_uString** ppustr_base_dir, + bool* b_delete_on_close) + { + oslFileError osl_error; + + OSL_PRECOND(((nullptr != pHandle) || (nullptr != ppustrTempFileURL)), "Invalid parameter!"); + + if ((pHandle == nullptr) && (ppustrTempFileURL == nullptr)) + { + osl_error = osl_File_E_INVAL; + } + else + { + osl_error = osl_setup_base_directory_impl_( + pustrDirectoryURL, ppustr_base_dir); + + *b_delete_on_close = (ppustrTempFileURL == nullptr); + } + + return osl_error; + } + +/***************************************************************** + * Create a unique file in the specified directory and return + * its name + ****************************************************************/ + +static oslFileError osl_create_temp_file_impl_( + const rtl_uString* pustr_base_directory, + oslFileHandle* file_handle, + rtl_uString** ppustr_temp_file_name) +{ + rtl_uString* rand_name = nullptr; + sal_uInt32 len_base_dir = 0; + rtl_uString* tmp_file_path = nullptr; + rtl_uString* tmp_file_url = nullptr; + sal_Int32 capacity = 0; + oslFileError osl_error = osl_File_E_None; + sal_Int32 offset_file_name; + const sal_Unicode* puchr; + + OSL_PRECOND(pustr_base_directory, "Invalid Parameter"); + OSL_PRECOND(file_handle, "Invalid Parameter"); + OSL_PRECOND(ppustr_temp_file_name, "Invalid Parameter"); + + len_base_dir = rtl_uString_getLength(pustr_base_directory); + + rtl_uString_new_WithLength( + &tmp_file_path, + (len_base_dir + 1 + RAND_NAME_LENGTH)); + capacity = len_base_dir + 1 + RAND_NAME_LENGTH; + + rtl_uStringbuffer_insert( + &tmp_file_path, + &capacity, + 0, + rtl_uString_getStr(const_cast<rtl_uString*>(pustr_base_directory)), + len_base_dir); + + offset_file_name = len_base_dir; + + puchr = rtl_uString_getStr(tmp_file_path); + + /* ensure that the last character is a '/' */ + + if (puchr[len_base_dir - 1] != '/') + { + rtl_uStringbuffer_insert_ascii( + &tmp_file_path, + &capacity, + len_base_dir, + "/", + 1); + + offset_file_name++; + } + + while(true) /* try until success */ + { + osl_gen_random_name_impl_(&rand_name); + + rtl_uStringbuffer_insert( + &tmp_file_path, + &capacity, + offset_file_name, + rtl_uString_getStr(rand_name), + rtl_uString_getLength(rand_name)); + + osl_error = osl_getFileURLFromSystemPath( + tmp_file_path, &tmp_file_url); + + if (osl_error == osl_File_E_None) + { + osl_error = openFile( + tmp_file_url, + file_handle, + osl_File_OpenFlag_Read | + osl_File_OpenFlag_Write | + osl_File_OpenFlag_Create, + S_IRUSR | S_IWUSR); + } + + /* in case of error osl_File_E_EXIST we simply try again else we give up */ + + if (osl_error != osl_File_E_EXIST) + { + rtl_uString_release(rand_name); + + if (tmp_file_url) + rtl_uString_release(tmp_file_url); + + break; + } + } /* while(1) */ + + if (osl_error == osl_File_E_None) + rtl_uString_assign(ppustr_temp_file_name, tmp_file_path); + + rtl_uString_release(tmp_file_path); + + return osl_error; +} + +oslFileError SAL_CALL osl_createTempFile( + rtl_uString* pustrDirectoryURL, + oslFileHandle* pHandle, + rtl_uString** ppustrTempFileURL) +{ + rtl_uString* base_directory = nullptr; + oslFileHandle temp_file_handle = nullptr; + bool b_delete_on_close; + oslFileError osl_error; + + osl_error = osl_setup_createTempFile_impl_( + pustrDirectoryURL, + pHandle, + ppustrTempFileURL, + &base_directory, + &b_delete_on_close); + + if (osl_error != osl_File_E_None) + return osl_error; + + rtl_uString* temp_file_name = nullptr; + osl_error = osl_create_temp_file_impl_( + base_directory, &temp_file_handle, &temp_file_name); + + rtl_uString* temp_file_url = nullptr; + if (osl_error == osl_File_E_None) + { + osl_error = osl_getFileURLFromSystemPath(temp_file_name, &temp_file_url); + rtl_uString_release(temp_file_name); + } + + if (osl_error == osl_File_E_None) + { + if (b_delete_on_close) + { + osl_error = osl_removeFile(temp_file_url); + + if (osl_error == osl_File_E_None) + { + *pHandle = temp_file_handle; + temp_file_handle = nullptr; + } + } + else + { + if (pHandle) + { + *pHandle = temp_file_handle; + temp_file_handle = nullptr; + } + + rtl_uString_assign(ppustrTempFileURL, temp_file_url); + } + + rtl_uString_release(temp_file_url); + } + + if (temp_file_handle) + osl_closeFile(temp_file_handle); + + rtl_uString_release(base_directory); + + return osl_error; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/thread.cxx b/sal/osl/unx/thread.cxx new file mode 100644 index 000000000..b122c5f31 --- /dev/null +++ b/sal/osl/unx/thread.cxx @@ -0,0 +1,1102 @@ +/* -*- 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 <cassert> +#include <cstddef> +#include <limits> +#include <functional> +#include <mutex> + +#include "system.hxx" +#include "unixerrnostring.hxx" +#include <string.h> +#if defined(OPENBSD) +#include <sched.h> +#endif +#ifdef __FreeBSD__ +#if __FreeBSD_version <= 1201517 +#include <pthread_np.h> +#define pthread_setname_np pthread_set_name_np +#endif +#endif +#include <config_options.h> +#include <o3tl/safeint.hxx> +#include <osl/thread.h> +#include <osl/nlsupport.h> +#include <rtl/textenc.h> +#include <rtl/alloc.h> +#include <sal/log.hxx> +#include <sal/macros.h> +#ifdef ANDROID +#include <jni.h> +#include <android/log.h> +#include <osl/detail/android-bootstrap.h> +#endif + +#if defined LINUX && ! defined __FreeBSD_kernel__ +#include <sys/syscall.h> +#endif + +/**************************************************************************** + * @@@ TODO @@@ + * + * (1) 'osl_thread_priority_init_Impl()' + * - insane assumption that initializing caller is main thread + * - use _POSIX_THREAD_PRIORITY_SCHEDULING, not NO_PTHREAD_PRIORITY (?) + * - POSIX doesn't require defined prio's for SCHED_OTHER (!) + * - use SCHED_RR instead of SCHED_OTHER for defined behaviour (?) + * (2) 'oslThreadIdentifier' and '{insert|remove|lookup}ThreadId()' + * - cannot reliably be applied to 'alien' threads; + * - memory leak for 'alien' thread 'HashEntry's; + * - use 'reinterpret_cast<unsigned long>(pthread_t)' as identifier + * instead (?) + * - if yes, change 'oslThreadIdentifier' to 'intptr_t' or similar + * (3) 'oslSigAlarmHandler()' (#71232#) + * - [Under Solaris we get SIGALRM in e.g. pthread_join which terminates + * the process. So we initialize our signal handling module and do + * register a SIGALRM Handler which catches and ignores it] + * - should this still happen, 'signal.c' needs to be fixed instead. + * + ****************************************************************************/ + +#define THREADIMPL_FLAGS_TERMINATE 0x00001 +#define THREADIMPL_FLAGS_STARTUP 0x00002 +#define THREADIMPL_FLAGS_SUSPENDED 0x00004 +#define THREADIMPL_FLAGS_ACTIVE 0x00008 +#define THREADIMPL_FLAGS_ATTACHED 0x00010 +#define THREADIMPL_FLAGS_DESTROYED 0x00020 + +namespace { + +typedef struct osl_thread_impl_st +{ + pthread_t m_hThread; + oslThreadIdentifier m_Ident; /* @@@ see TODO @@@ */ + short m_Flags; + oslWorkerFunction m_WorkerFunction; + void* m_pData; + pthread_mutex_t m_Lock; + pthread_cond_t m_Cond; +} Thread_Impl; + +#if !defined NO_PTHREAD_PRIORITY +struct osl_thread_priority_st +{ + int m_Highest; + int m_Above_Normal; + int m_Normal; + int m_Below_Normal; + int m_Lowest; +}; +#endif + +} + +#if !defined NO_PTHREAD_PRIORITY +#define OSL_THREAD_PRIORITY_INITIALIZER { 127, 96, 64, 32, 0 } +#endif + +static void osl_thread_priority_init_Impl(); + +namespace { + +struct osl_thread_textencoding_st +{ + pthread_key_t m_key; /* key to store thread local text encoding */ + rtl_TextEncoding m_default; /* the default text encoding */ +}; + +} + +#define OSL_THREAD_TEXTENCODING_INITIALIZER { 0, RTL_TEXTENCODING_DONTKNOW } +static void osl_thread_textencoding_init_Impl(); + +namespace { + +struct osl_thread_global_st +{ + pthread_once_t m_once; +#if !defined NO_PTHREAD_PRIORITY + struct osl_thread_priority_st m_priority; +#endif + struct osl_thread_textencoding_st m_textencoding; +}; + +} + +static struct osl_thread_global_st g_thread = +{ + PTHREAD_ONCE_INIT, +#if !defined NO_PTHREAD_PRIORITY + OSL_THREAD_PRIORITY_INITIALIZER, +#endif + OSL_THREAD_TEXTENCODING_INITIALIZER +}; + +static void osl_thread_init_Impl(); + +static Thread_Impl* osl_thread_construct_Impl(); +static void osl_thread_destruct_Impl (Thread_Impl ** ppImpl); + +static void* osl_thread_start_Impl (void * pData); +static void osl_thread_cleanup_Impl (Thread_Impl * pImpl); + +static oslThread osl_thread_create_Impl ( + oslWorkerFunction pWorker, void * pThreadData, short nFlags); + +/* @@@ see TODO @@@ */ +static oslThreadIdentifier insertThreadId (pthread_t hThread); +static oslThreadIdentifier lookupThreadId (pthread_t hThread); +static void removeThreadId (pthread_t hThread); + +static void osl_thread_init_Impl() +{ + osl_thread_priority_init_Impl(); + osl_thread_textencoding_init_Impl(); +} + +Thread_Impl* osl_thread_construct_Impl() +{ + Thread_Impl* pImpl = new Thread_Impl; + memset (pImpl, 0, sizeof(Thread_Impl)); + + pthread_mutex_init (&(pImpl->m_Lock), PTHREAD_MUTEXATTR_DEFAULT); + pthread_cond_init (&(pImpl->m_Cond), PTHREAD_CONDATTR_DEFAULT); + return pImpl; +} + +static void osl_thread_destruct_Impl (Thread_Impl ** ppImpl) +{ + assert(ppImpl); + if (*ppImpl) + { + pthread_cond_destroy (&((*ppImpl)->m_Cond)); + pthread_mutex_destroy (&((*ppImpl)->m_Lock)); + + delete *ppImpl; + (*ppImpl) = nullptr; + } +} + +static void osl_thread_cleanup_Impl (Thread_Impl * pImpl) +{ + pthread_t thread; + bool attached; + bool destroyed; + + pthread_mutex_lock (&(pImpl->m_Lock)); + + thread = pImpl->m_hThread; + attached = (pImpl->m_Flags & THREADIMPL_FLAGS_ATTACHED) != 0; + destroyed = (pImpl->m_Flags & THREADIMPL_FLAGS_DESTROYED) != 0; + pImpl->m_Flags &= ~(THREADIMPL_FLAGS_ACTIVE | THREADIMPL_FLAGS_ATTACHED); + + pthread_mutex_unlock (&(pImpl->m_Lock)); + + /* release oslThreadIdentifier @@@ see TODO @@@ */ + removeThreadId (thread); + + if (attached) + { + pthread_detach (thread); + } + + if (destroyed) + { + osl_thread_destruct_Impl (&pImpl); + } +} + +static void* osl_thread_start_Impl (void* pData) +{ + bool terminate; + Thread_Impl* pImpl= static_cast<Thread_Impl*>(pData); + + assert(pImpl); + + pthread_mutex_lock (&(pImpl->m_Lock)); + + /* request oslThreadIdentifier @@@ see TODO @@@ */ + pImpl->m_Ident = insertThreadId (pImpl->m_hThread); + + /* signal change from STARTUP to ACTIVE state */ + pImpl->m_Flags &= ~THREADIMPL_FLAGS_STARTUP; + pImpl->m_Flags |= THREADIMPL_FLAGS_ACTIVE; + pthread_cond_signal (&(pImpl->m_Cond)); + + /* Check if thread is started in SUSPENDED state */ + while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED) + { + /* wait until SUSPENDED flag is cleared */ + pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock)); + } + + /* check for SUSPENDED to TERMINATE state change */ + terminate = ((pImpl->m_Flags & THREADIMPL_FLAGS_TERMINATE) > 0); + + pthread_mutex_unlock (&(pImpl->m_Lock)); + + if (!terminate) + { +#ifdef ANDROID + JNIEnv* env = 0; + int res = (*lo_get_javavm()).AttachCurrentThread(&env, NULL); + __android_log_print(ANDROID_LOG_INFO, "LibreOffice", "New sal thread started and attached res=%d", res); +#endif + /* call worker function */ + pImpl->m_WorkerFunction(pImpl->m_pData); + +#ifdef ANDROID + res = (*lo_get_javavm()).DetachCurrentThread(); + __android_log_print(ANDROID_LOG_INFO, "LibreOffice", "Detached finished sal thread res=%d", res); +#endif + } + + osl_thread_cleanup_Impl (pImpl); + return nullptr; +} + +static oslThread osl_thread_create_Impl ( + oslWorkerFunction pWorker, + void* pThreadData, + short nFlags) +{ + Thread_Impl* pImpl; +#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS) + pthread_attr_t attr; + size_t stacksize; +#endif + int nRet=0; + + pImpl = osl_thread_construct_Impl(); + if (!pImpl) + return nullptr; /* ENOMEM */ + + pImpl->m_WorkerFunction = pWorker; + pImpl->m_pData = pThreadData; + pImpl->m_Flags = nFlags | THREADIMPL_FLAGS_STARTUP; + + pthread_mutex_lock (&(pImpl->m_Lock)); + +#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS) + if (pthread_attr_init(&attr) != 0) + return nullptr; + +#if defined OPENBSD + stacksize = 262144; +#elif !ENABLE_RUNTIME_OPTIMIZATIONS + stacksize = 12 * 1024 * 1024; // 8MB is not enough for ASAN on x86-64 +#else + stacksize = 1 * 1024 * 1024; // macOS default for non-main threads (512kB) is not enough... +#endif + if (pthread_attr_setstacksize(&attr, stacksize) != 0) { + pthread_attr_destroy(&attr); + return nullptr; + } +#endif + + if ((nRet = pthread_create ( + &(pImpl->m_hThread), +#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS) + &attr, +#else + PTHREAD_ATTR_DEFAULT, +#endif + osl_thread_start_Impl, + static_cast<void*>(pImpl))) != 0) + { + SAL_WARN( + "sal.osl", + "pthread_create failed: " << UnixErrnoString(nRet)); + + pthread_mutex_unlock (&(pImpl->m_Lock)); + osl_thread_destruct_Impl (&pImpl); + + return nullptr; + } + +#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS) + pthread_attr_destroy(&attr); +#endif + + /* wait for change from STARTUP to ACTIVE state */ + while (pImpl->m_Flags & THREADIMPL_FLAGS_STARTUP) + { + /* wait until STARTUP flag is cleared */ + pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock)); + } + + pthread_mutex_unlock (&(pImpl->m_Lock)); + + return static_cast<oslThread>(pImpl); +} + +oslThread osl_createThread ( + oslWorkerFunction pWorker, + void * pThreadData) +{ + return osl_thread_create_Impl ( + pWorker, + pThreadData, + THREADIMPL_FLAGS_ATTACHED); +} + +oslThread osl_createSuspendedThread ( + oslWorkerFunction pWorker, + void * pThreadData) +{ + return osl_thread_create_Impl ( + pWorker, + pThreadData, + THREADIMPL_FLAGS_ATTACHED | + THREADIMPL_FLAGS_SUSPENDED ); +} + +void SAL_CALL osl_destroyThread(oslThread Thread) +{ + if (Thread != nullptr) { + Thread_Impl * impl = static_cast<Thread_Impl *>(Thread); + bool active; + pthread_mutex_lock(&impl->m_Lock); + active = (impl->m_Flags & THREADIMPL_FLAGS_ACTIVE) != 0; + impl->m_Flags |= THREADIMPL_FLAGS_DESTROYED; + pthread_mutex_unlock(&impl->m_Lock); + if (!active) { + osl_thread_destruct_Impl(&impl); + } + } +} + +void SAL_CALL osl_resumeThread(oslThread Thread) +{ + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_resumeThread(nullptr) call"); + return; /* EINVAL */ + } + + pthread_mutex_lock (&(pImpl->m_Lock)); + + if (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED) + { + /* clear SUSPENDED flag */ + pImpl->m_Flags &= ~THREADIMPL_FLAGS_SUSPENDED; + pthread_cond_signal (&(pImpl->m_Cond)); + } + + pthread_mutex_unlock (&(pImpl->m_Lock)); +} + +void SAL_CALL osl_suspendThread(oslThread Thread) +{ + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_suspendThread(nullptr) call"); + return; /* EINVAL */ + } + + pthread_mutex_lock (&(pImpl->m_Lock)); + + pImpl->m_Flags |= THREADIMPL_FLAGS_SUSPENDED; + + if (pthread_equal (pthread_self(), pImpl->m_hThread)) + { + /* self suspend */ + while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED) + { + /* wait until SUSPENDED flag is cleared */ + pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock)); + } + } + + pthread_mutex_unlock (&(pImpl->m_Lock)); +} + +sal_Bool SAL_CALL osl_isThreadRunning(const oslThread Thread) +{ + bool active; + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + return false; + + pthread_mutex_lock (&(pImpl->m_Lock)); + active = ((pImpl->m_Flags & THREADIMPL_FLAGS_ACTIVE) > 0); + pthread_mutex_unlock (&(pImpl->m_Lock)); + + return active; +} + +void SAL_CALL osl_joinWithThread(oslThread Thread) +{ + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + return; + + pthread_mutex_lock (&(pImpl->m_Lock)); + + pthread_t const thread = pImpl->m_hThread; + bool const attached = ((pImpl->m_Flags & THREADIMPL_FLAGS_ATTACHED) > 0); + + /* check this only if *this* thread is still attached - if it's not, + then it could have terminated and another newly created thread could + have recycled the same id as m_hThread! */ + if (attached && pthread_equal(pthread_self(), pImpl->m_hThread)) + { + assert(false); /* Win32 implementation would deadlock here! */ + /* self join */ + pthread_mutex_unlock (&(pImpl->m_Lock)); + return; /* EDEADLK */ + } + + pImpl->m_Flags &= ~THREADIMPL_FLAGS_ATTACHED; + + pthread_mutex_unlock (&(pImpl->m_Lock)); + + if (attached) + { + pthread_join (thread, nullptr); + } +} + +void SAL_CALL osl_terminateThread(oslThread Thread) +{ + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_terminateThread(nullptr) call"); + return; /* EINVAL */ + } + + pthread_mutex_lock (&(pImpl->m_Lock)); + + if (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED) + { + /* clear SUSPENDED flag */ + pImpl->m_Flags &= ~THREADIMPL_FLAGS_SUSPENDED; + pthread_cond_signal (&(pImpl->m_Cond)); + } + + pImpl->m_Flags |= THREADIMPL_FLAGS_TERMINATE; + + pthread_mutex_unlock (&(pImpl->m_Lock)); +} + +sal_Bool SAL_CALL osl_scheduleThread(oslThread Thread) +{ + bool terminate; + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_scheduleThread(nullptr) call"); + return false; /* EINVAL */ + } + + if (!(pthread_equal (pthread_self(), pImpl->m_hThread))) + { + SAL_WARN("sal.osl", "invalid osl_scheduleThread(non-self) call"); + return false; /* EINVAL */ + } + + pthread_mutex_lock (&(pImpl->m_Lock)); + + while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED) + { + /* wait until SUSPENDED flag is cleared */ + pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock)); + } + + terminate = ((pImpl->m_Flags & THREADIMPL_FLAGS_TERMINATE) > 0); + + pthread_mutex_unlock(&(pImpl->m_Lock)); + + return !terminate; +} + +void SAL_CALL osl_waitThread(const TimeValue* pDelay) +{ + if (pDelay) + { + struct timespec delay; + + SET_TIMESPEC(delay, pDelay->Seconds, pDelay->Nanosec); + + SLEEP_TIMESPEC(delay); + } +} + +/** Yields thread + + @attention Note that POSIX scheduling @em really requires threads to call this + function, since a thread only reschedules to other thread, when + it blocks (sleep, blocking I/O) OR calls sched_yield(). +*/ +void SAL_CALL osl_yieldThread() +{ + sched_yield(); +} + +void SAL_CALL osl_setThreadName(char const * name) +{ + assert( name ); +#if defined LINUX && ! defined __FreeBSD_kernel__ + const int LINUX_THREAD_NAME_MAXLEN = 15; + if ( strlen( name ) > LINUX_THREAD_NAME_MAXLEN ) + SAL_INFO( "sal.osl", "osl_setThreadName truncated thread name to " + << LINUX_THREAD_NAME_MAXLEN << " chars from name '" + << name << "'" ); + char shortname[ LINUX_THREAD_NAME_MAXLEN + 1 ]; + shortname[ LINUX_THREAD_NAME_MAXLEN ] = '\0'; + strncpy( shortname, name, LINUX_THREAD_NAME_MAXLEN ); + int err = pthread_setname_np( pthread_self(), shortname ); + if ( 0 != err ) + SAL_WARN("sal.osl", "pthread_setname_np failed with errno " << err); +#elif defined __FreeBSD__ + pthread_setname_np( pthread_self(), name ); +#elif defined MACOSX || defined IOS + pthread_setname_np( name ); +#else + (void) name; +#endif +} + +/* osl_getThreadIdentifier @@@ see TODO @@@ */ + +namespace { + +struct HashEntry +{ + pthread_t Handle; + oslThreadIdentifier Ident; + HashEntry * Next; +}; + +} + +static HashEntry* HashTable[31]; +const int HashSize = SAL_N_ELEMENTS(HashTable); + +static std::mutex HashLock; + +#if ! ((defined LINUX && !defined __FreeBSD_kernel__) || defined MACOSX || defined IOS) +static oslThreadIdentifier LastIdent = 0; +#endif + +namespace { + +std::size_t HASHID(pthread_t x) +{ return std::hash<pthread_t>()(x) % HashSize; } + +} + +static oslThreadIdentifier lookupThreadId (pthread_t hThread) +{ + HashEntry *pEntry; + + std::unique_lock aGuard(HashLock); + + pEntry = HashTable[HASHID(hThread)]; + while (pEntry != nullptr) + { + if (pthread_equal(pEntry->Handle, hThread)) + { + return pEntry->Ident; + } + pEntry = pEntry->Next; + } + + return 0; +} + +static oslThreadIdentifier insertThreadId (pthread_t hThread) +{ + HashEntry *pEntry, *pInsert = nullptr; + + std::unique_lock aGuard(HashLock); + + pEntry = HashTable[HASHID(hThread)]; + + while (pEntry != nullptr) + { + if (pthread_equal(pEntry->Handle, hThread)) + break; + + pInsert = pEntry; + pEntry = pEntry->Next; + } + + if (pEntry == nullptr) + { + pEntry = static_cast<HashEntry*>(calloc(sizeof(HashEntry), 1)); + + pEntry->Handle = hThread; + +#if defined LINUX && ! defined __FreeBSD_kernel__ +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)) + // gettid returns a pid_t, which POSIX defines to be a signed integer type; assume that all + // valid pid_t values on Linux are positive (zero is filtered out in the generic code + // below): + pid_t const tid = gettid(); + assert(tid >= 0); +#else + long const tid = syscall(SYS_gettid); + if (tid < 0 || o3tl::make_unsigned(tid) > std::numeric_limits<sal_uInt32>::max()) { + std::abort(); + } +#endif + pEntry->Ident = tid; +#elif defined MACOSX || defined IOS + // currently the value of pthread_threadid_np is the same then + // syscall(SYS_thread_selfid), which returns an int as the TID. + // may change, as the syscall interface was deprecated. + uint64_t mac_tid; + pthread_threadid_np(nullptr, &mac_tid); + if (mac_tid > SAL_MAX_UINT32) + std::abort(); + pEntry->Ident = mac_tid; +#else + ++LastIdent; + if (0 == LastIdent) + LastIdent = 1; + pEntry->Ident = LastIdent; +#endif + if (0 == pEntry->Ident) + std::abort(); + + if (pInsert) + pInsert->Next = pEntry; + else + HashTable[HASHID(hThread)] = pEntry; + } + + return pEntry->Ident; +} + +static void removeThreadId (pthread_t hThread) +{ + HashEntry *pEntry, *pRemove = nullptr; + + std::unique_lock aGuard(HashLock); + + pEntry = HashTable[HASHID(hThread)]; + while (pEntry != nullptr) + { + if (pthread_equal(pEntry->Handle, hThread)) + break; + + pRemove = pEntry; + pEntry = pEntry->Next; + } + + if (pEntry != nullptr) + { + if (pRemove) + pRemove->Next = pEntry->Next; + else + HashTable[HASHID(hThread)] = pEntry->Next; + + free(pEntry); + } +} + +oslThreadIdentifier SAL_CALL osl_getThreadIdentifier(oslThread Thread) +{ + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + oslThreadIdentifier Ident; + + if (pImpl) + Ident = pImpl->m_Ident; + else + { + /* current thread */ + pthread_t current = pthread_self(); + + Ident = lookupThreadId (current); + if (Ident == 0) + /* @@@ see TODO: alien pthread_self() @@@ */ + Ident = insertThreadId (current); + } + + return Ident; +} + +/***************************************************************************** + @@@ see TODO @@@ + osl_thread_priority_init_Impl + + set the base-priority of the main-thread to + oslThreadPriorityNormal (64) since 0 (lowest) is + the system default. This behaviour collides with + our enum-priority definition (highest..normal..lowest). + A normaluser will expect the main-thread of an app. + to have the "normal" priority. + +*****************************************************************************/ +static void osl_thread_priority_init_Impl() +{ +#ifndef NO_PTHREAD_PRIORITY + struct sched_param param; + int policy=0; + int nRet=0; + +/* @@@ see TODO: calling thread may not be main thread @@@ */ + + if ((nRet = pthread_getschedparam(pthread_self(), &policy, ¶m)) != 0) + { + SAL_WARN( + "sal.osl", + "pthread_getschedparam failed: " << UnixErrnoString(nRet)); + return; + } + +#if defined (__sun) + if ( policy >= _SCHED_NEXT) + { + /* mfe: pthread_getschedparam on Solaris has a possible Bug */ + /* one gets 959917873 as the policy */ + /* so set the policy to a default one */ + policy=SCHED_OTHER; + } +#endif /* __sun */ + + if ((nRet = sched_get_priority_min(policy) ) != -1) + { + SAL_INFO( + "sal.osl", "Min Prioriy for policy " << policy << " == " << nRet); + g_thread.m_priority.m_Lowest=nRet; + } + else + { + int e = errno; + SAL_WARN( + "sal.osl", + "sched_get_priority_min failed: " << UnixErrnoString(e)); + } + + if ((nRet = sched_get_priority_max(policy) ) != -1) + { + SAL_INFO( + "sal.osl", "Max Prioriy for policy " << policy << " == " << nRet); + g_thread.m_priority.m_Highest=nRet; + } + else + { + int e = errno; + SAL_WARN( + "sal.osl", + "sched_get_priority_max failed: " << UnixErrnoString(e)); + } + + g_thread.m_priority.m_Normal = + (g_thread.m_priority.m_Lowest + g_thread.m_priority.m_Highest) / 2; + g_thread.m_priority.m_Below_Normal = + (g_thread.m_priority.m_Lowest + g_thread.m_priority.m_Normal) / 2; + g_thread.m_priority.m_Above_Normal = + (g_thread.m_priority.m_Normal + g_thread.m_priority.m_Highest) / 2; + +/* @@@ set prio of calling (not main) thread (?) @@@ */ + + param.sched_priority= g_thread.m_priority.m_Normal; + + if ((nRet = pthread_setschedparam(pthread_self(), policy, ¶m)) != 0) + { + SAL_WARN( + "sal.osl", + "pthread_setschedparam failed: " << UnixErrnoString(nRet)); + SAL_INFO( + "sal.osl", + "Thread ID " << pthread_self() << ", Policy " << policy + << ", Priority " << param.sched_priority); + } + +#endif /* NO_PTHREAD_PRIORITY */ +} + +/** + Impl-Notes: contrary to solaris-docu, which claims + valid priority-levels from 0 .. INT_MAX, only the + range 0..127 is accepted. (0 lowest, 127 highest) +*/ +void SAL_CALL osl_setThreadPriority ( + oslThread Thread, + oslThreadPriority Priority) +{ +#ifndef NO_PTHREAD_PRIORITY + + struct sched_param Param; + int policy; + int nRet; + +#endif /* NO_PTHREAD_PRIORITY */ + + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_setThreadPriority(nullptr, ...) call"); + return; /* EINVAL */ + } + +#ifdef NO_PTHREAD_PRIORITY + (void) Priority; /* unused */ +#else /* NO_PTHREAD_PRIORITY */ + + if (pthread_getschedparam(pImpl->m_hThread, &policy, &Param) != 0) + return; /* ESRCH */ + +#if defined (__sun) + if ( policy >= _SCHED_NEXT) + { + /* mfe: pthread_getschedparam on Solaris has a possible Bug */ + /* one gets 959917873 as the policy */ + /* so set the policy to a default one */ + policy=SCHED_OTHER; + } +#endif /* __sun */ + + pthread_once (&(g_thread.m_once), osl_thread_init_Impl); + + switch(Priority) + { + case osl_Thread_PriorityHighest: + Param.sched_priority= g_thread.m_priority.m_Highest; + break; + + case osl_Thread_PriorityAboveNormal: + Param.sched_priority= g_thread.m_priority.m_Above_Normal; + break; + + case osl_Thread_PriorityNormal: + Param.sched_priority= g_thread.m_priority.m_Normal; + break; + + case osl_Thread_PriorityBelowNormal: + Param.sched_priority= g_thread.m_priority.m_Below_Normal; + break; + + case osl_Thread_PriorityLowest: + Param.sched_priority= g_thread.m_priority.m_Lowest; + break; + + case osl_Thread_PriorityUnknown: + SAL_WARN( + "sal.osl", + "invalid osl_setThreadPriority(..., osl_Thread_PriorityUnknown)" + " call"); + return; + + default: + SAL_WARN( + "sal.osl", + "invalid osl_setThreadPriority(..., " << Priority << ") call"); + return; + } + + if ((nRet = pthread_setschedparam(pImpl->m_hThread, policy, &Param)) != 0) + { + SAL_WARN( + "sal.osl", + "pthread_setschedparam failed: " << UnixErrnoString(nRet)); + } + +#endif /* NO_PTHREAD_PRIORITY */ +} + +oslThreadPriority SAL_CALL osl_getThreadPriority(const oslThread Thread) +{ +#ifndef NO_PTHREAD_PRIORITY + + struct sched_param Param; + int Policy; + +#endif /* NO_PTHREAD_PRIORITY */ + + oslThreadPriority Priority = osl_Thread_PriorityNormal; + Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread); + + if (!pImpl) + { + SAL_WARN("sal.osl", "invalid osl_getThreadPriority(nullptr) call"); + return osl_Thread_PriorityUnknown; /* EINVAL */ + } + +#ifndef NO_PTHREAD_PRIORITY + + if (pthread_getschedparam(pImpl->m_hThread, &Policy, &Param) != 0) + return osl_Thread_PriorityUnknown; /* ESRCH */ + + pthread_once (&(g_thread.m_once), osl_thread_init_Impl); + + /* map pthread priority to enum */ + if (Param.sched_priority==g_thread.m_priority.m_Highest) + { + /* 127 - highest */ + Priority= osl_Thread_PriorityHighest; + } + else if (Param.sched_priority > g_thread.m_priority.m_Normal) + { + /* 65..126 - above normal */ + Priority= osl_Thread_PriorityAboveNormal; + } + else if (Param.sched_priority == g_thread.m_priority.m_Normal) + { + /* normal */ + Priority= osl_Thread_PriorityNormal; + } + else if (Param.sched_priority > g_thread.m_priority.m_Lowest) + { + /* 63..1 -below normal */ + Priority= osl_Thread_PriorityBelowNormal; + } + else if (Param.sched_priority == g_thread.m_priority.m_Lowest) + { + /* 0 - lowest */ + Priority= osl_Thread_PriorityLowest; + } + else + { + /* unknown */ + Priority= osl_Thread_PriorityUnknown; + } + +#endif /* NO_PTHREAD_PRIORITY */ + + return Priority; +} + +namespace { + +struct wrapper_pthread_key +{ + pthread_key_t m_key; + oslThreadKeyCallbackFunction pfnCallback; +}; + +} + +oslThreadKey SAL_CALL osl_createThreadKey( oslThreadKeyCallbackFunction pCallback ) +{ + wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(malloc(sizeof(wrapper_pthread_key))); + + if (pKey) + { + pKey->pfnCallback = pCallback; + + if (pthread_key_create(&(pKey->m_key), pKey->pfnCallback) != 0) + { + free(pKey); + pKey = nullptr; + } + } + + return static_cast<oslThreadKey>(pKey); +} + +void SAL_CALL osl_destroyThreadKey(oslThreadKey Key) +{ + wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key); + if (pKey) + { + pthread_key_delete(pKey->m_key); + free(pKey); + } +} + +void* SAL_CALL osl_getThreadKeyData(oslThreadKey Key) +{ + wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key); + return pKey ? pthread_getspecific(pKey->m_key) : nullptr; +} + +sal_Bool SAL_CALL osl_setThreadKeyData(oslThreadKey Key, void *pData) +{ + bool bRet; + void *pOldData = nullptr; + wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key); + if (!pKey) + return false; + + if (pKey->pfnCallback) + pOldData = pthread_getspecific(pKey->m_key); + + bRet = (pthread_setspecific(pKey->m_key, pData) == 0); + + if (bRet && pKey->pfnCallback && pOldData) + pKey->pfnCallback(pOldData); + + return bRet; +} + +static void osl_thread_textencoding_init_Impl() +{ + rtl_TextEncoding defaultEncoding; + + /* create thread specific data key */ + pthread_key_create (&(g_thread.m_textencoding.m_key), nullptr); + + /* determine default text encoding */ + defaultEncoding = osl_getTextEncodingFromLocale(nullptr); + // Tools string functions call abort() on an unknown encoding so ASCII is a + // meaningful fallback: + if ( RTL_TEXTENCODING_DONTKNOW == defaultEncoding ) + { + SAL_WARN("sal.osl", "RTL_TEXTENCODING_DONTKNOW -> _ASCII_US"); + defaultEncoding = RTL_TEXTENCODING_ASCII_US; + } + + g_thread.m_textencoding.m_default = defaultEncoding; +} + +rtl_TextEncoding SAL_CALL osl_getThreadTextEncoding() +{ + rtl_TextEncoding threadEncoding; + + pthread_once (&(g_thread.m_once), osl_thread_init_Impl); + + /* check for thread specific encoding, use default if not set */ + threadEncoding = static_cast<rtl_TextEncoding>( + reinterpret_cast<sal_uIntPtr>(pthread_getspecific(g_thread.m_textencoding.m_key))); + if (threadEncoding == 0) + threadEncoding = g_thread.m_textencoding.m_default; + + return threadEncoding; +} + +rtl_TextEncoding osl_setThreadTextEncoding(rtl_TextEncoding Encoding) +{ + rtl_TextEncoding oldThreadEncoding = osl_getThreadTextEncoding(); + + /* save encoding in thread local storage */ + pthread_setspecific ( + g_thread.m_textencoding.m_key, + reinterpret_cast<void*>(static_cast<sal_uIntPtr>(Encoding))); + + return oldThreadEncoding; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/time.cxx b/sal/osl/unx/time.cxx new file mode 100644 index 000000000..a77015614 --- /dev/null +++ b/sal/osl/unx/time.cxx @@ -0,0 +1,310 @@ +/* -*- 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 "saltime.hxx" +#include "system.hxx" + +#include <osl/time.h> +#include <time.h> +#include <unistd.h> + +#ifdef __MACH__ +#include <mach/clock.h> +#include <mach/mach.h> +#endif + +/* FIXME: detection should be done in configure script */ +#if defined(MACOSX) || defined(IOS) || defined(FREEBSD) || defined(NETBSD) || \ + defined(LINUX) || defined(OPENBSD) || defined(DRAGONFLY) +#define STRUCT_TM_HAS_GMTOFF 1 + +#elif defined(__sun) +#define HAS_ALTZONE 1 +#endif + +#ifdef __MACH__ +typedef mach_timespec_t osl_time_t; +#else +#if defined(_POSIX_TIMERS) +#define USE_CLOCK_GETTIME +typedef struct timespec osl_time_t; +#else +typedef struct timeval osl_time_t; +#endif +#endif +static osl_time_t startTime; + +sal_Bool SAL_CALL osl_getSystemTime(TimeValue* tv) +{ +#ifdef __MACH__ + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + tv->Seconds = mts.tv_sec; + tv->Nanosec = mts.tv_nsec; +#else + int res; + osl_time_t tp; +#if defined(USE_CLOCK_GETTIME) + res = clock_gettime(CLOCK_REALTIME, &tp); +#else + res = gettimeofday(&tp, NULL); +#endif + + if (res != 0) + { + return false; + } + + tv->Seconds = tp.tv_sec; + #if defined(USE_CLOCK_GETTIME) + tv->Nanosec = tp.tv_nsec; + #else + tv->Nanosec = tp.tv_usec * 1000; + #endif +#endif + return true; +} + +sal_Bool SAL_CALL osl_getDateTimeFromTimeValue( const TimeValue* pTimeVal, oslDateTime* pDateTime ) +{ + struct tm *pSystemTime; + struct tm tmBuf; + time_t atime; + + atime = static_cast<time_t>(pTimeVal->Seconds); + + /* Convert time from type time_t to struct tm */ + pSystemTime = gmtime_r( &atime, &tmBuf ); + + /* Convert struct tm to struct oslDateTime */ + if ( pSystemTime != nullptr ) + { + pDateTime->NanoSeconds = pTimeVal->Nanosec; + pDateTime->Seconds = pSystemTime->tm_sec; + pDateTime->Minutes = pSystemTime->tm_min; + pDateTime->Hours = pSystemTime->tm_hour; + pDateTime->Day = pSystemTime->tm_mday; + pDateTime->DayOfWeek = pSystemTime->tm_wday; + pDateTime->Month = pSystemTime->tm_mon + 1; + pDateTime->Year = pSystemTime->tm_year + 1900; + + return true; + } + + return false; +} + +sal_Bool SAL_CALL osl_getTimeValueFromDateTime( const oslDateTime* pDateTime, TimeValue* pTimeVal ) +{ + struct tm aTime; + time_t nSeconds; + + /* Convert struct oslDateTime to struct tm */ + aTime.tm_sec = pDateTime->Seconds; + aTime.tm_min = pDateTime->Minutes; + aTime.tm_hour = pDateTime->Hours; + aTime.tm_mday = pDateTime->Day; + + if ( pDateTime->Month > 0 ) + aTime.tm_mon = pDateTime->Month - 1; + else + return false; + + aTime.tm_year = pDateTime->Year - 1900; + + aTime.tm_isdst = -1; + aTime.tm_wday = 0; + aTime.tm_yday = 0; + +#if defined(STRUCT_TM_HAS_GMTOFF) + aTime.tm_gmtoff = 0; +#endif + + /* Convert time to calendar value */ + nSeconds = mktime( &aTime ); + + /* + * mktime expects the struct tm to be in local timezone, so we have to adjust + * the returned value to be timezone neutral. + */ + + if ( nSeconds != time_t(-1) ) + { + time_t bias; + + /* timezone corrections */ + tzset(); + +#if defined(STRUCT_TM_HAS_GMTOFF) + /* members of struct tm are corrected by mktime */ + bias = 0 - aTime.tm_gmtoff; + +#elif defined(HAS_ALTZONE) + /* check if daylight saving time is in effect */ + bias = aTime.tm_isdst > 0 ? altzone : timezone; +#else + /* expect daylight saving time to be one hour */ + bias = aTime.tm_isdst > 0 ? timezone - 3600 : timezone; +#endif + + pTimeVal->Seconds = nSeconds; + pTimeVal->Nanosec = pDateTime->NanoSeconds; + + if ( nSeconds > bias ) + pTimeVal->Seconds -= bias; + + return true; + } + + return false; +} + +sal_Bool SAL_CALL osl_getLocalTimeFromSystemTime( const TimeValue* pSystemTimeVal, TimeValue* pLocalTimeVal ) +{ + struct tm *pLocalTime; + struct tm tmBuf; + time_t bias; + time_t atime; + + atime = static_cast<time_t>(pSystemTimeVal->Seconds); + pLocalTime = localtime_r( &atime, &tmBuf ); + +#if defined(STRUCT_TM_HAS_GMTOFF) + /* members of struct tm are corrected by mktime */ + bias = -pLocalTime->tm_gmtoff; + +#elif defined(HAS_ALTZONE) + /* check if daylight saving time is in effect */ + bias = pLocalTime->tm_isdst > 0 ? altzone : timezone; +#else + /* expect daylight saving time to be one hour */ + bias = pLocalTime->tm_isdst > 0 ? timezone - 3600 : timezone; +#endif + + if ( static_cast<sal_Int64>(pSystemTimeVal->Seconds) > bias ) + { + pLocalTimeVal->Seconds = pSystemTimeVal->Seconds - bias; + pLocalTimeVal->Nanosec = pSystemTimeVal->Nanosec; + + return true; + } + + return false; +} + +sal_Bool SAL_CALL osl_getSystemTimeFromLocalTime( const TimeValue* pLocalTimeVal, TimeValue* pSystemTimeVal ) +{ + struct tm *pLocalTime; + struct tm tmBuf; + time_t bias; + time_t atime; + + atime = static_cast<time_t>(pLocalTimeVal->Seconds); + + /* Convert atime, which is a local time, to its GMT equivalent. Then, get + * the timezone offset for the local time for the GMT equivalent time. Note + * that we cannot directly use local time to determine the timezone offset + * because GMT is the only reliable time that we can determine timezone + * offset from. + */ + + atime = mktime( gmtime_r( &atime, &tmBuf ) ); + pLocalTime = localtime_r( &atime, &tmBuf ); + +#if defined(STRUCT_TM_HAS_GMTOFF) + /* members of struct tm are corrected by mktime */ + bias = 0 - pLocalTime->tm_gmtoff; + +#elif defined(HAS_ALTZONE) + /* check if daylight saving time is in effect */ + bias = pLocalTime->tm_isdst > 0 ? altzone : timezone; +#else + /* expect daylight saving time to be one hour */ + bias = pLocalTime->tm_isdst > 0 ? timezone - 3600 : timezone; +#endif + + if ( static_cast<sal_Int64>(pLocalTimeVal->Seconds) + bias > 0 ) + { + pSystemTimeVal->Seconds = pLocalTimeVal->Seconds + bias; + pSystemTimeVal->Nanosec = pLocalTimeVal->Nanosec; + + return true; + } + + return false; +} + +void sal_initGlobalTimer() +{ +#ifdef __MACH__ + clock_serv_t cclock; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &startTime); + mach_port_deallocate(mach_task_self(), cclock); +#else /* ! (MACOSX || IOS) */ +#if defined(USE_CLOCK_GETTIME) + clock_gettime(CLOCK_REALTIME, &startTime); +#else /* Ndef USE_CLOCK_GETTIME */ + gettimeofday( &startTime, NULL ); +#endif /* NDef USE_CLOCK_GETTIME */ +#endif /* ! (MACOSX || IOS) */ +} + +sal_uInt32 SAL_CALL osl_getGlobalTimer() +{ + sal_uInt32 nSeconds; + +#ifdef __MACH__ + clock_serv_t cclock; + mach_timespec_t currentTime; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, ¤tTime); + mach_port_deallocate(mach_task_self(), cclock); + + nSeconds = ( currentTime.tv_sec - startTime.tv_sec ); + nSeconds = ( nSeconds * 1000 ) + static_cast<long>(( currentTime.tv_nsec - startTime.tv_nsec) / 1000000 ); +#else + osl_time_t currentTime; + +#if defined(USE_CLOCK_GETTIME) + clock_gettime(CLOCK_REALTIME, ¤tTime); +#else + gettimeofday( ¤tTime, NULL ); +#endif + + nSeconds = static_cast<sal_uInt32>( currentTime.tv_sec - startTime.tv_sec ); +#if defined(USE_CLOCK_GETTIME) + nSeconds = ( nSeconds * 1000 ) + static_cast<long>(( currentTime.tv_nsec - startTime.tv_nsec) / 1000000 ); +#else + nSeconds = ( nSeconds * 1000 ) + (long) (( currentTime.tv_usec - startTime.tv_usec) / 1000 ); +#endif +#endif + return nSeconds; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/unixerrnostring.hxx b/sal/osl/unx/unixerrnostring.hxx new file mode 100644 index 000000000..9e13b04e6 --- /dev/null +++ b/sal/osl/unx/unixerrnostring.hxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#ifndef INCLUDED_SAL_OSL_UNX_UNIXERRNOSTRING_HXX +#define INCLUDED_SAL_OSL_UNX_UNIXERRNOSTRING_HXX + +#include <string> + +// Return the symbolic name of an errno value, like "ENOENT". + +// Rationale why to use this and not strerror(): This is intended to be used in SAL_INFO() and +// SAL_WARN(). Such messages are intended to be read by developers, not end-users. Developers are +// (or should be) familiar with symbolic errno names in code anyway. strerror() is localized and the +// localised error strings might be less familiar to a developer that happens to run a localised +// environment. + +std::string UnixErrnoString(int nErrno); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/uunxapi.cxx b/sal/osl/unx/uunxapi.cxx new file mode 100644 index 000000000..5311bc719 --- /dev/null +++ b/sal/osl/unx/uunxapi.cxx @@ -0,0 +1,905 @@ +/* -*- Mode: ObjC; 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 <string_view> + +#include <config_features.h> + +#include "uunxapi.hxx" +#include "system.hxx" +#include "unixerrnostring.hxx" +#include <limits.h> +#include <rtl/ustring.hxx> +#include <osl/thread.h> +#include <sal/log.hxx> + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +OString osl::OUStringToOString(std::u16string_view s) +{ + return rtl::OUStringToOString(s, osl_getThreadTextEncoding()); +} + +#if HAVE_FEATURE_MACOSX_SANDBOX + +#include <Foundation/Foundation.h> +#include <Security/Security.h> +#include <mach-o/dyld.h> + +static NSUserDefaults *userDefaults = NULL; +static bool isSandboxed = false; + +static void do_once() +{ + SecCodeRef code; + OSStatus rc = SecCodeCopySelf(kSecCSDefaultFlags, &code); + + SecStaticCodeRef staticCode; + if (rc == errSecSuccess) + rc = SecCodeCopyStaticCode(code, kSecCSDefaultFlags, &staticCode); + + CFDictionaryRef signingInformation; + if (rc == errSecSuccess) + rc = SecCodeCopySigningInformation(staticCode, kSecCSRequirementInformation, &signingInformation); + + CFDictionaryRef entitlements = NULL; + if (rc == errSecSuccess) + entitlements = (CFDictionaryRef) CFDictionaryGetValue(signingInformation, kSecCodeInfoEntitlementsDict); + + if (entitlements != NULL) + if (CFDictionaryGetValue(entitlements, CFSTR("com.apple.security.app-sandbox")) != NULL) + isSandboxed = true; + + if (isSandboxed) + userDefaults = [NSUserDefaults standardUserDefaults]; +} + +typedef struct { + NSURL *scopeURL; + NSAutoreleasePool *pool; +} accessFilePathState; + +static accessFilePathState * +prepare_to_access_file_path( const char *cpFilePath ) +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, &do_once); + NSURL *fileURL = nil; + NSData *data = nil; + BOOL stale; + accessFilePathState *state; + + if (!isSandboxed) + return NULL; + + // If malloc() fails we are screwed anyway + state = (accessFilePathState*) malloc(sizeof(accessFilePathState)); + + state->pool = [[NSAutoreleasePool alloc] init]; + state->scopeURL = nil; + + if (userDefaults != nil) + fileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:cpFilePath]]; + + if (fileURL != nil) + data = [userDefaults dataForKey:[@"bookmarkFor:" stringByAppendingString:[fileURL absoluteString]]]; + + if (data != nil) + state->scopeURL = [NSURL URLByResolvingBookmarkData:data + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:nil + bookmarkDataIsStale:&stale + error:nil]; + if (state->scopeURL != nil) + [state->scopeURL startAccessingSecurityScopedResource]; + + return state; +} + +static void +done_accessing_file_path( const char * /*cpFilePath*/, accessFilePathState *state ) +{ + if (!isSandboxed) + return; + + int saved_errno = errno; + + if (state->scopeURL != nil) + [state->scopeURL stopAccessingSecurityScopedResource]; + [state->pool release]; + free(state); + + errno = saved_errno; +} + +#else + +typedef void accessFilePathState; + +#define prepare_to_access_file_path( cpFilePath ) nullptr + +#define done_accessing_file_path( cpFilePath, state ) ((void) cpFilePath, (void) state) + +#endif + +#ifdef MACOSX +/* + * Helper function for resolving Mac native alias files (not the same as unix alias files) + * and to return the resolved alias as OString + */ +static OString macxp_resolveAliasAndConvert(OString const & p) +{ + char path[PATH_MAX]; + if (p.getLength() < PATH_MAX) + { + strcpy(path, p.getStr()); + macxp_resolveAlias(path, PATH_MAX); + return OString(path); + } + return p; +} +#endif /* MACOSX */ + +int osl::access(const OString& pstrPath, int mode) +{ + OString fn = pstrPath; +#ifdef ANDROID + if (fn == "/assets" || fn.startsWith("/assets/")) + { + struct stat stat; + if (lo_apk_lstat(fn.getStr(), &stat) == -1) + return -1; + if (mode & W_OK) + { + errno = EACCES; + return -1; + } + return 0; + } +#endif + +#ifdef MACOSX + fn = macxp_resolveAliasAndConvert(fn); +#endif + + accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); + + int result = ::access(fn.getStr(), mode); + int saved_errno = errno; + if (result == -1) + SAL_INFO("sal.file", "access(" << fn << ",0" << std::oct << mode << std::dec << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "access(" << fn << ",0" << std::oct << mode << std::dec << "): OK"); + + done_accessing_file_path(fn.getStr(), state); + + errno = saved_errno; + + return result; +} + +namespace { + +OString toOString(OString const & s) { return s; } + +OString toOString(std::u16string_view s) { return osl::OUStringToOString(s); } + +template<typename T> T fromOString(OString const &) = delete; + +template<> OString fromOString(OString const & s) { return s; } + +template<> OUString fromOString(OString const & s) +{ return OStringToOUString(s, osl_getThreadTextEncoding()); } + +template<typename T> bool realpath_(const T& pstrFileName, T& ppstrResolvedName) +{ + OString fn = toOString(pstrFileName); +#if defined ANDROID || defined(EMSCRIPTEN) +#if defined ANDROID + if (fn == "/assets" || fn.startsWith("/assets/")) +#else + if (fn == "/instdir" || fn.startsWith("/instdir/")) +#endif + { + if (osl::access(fn, F_OK) == -1) + return false; + + ppstrResolvedName = pstrFileName; + + return true; + } +#endif // ANDROID || EMSCRIPTEN + +#ifdef MACOSX + fn = macxp_resolveAliasAndConvert(fn); +#endif + + accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); + + char rp[PATH_MAX]; + bool bRet = realpath(fn.getStr(), rp); + int saved_errno = errno; + if (!bRet) + SAL_INFO("sal.file", "realpath(" << fn << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "realpath(" << fn << "): OK"); + + done_accessing_file_path(fn.getStr(), state); + + if (bRet) + { + ppstrResolvedName = fromOString<T>(OString(rp)); + } + + errno = saved_errno; + + return bRet; +} + +} + +bool osl::realpath(const OUString& pustrFileName, OUString& ppustrResolvedName) +{ + return realpath_(pustrFileName, ppustrResolvedName); +} + +bool osl::realpath(const OString& pstrFileName, OString& ppstrResolvedName) +{ + return realpath_(pstrFileName, ppstrResolvedName); +} + +int stat_c(const char* cpPath, struct stat* buf) +{ +#ifdef ANDROID + if (strncmp(cpPath, "/assets", sizeof("/assets")-1) == 0 && + (cpPath[sizeof("/assets")-1] == '\0' || + cpPath[sizeof("/assets")-1] == '/')) + return lo_apk_lstat(cpPath, buf); +#endif + + accessFilePathState *state = prepare_to_access_file_path(cpPath); + + int result = stat(cpPath, buf); + int saved_errno = errno; + if (result == -1) + SAL_INFO("sal.file", "stat(" << cpPath << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "stat(" << cpPath << "): OK"); + + done_accessing_file_path(cpPath, state); + + errno = saved_errno; + + return result; +} + +int lstat_c(const char* cpPath, struct stat* buf) +{ +#ifdef ANDROID + if (strncmp(cpPath, "/assets", sizeof("/assets")-1) == 0 && + (cpPath[sizeof("/assets")-1] == '\0' || + cpPath[sizeof("/assets")-1] == '/')) + return lo_apk_lstat(cpPath, buf); +#endif + + accessFilePathState *state = prepare_to_access_file_path(cpPath); + + int result = lstat(cpPath, buf); + int saved_errno = errno; + if (result == -1) + SAL_INFO("sal.file", "lstat(" << cpPath << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "lstat(" << cpPath << "): OK"); + + done_accessing_file_path(cpPath, state); + + errno = saved_errno; + + return result; +} + +namespace { + +template<typename T> int lstat_(const T& pstrPath, struct stat& buf) +{ + OString fn = toOString(pstrPath); + +#ifdef MACOSX + fn = macxp_resolveAliasAndConvert(fn); +#endif + + return lstat_c(fn.getStr(), &buf); +} + +} + +int osl::lstat(const OUString& pustrPath, struct stat& buf) +{ + return lstat_(pustrPath, buf); +} + +int osl::lstat(const OString& pstrPath, struct stat& buf) +{ + return lstat_(pstrPath, buf); +} + +int osl::mkdir(const OString& path, mode_t mode) +{ + accessFilePathState *state = prepare_to_access_file_path(path.getStr()); + + int result = ::mkdir(path.getStr(), mode); + int saved_errno = errno; + if (result == -1) + SAL_INFO("sal.file", "mkdir(" << path << ",0" << std::oct << mode << std::dec << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "mkdir(" << path << ",0" << std::oct << mode << std::dec << "): OK"); + + done_accessing_file_path(path.getStr(), state); + + errno = saved_errno; + + return result; +} + +int open_c(const OString& path, int oflag, int mode) +{ + accessFilePathState *state = prepare_to_access_file_path(path.getStr()); + + int result = open(path.getStr(), oflag, mode); + int saved_errno = errno; + if (result == -1) + SAL_INFO("sal.file", "open(" << path << ",0" << std::oct << oflag << ",0" << mode << std::dec << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "open(" << path << ",0" << std::oct << oflag << ",0" << mode << std::dec << ") => " << result); + +#if HAVE_FEATURE_MACOSX_SANDBOX + if (isSandboxed && result != -1 && (oflag & O_CREAT) && (oflag & O_EXCL)) + { + // A new file was created. Check if it is outside the sandbox. + // (In that case it must be one the user selected as export or + // save destination in a file dialog, otherwise we wouldn't + // have been able to create it.) Create and store a security + // scoped bookmark for it so that we can access the file in + // the future, too. (For the "Recent Files" functionality.) + const char *sandbox = [NSHomeDirectory() UTF8String]; + if (!(strncmp(sandbox, path.getStr(), strlen(sandbox)) == 0 && + path[strlen(sandbox)] == '/')) + { + auto cpPath = path.getStr(); + NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:cpPath]]; + NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope + includingResourceValuesForKeys:nil + relativeToURL:nil + error:nil]; + if (data != NULL) + { + [userDefaults setObject:data + forKey:[@"bookmarkFor:" stringByAppendingString:[url absoluteString]]]; + } + } + } +#endif + + done_accessing_file_path(path.getStr(), state); + + errno = saved_errno; + + return result; +} + +int utime_c(const char *cpPath, struct utimbuf *times) +{ + accessFilePathState *state = prepare_to_access_file_path(cpPath); + + int result = utime(cpPath, times); + + done_accessing_file_path(cpPath, state); + + return result; +} + +int ftruncate_with_name(int fd, sal_uInt64 uSize, const OString& path) +{ + /* When sandboxed on macOS, ftruncate(), even if it takes an + * already open file descriptor which was returned from an open() + * call already checked by the sandbox, still requires a security + * scope bookmark for the file to be active in case the file is + * one that the sandbox doesn't otherwise allow access to. Luckily + * LibreOffice usually calls ftruncate() through the helpful C++ + * abstraction layer that keeps the pathname around. + */ + + OString fn(path); + +#ifdef MACOSX + fn = macxp_resolveAliasAndConvert(fn); +#endif + + accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); + + int result = ftruncate(fd, uSize); + int saved_errno = errno; + if (result < 0) + SAL_INFO("sal.file", "ftruncate(" << fd << "," << uSize << "): " << UnixErrnoString(saved_errno)); + else + SAL_INFO("sal.file", "ftruncate(" << fd << "," << uSize << "): OK"); + + done_accessing_file_path(fn.getStr(), state); + + errno = saved_errno; + + return result; +} + + +std::string UnixErrnoString(int nErrno) +{ + // Errnos from <asm-generic/errno-base.h> and <asm-generic/errno.h> on Linux and <sys/errno.h> + // on macOS. + switch (nErrno) + { + case EPERM: + return "EPERM"; + case ENOENT: + return "ENOENT"; + case ESRCH: + return "ESRCH"; + case EINTR: + return "EINTR"; + case EIO: + return "EIO"; + case ENXIO: + return "ENXIO"; + case E2BIG: + return "E2BIG"; + case ENOEXEC: + return "ENOEXEC"; + case EBADF: + return "EBADF"; + case ECHILD: + return "ECHILD"; + case EAGAIN: + return "EAGAIN"; + case ENOMEM: + return "ENOMEM"; + case EACCES: + return "EACCES"; + case EFAULT: + return "EFAULT"; +#ifdef ENOTBLK + case ENOTBLK: + return "ENOTBLK"; +#endif + case EBUSY: + return "EBUSY"; + case EEXIST: + return "EEXIST"; + case EXDEV: + return "EXDEV"; + case ENODEV: + return "ENODEV"; + case ENOTDIR: + return "ENOTDIR"; + case EISDIR: + return "EISDIR"; + case EINVAL: + return "EINVAL"; + case ENFILE: + return "ENFILE"; + case EMFILE: + return "EMFILE"; + case ENOTTY: + return "ENOTTY"; + case ETXTBSY: + return "ETXTBSY"; + case EFBIG: + return "EFBIG"; + case ENOSPC: + return "ENOSPC"; + case ESPIPE: + return "ESPIPE"; + case EROFS: + return "EROFS"; + case EMLINK: + return "EMLINK"; + case EPIPE: + return "EPIPE"; + case EDOM: + return "EDOM"; + case ERANGE: + return "ERANGE"; + case EDEADLK: + return "EDEADLK"; + case ENAMETOOLONG: + return "ENAMETOOLONG"; + case ENOLCK: + return "ENOLCK"; + case ENOSYS: + return "ENOSYS"; + case ENOTEMPTY: + return "ENOTEMPTY"; + case ELOOP: + return "ELOOP"; + case ENOMSG: + return "ENOMSG"; + case EIDRM: + return "EIDRM"; +#ifdef ECHRNG + case ECHRNG: + return "ECHRNG"; +#endif +#ifdef EL2NSYNC + case EL2NSYNC: + return "EL2NSYNC"; +#endif +#ifdef EL3HLT + case EL3HLT: + return "EL3HLT"; +#endif +#ifdef EL3RST + case EL3RST: + return "EL3RST"; +#endif +#ifdef ELNRNG + case ELNRNG: + return "ELNRNG"; +#endif +#ifdef EUNATCH + case EUNATCH: + return "EUNATCH"; +#endif +#ifdef ENOCSI + case ENOCSI: + return "ENOCSI"; +#endif +#ifdef EL2HLT + case EL2HLT: + return "EL2HLT"; +#endif +#ifdef EBADE + case EBADE: + return "EBADE"; +#endif +#ifdef EBADR + case EBADR: + return "EBADR"; +#endif +#ifdef EXFULL + case EXFULL: + return "EXFULL"; +#endif +#ifdef ENOANO + case ENOANO: + return "ENOANO"; +#endif +#ifdef EBADRQC + case EBADRQC: + return "EBADRQC"; +#endif +#ifdef EBADSLT + case EBADSLT: + return "EBADSLT"; +#endif +#ifdef EBFONT + case EBFONT: + return "EBFONT"; +#endif + case ENOSTR: + return "ENOSTR"; + case ENODATA: + return "ENODATA"; + case ETIME: + return "ETIME"; + case ENOSR: + return "ENOSR"; +#ifdef ENONET + case ENONET: + return "ENONET"; +#endif +#ifdef ENOPKG + case ENOPKG: + return "ENOPKG"; +#endif +#ifdef EREMOTE + case EREMOTE: + return "EREMOTE"; +#endif + case ENOLINK: + return "ENOLINK"; +#ifdef EADV + case EADV: + return "EADV"; +#endif +#ifdef ESRMNT + case ESRMNT: + return "ESRMNT"; +#endif +#ifdef ECOMM + case ECOMM: + return "ECOMM"; +#endif + case EPROTO: + return "EPROTO"; + case EMULTIHOP: + return "EMULTIHOP"; +#ifdef EDOTDOT + case EDOTDOT: + return "EDOTDOT"; +#endif + case EBADMSG: + return "EBADMSG"; + case EOVERFLOW: + return "EOVERFLOW"; +#ifdef ENOTUNIQ + case ENOTUNIQ: + return "ENOTUNIQ"; +#endif +#ifdef EBADFD + case EBADFD: + return "EBADFD"; +#endif +#ifdef EREMCHG + case EREMCHG: + return "EREMCHG"; +#endif +#ifdef ELIBACC + case ELIBACC: + return "ELIBACC"; +#endif +#ifdef ELIBBAD + case ELIBBAD: + return "ELIBBAD"; +#endif +#ifdef ELIBSCN + case ELIBSCN: + return "ELIBSCN"; +#endif +#ifdef ELIBMAX + case ELIBMAX: + return "ELIBMAX"; +#endif +#ifdef ELIBEXEC + case ELIBEXEC: + return "ELIBEXEC"; +#endif + case EILSEQ: + return "EILSEQ"; +#ifdef ERESTART + case ERESTART: + return "ERESTART"; +#endif +#ifdef ESTRPIPE + case ESTRPIPE: + return "ESTRPIPE"; +#endif +#ifdef EUSERS + case EUSERS: + return "EUSERS"; +#endif + case ENOTSOCK: + return "ENOTSOCK"; + case EDESTADDRREQ: + return "EDESTADDRREQ"; + case EMSGSIZE: + return "EMSGSIZE"; + case EPROTOTYPE: + return "EPROTOTYPE"; + case ENOPROTOOPT: + return "ENOPROTOOPT"; + case EPROTONOSUPPORT: + return "EPROTONOSUPPORT"; +#ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: + return "ESOCKTNOSUPPORT"; +#endif +#ifdef EOPNOTSUPP + case EOPNOTSUPP: + return "EOPNOTSUPP"; +#endif + case EPFNOSUPPORT: + return "EPFNOSUPPORT"; + case EAFNOSUPPORT: + return "EAFNOSUPPORT"; + case EADDRINUSE: + return "EADDRINUSE"; + case EADDRNOTAVAIL: + return "EADDRNOTAVAIL"; + case ENETDOWN: + return "ENETDOWN"; + case ENETUNREACH: + return "ENETUNREACH"; + case ENETRESET: + return "ENETRESET"; + case ECONNABORTED: + return "ECONNABORTED"; + case ECONNRESET: + return "ECONNRESET"; + case ENOBUFS: + return "ENOBUFS"; + case EISCONN: + return "EISCONN"; + case ENOTCONN: + return "ENOTCONN"; +#ifdef ESHUTDOWN + case ESHUTDOWN: + return "ESHUTDOWN"; +#endif +#ifdef ETOOMANYREFS + case ETOOMANYREFS: + return "ETOOMANYREFS"; +#endif + case ETIMEDOUT: + return "ETIMEDOUT"; + case ECONNREFUSED: + return "ECONNREFUSED"; +#ifdef EHOSTDOWN + case EHOSTDOWN: + return "EHOSTDOWN"; +#endif + case EHOSTUNREACH: + return "EHOSTUNREACH"; + case EALREADY: + return "EALREADY"; + case EINPROGRESS: + return "EINPROGRESS"; + case ESTALE: + return "ESTALE"; +#ifdef EUCLEAN + case EUCLEAN: + return "EUCLEAN"; +#endif +#ifdef ENOTNAM + case ENOTNAM: + return "ENOTNAM"; +#endif +#ifdef ENAVAIL + case ENAVAIL: + return "ENAVAIL"; +#endif +#ifdef EISNAM + case EISNAM: + return "EISNAM"; +#endif +#ifdef EREMOTEIO + case EREMOTEIO: + return "EREMOTEIO"; +#endif + case EDQUOT: + return "EDQUOT"; +#ifdef ENOMEDIUM + case ENOMEDIUM: + return "ENOMEDIUM"; +#endif +#ifdef EMEDIUMTYPE + case EMEDIUMTYPE: + return "EMEDIUMTYPE"; +#endif + case ECANCELED: + return "ECANCELED"; +#ifdef ENOKEY + case ENOKEY: + return "ENOKEY"; +#endif +#ifdef EKEYEXPIRED + case EKEYEXPIRED: + return "EKEYEXPIRED"; +#endif +#ifdef EKEYREVOKED + case EKEYREVOKED: + return "EKEYREVOKED"; +#endif +#ifdef EKEYREJECTED + case EKEYREJECTED: + return "EKEYREJECTED"; +#endif +#ifdef EOWNERDEAD + case EOWNERDEAD: + return "EOWNERDEAD"; +#endif +#ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: + return "ENOTRECOVERABLE"; +#endif +#ifdef ERFKILL + case ERFKILL: + return "ERFKILL"; +#endif +#ifdef EHWPOISON + case EHWPOISON: + return "EHWPOISON"; +#endif +#ifdef EPROCLIM + case EPROCLIM: + return "EPROCLIM"; +#endif +#ifdef EBADRPC + case EBADRPC: + return "EBADRPC"; +#endif +#ifdef ERPCMISMATCH + case ERPCMISMATCH: + return "ERPCMISMATCH"; +#endif +#ifdef EPROGUNAVAIL + case EPROGUNAVAIL: + return "EPROGUNAVAIL"; +#endif +#ifdef EPROGMISMATCH + case EPROGMISMATCH: + return "EPROGMISMATCH"; +#endif +#ifdef EPROCUNAVAIL + case EPROCUNAVAIL: + return "EPROCUNAVAIL"; +#endif +#ifdef EFTYPE + case EFTYPE: + return "EFTYPE"; +#endif +#ifdef EAUTH + case EAUTH: + return "EAUTH"; +#endif +#ifdef ENEEDAUTH + case ENEEDAUTH: + return "ENEEDAUTH"; +#endif +#ifdef EPWROFF + case EPWROFF: + return "EPWROFF"; +#endif +#ifdef EDEVERR + case EDEVERR: + return "EDEVERR"; +#endif +#ifdef EBADEXEC + case EBADEXEC: + return "EBADEXEC"; +#endif +#ifdef EBADARCH + case EBADARCH: + return "EBADARCH"; +#endif +#ifdef ESHLIBVERS + case ESHLIBVERS: + return "ESHLIBVERS"; +#endif +#ifdef EBADMACHO + case EBADMACHO: + return "EBADMACHO"; +#endif +#ifdef ENOATTR + case ENOATTR: + return "ENOATTR"; +#endif +#ifdef EQFULL + case EQFULL: + return "EQFULL"; +#endif + default: + char* str = strerror(nErrno); + return std::to_string(nErrno) + " (" + std::string(str) + ")"; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/uunxapi.hxx b/sal/osl/unx/uunxapi.hxx new file mode 100644 index 000000000..9f792765c --- /dev/null +++ b/sal/osl/unx/uunxapi.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_UUNXAPI_HXX +#define INCLUDED_SAL_OSL_UNX_UUNXAPI_HXX + +#include <sal/config.h> + +#include <string_view> + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <rtl/ustring.h> + +#include <rtl/ustring.hxx> + +int stat_c(const char *cpPath, struct stat* buf); + +int lstat_c(const char *cpPath, struct stat* buf); + +int mkdir_c(OString const & path, mode_t mode); + +int open_c(const OString& path, int oflag, int mode); + +int utime_c(const char *cpPath, struct utimbuf *times); + +int ftruncate_with_name(int fd, sal_uInt64 uSize, const OString& path); + +namespace osl +{ + OString OUStringToOString(std::u16string_view s); + + int access(const OString& strPath, int mode); + + /*********************************** + osl::realpath + + @descr + The return value differs from the + realpath function + + @returns sal_True on success else + sal_False + + @see realpath + **********************************/ + + bool realpath( + const OUString& ustrFileName, + OUString& ustrResolvedName); + + bool realpath( + const OString& strFileName, + OString& strResolvedName); + + int lstat(const OUString& ustrPath, struct stat& buf); + + int lstat(const OString& strPath, struct stat& buf); + + int mkdir(const OString& aPath, mode_t aMode); +} // end namespace osl + +#endif // INCLUDED_SAL_OSL_UNX_UUNXAPI_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/uunxapi.mm b/sal/osl/unx/uunxapi.mm new file mode 100644 index 000000000..5a7e17f34 --- /dev/null +++ b/sal/osl/unx/uunxapi.mm @@ -0,0 +1 @@ +#include "uunxapi.cxx" diff --git a/sal/osl/w32/backtrace.cxx b/sal/osl/w32/backtrace.cxx new file mode 100644 index 000000000..d9231de44 --- /dev/null +++ b/sal/osl/w32/backtrace.cxx @@ -0,0 +1,92 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <limits> +#include <memory> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <process.h> +#include <iostream> +#define OPTIONAL +#include <DbgHelp.h> + +#include <rtl/ustrbuf.hxx> +#include <sal/backtrace.hxx> + +#include <backtraceasstring.hxx> + +namespace { + +template<typename T> T clampToULONG(T n) { + auto const maxUlong = std::numeric_limits<ULONG>::max(); + return n > maxUlong ? static_cast<T>(maxUlong) : n; +} + +} + +OUString osl::detail::backtraceAsString(sal_uInt32 maxDepth) +{ + std::unique_ptr<sal::BacktraceState> backtrace = sal::backtrace_get( maxDepth ); + return sal::backtrace_to_string( backtrace.get()); +} + +std::unique_ptr<sal::BacktraceState> sal::backtrace_get(sal_uInt32 maxDepth) +{ + assert(maxDepth != 0); + maxDepth = clampToULONG(maxDepth); + + auto pStack = new void *[maxDepth]; + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb204633.aspx + // "CaptureStackBackTrace function" claims that you "can capture up to + // MAXUSHORT frames", and on Windows Server 2003 and Windows XP it even + // "must be less than 63", but assume that a too large input value is + // clamped internally, instead of resulting in an error: + int nFrames = CaptureStackBackTrace( 0, static_cast<ULONG>(maxDepth), pStack, nullptr ); + + return std::unique_ptr<BacktraceState>(new BacktraceState{ pStack, nFrames }); +} + +OUString sal::backtrace_to_string(BacktraceState* backtraceState) +{ + HANDLE hProcess = GetCurrentProcess(); + // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize + // says to not initialize more than once. + [[maybe_unused]] static bool bInitialized = SymInitialize(hProcess, nullptr, false); + SymRefreshModuleList(hProcess); + SYMBOL_INFO * pSymbol; + pSymbol = static_cast<SYMBOL_INFO *>(calloc( sizeof( SYMBOL_INFO ) + 1024 * sizeof( char ), 1 )); + assert(pSymbol); + pSymbol->MaxNameLen = 1024 - 1; + pSymbol->SizeOfStruct = sizeof( SYMBOL_INFO ); + + auto nFrames = backtraceState->nDepth; + OUStringBuffer aBuf; + for( int i = 0; i < nFrames; i++ ) + { + SymFromAddr( hProcess, reinterpret_cast<DWORD64>(backtraceState->buffer[ i ]), nullptr, pSymbol ); + aBuf.append( static_cast<sal_Int32>(nFrames - i - 1) ); + aBuf.append( ": " ); + aBuf.appendAscii( pSymbol->Name ); + aBuf.append( " - 0x" ); + aBuf.append( static_cast<sal_Int64>(pSymbol->Address), 16 ); + aBuf.append( "\n" ); + } + + free( pSymbol ); + + return aBuf.makeStringAndClear(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/conditn.cxx b/sal/osl/w32/conditn.cxx new file mode 100644 index 000000000..6bc6d9d3e --- /dev/null +++ b/sal/osl/w32/conditn.cxx @@ -0,0 +1,114 @@ +/* -*- 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 "system.h" + +#include <osl/conditn.h> +#include <osl/diagnose.h> +#include <osl/time.h> + +/* + under WIN32, we use the void* oslCondition + as a WIN32 HANDLE (which is also a 32-bit value) +*/ + +oslCondition SAL_CALL osl_createCondition(void) +{ + oslCondition Condition; + + Condition= reinterpret_cast<oslCondition>(CreateEventW(nullptr, /* no security */ + true, /* manual reset */ + false, /* initial state not signaled */ + nullptr)); /* automatic name */ + + return Condition; + +} + +void SAL_CALL osl_destroyCondition(oslCondition Condition) +{ + if(Condition) + OSL_VERIFY(CloseHandle(Condition)); +} + +sal_Bool SAL_CALL osl_setCondition(oslCondition Condition) +{ + OSL_ASSERT(Condition); + + return SetEvent(reinterpret_cast<HANDLE>(Condition)) != FALSE; +} + +sal_Bool SAL_CALL osl_resetCondition(oslCondition Condition) +{ + OSL_ASSERT(Condition); + + return ResetEvent(reinterpret_cast<HANDLE>(Condition)) != FALSE; +} + +oslConditionResult SAL_CALL osl_waitCondition(oslCondition Condition, + const TimeValue* pTimeout) +{ + DWORD timeout; + + OSL_ASSERT(Condition); + + if (pTimeout) + timeout = pTimeout->Seconds * 1000 + pTimeout->Nanosec / 1000000L; + else + timeout = INFINITE; + + /* It's necessary to process SendMessage calls to the current thread to give other threads + access to COM objects instantiated in this thread */ + + while ( true ) + { + /* Only wake up if a SendMessage call to the threads message loop is detected */ + switch( MsgWaitForMultipleObjects( 1, reinterpret_cast<HANDLE *>(&Condition), FALSE, timeout, QS_SENDMESSAGE ) ) + { + case WAIT_OBJECT_0 + 1: + { + MSG msg; + + /* We Must not dispatch the message. PM_NOREMOVE leaves the message queue untouched + but dispatches SendMessage calls automatically */ + + PeekMessageW( &msg, nullptr, 0, 0, PM_NOREMOVE ); + } + break; + + case WAIT_OBJECT_0: + return osl_cond_result_ok; + + case WAIT_TIMEOUT: + return osl_cond_result_timeout; + + default: + return osl_cond_result_error; + } + } +} + +sal_Bool SAL_CALL osl_checkCondition(oslCondition Condition) +{ + OSL_ASSERT(Condition); + + return WaitForSingleObject(reinterpret_cast<HANDLE>(Condition), 0) == WAIT_OBJECT_0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/dllentry.cxx b/sal/osl/w32/dllentry.cxx new file mode 100644 index 000000000..81139a058 --- /dev/null +++ b/sal/osl/w32/dllentry.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <systools/win32/uwinapi.h> +#include <tlhelp32.h> +#include <rpc.h> +#include <winsock.h> +#ifdef _DEBUG +#include <crtdbg.h> +#endif +#include <osl/diagnose.h> +#include <sal/types.h> +#include <float.h> + +#include <osl/mutex.h> +#include <osl/thread.h> + +#include "file_url.hxx" +#include <rtllifecycle.h> + +#include "thread.hxx" + +/* +This is needed because DllMain is called after static constructors. A DLL's +startup and shutdown sequence looks like this: + +_pRawDllMain() +_CRT_INIT() +DllMain() +... +DllMain() +_CRT_INIT() +_pRawDllMain() + +*/ + +extern "C" { + +static BOOL WINAPI RawDllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ); +BOOL (WINAPI *_pRawDllMain)(HINSTANCE, DWORD, LPVOID) = RawDllMain; + +} + +static BOOL WINAPI RawDllMain( HINSTANCE, DWORD fdwReason, LPVOID ) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + { +#ifdef _DEBUG + WCHAR buf[64]; + DWORD const res = GetEnvironmentVariableW(L"SAL_NO_ASSERT_DIALOGS", buf, SAL_N_ELEMENTS(buf)); + if (res && res < SAL_N_ELEMENTS(buf)) + { + // disable the dialog on abort() + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, (_CRTDBG_MODE_DEBUG|_CRTDBG_MODE_FILE)); + // not sure which assertions this affects + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, (_CRTDBG_MODE_DEBUG|_CRTDBG_MODE_FILE)); + // disable the dialog on assert(false) + _set_error_mode(_OUT_TO_STDERR); + } +#endif + +#if OSL_DEBUG_LEVEL < 2 + /* Suppress file error messages from system like "Floppy A: not inserted" */ + SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS ); +#endif + + //We disable floating point exceptions. This is the usual state at program startup + //but on Windows 98 and ME this is not always the case. + _control87(_MCW_EM, _MCW_EM); + break; + } + + case DLL_PROCESS_DETACH: + WSACleanup( ); + + /* + + On a product build memory management finalization might + cause a crash without assertion (assertions off) if heap is + corrupted. But a crash report won't help here because at + this point all other threads have been terminated and only + ntdll is on the stack. No chance to find the reason for the + corrupted heap if so. + + So annoying the user with a crash report is completely useless. + + */ + +#ifndef DBG_UTIL + __try +#endif + { + /* cleanup locale hashtable */ + rtl_locale_fini(); + + /* finalize memory management */ + rtl_cache_fini(); + rtl_arena_fini(); + } +#ifndef DBG_UTIL + __except( EXCEPTION_EXECUTE_HANDLER ) + { + } +#endif + break; + } + + return TRUE; +} + +static DWORD GetParentProcessId() +{ + DWORD dwParentProcessId = 0; + HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + + if ( IsValidHandle( hSnapshot ) ) + { + PROCESSENTRY32 pe; + bool fSuccess; + + ZeroMemory( &pe, sizeof(pe) ); + pe.dwSize = sizeof(pe); + fSuccess = Process32First( hSnapshot, &pe ); + + while( fSuccess ) + { + if ( GetCurrentProcessId() == pe.th32ProcessID ) + { + dwParentProcessId = pe.th32ParentProcessID; + break; + } + + fSuccess = Process32Next( hSnapshot, &pe ); + } + + CloseHandle( hSnapshot ); + } + + return dwParentProcessId; +} + +static DWORD WINAPI ParentMonitorThreadProc( LPVOID lpParam ) +{ + DWORD_PTR dwParentProcessId = reinterpret_cast<DWORD_PTR>(lpParam); + + HANDLE hParentProcess = OpenProcess( SYNCHRONIZE, FALSE, dwParentProcessId ); + + osl_setThreadName("headless ParentMonitorThread"); + + if ( IsValidHandle( hParentProcess ) ) + { + if ( WAIT_OBJECT_0 == WaitForSingleObject( hParentProcess, INFINITE ) ) + { + TerminateProcess( GetCurrentProcess(), 0 ); + } + CloseHandle( hParentProcess ); + } + return 0; +} + +extern "C" +BOOL WINAPI DllMain( HINSTANCE, DWORD fdwReason, LPVOID ) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + { + WCHAR szBuffer[64]; + + // This code will attach the process to its parent process + // if the parent process had set the environment variable. + // The corresponding code (setting the environment variable) + // is desktop/win32/source/officeloader.cxx + + DWORD dwResult = GetEnvironmentVariableW( L"ATTACHED_PARENT_PROCESSID", szBuffer, SAL_N_ELEMENTS(szBuffer) ); + + if ( dwResult && dwResult < SAL_N_ELEMENTS(szBuffer) ) + { + DWORD dwThreadId = 0; + + DWORD_PTR dwParentProcessId = static_cast<DWORD_PTR>(_wtol( szBuffer )); + + if ( dwParentProcessId && GetParentProcessId() == dwParentProcessId ) + { + // No error check, it works or it does not + // Thread should only be started for headless mode, see desktop/win32/source/officeloader.cxx + HANDLE hThread + = CreateThread(nullptr, 0, ParentMonitorThreadProc, + reinterpret_cast<LPVOID>(dwParentProcessId), 0, &dwThreadId); + // Note: calling CreateThread in DllMain is discouraged + // but this is only done in the headless mode and in + // that case no other threads should be running at startup + // when sal3.dll is loaded; also there is no + // synchronization with the spawned thread, so there + // does not appear to be a real risk of deadlock here + if (hThread) + CloseHandle(hThread); + } + } + + return TRUE; + } + + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + osl_callThreadKeyCallbackOnThreadDetach( ); + break; + } + + return TRUE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file-impl.hxx b/sal/osl/w32/file-impl.hxx new file mode 100644 index 000000000..3f1d2136d --- /dev/null +++ b/sal/osl/w32/file-impl.hxx @@ -0,0 +1,25 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_W32_FILE_IMPL_HXX +#define INCLUDED_SAL_OSL_W32_FILE_IMPL_HXX + +#include <sal/config.h> + +#include <osl/file.h> +#include <sal/types.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +extern "C" oslFileHandle osl_createFileHandleFromOSHandle(HANDLE hFile, sal_uInt32 uFlags); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/w32/file.cxx b/sal/osl/w32/file.cxx new file mode 100644 index 000000000..be921aede --- /dev/null +++ b/sal/osl/w32/file.cxx @@ -0,0 +1,1121 @@ +/* -*- 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 <systools/win32/uwinapi.h> + +#include <osl/file.hxx> +#include <rtl/alloc.h> +#include <rtl/byteseq.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include "file-impl.hxx" +#include "file_url.hxx" +#include "file_error.hxx" + +#include <atomic> +#include <cassert> +#include <algorithm> +#include <limits> + +#ifdef max /* conflict w/ std::numeric_limits<T>::max() */ +#undef max +#endif +#ifdef min +#undef min +#endif + +namespace { + +/** State + */ +enum class StateBits +{ + Seekable = 1, /*< open() sets, iff regular file */ + Readable = 2, /*< open() sets, read() requires */ + Writeable = 4, /*< open() sets, write() requires */ + Modified = 8 /* write() sets, flush() resets */ +}; + +} + +template<> struct o3tl::typed_flags<StateBits>: o3tl::is_typed_flags<StateBits, 0xF> {}; + +namespace { + +/** File handle implementation. +*/ +struct FileHandle_Impl +{ + CRITICAL_SECTION m_mutex; + HANDLE m_hFile; + + StateBits m_state; + + sal_uInt64 m_size; /*< file size */ + LONGLONG m_offset; /*< physical offset from begin of file */ + // m_filepos is hit hard in some situations, where the overhead of a mutex starts to show up, so use an atomic + std::atomic<LONGLONG> m_filepos; /*< logical offset from begin of file */ + + LONGLONG 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; + + explicit FileHandle_Impl (HANDLE hFile); + ~FileHandle_Impl(); + + static SIZE_T getpagesize(); + + sal_uInt64 getPos() const; + oslFileError setPos (sal_uInt64 uPos); + + sal_uInt64 getSize() const; + oslFileError setSize (sal_uInt64 uPos); + + oslFileError readAt( + LONGLONG nOffset, + void * pBuffer, + DWORD nBytesRequested, + sal_uInt64 * pBytesRead); + + oslFileError writeAt( + LONGLONG nOffset, + void const * pBuffer, + DWORD nBytesToWrite, + sal_uInt64 * pBytesWritten); + + oslFileError readFileAt( + LONGLONG nOffset, + void * pBuffer, + sal_uInt64 uBytesRequested, + sal_uInt64 * pBytesRead); + + oslFileError writeFileAt( + LONGLONG nOffset, + void const * pBuffer, + sal_uInt64 uBytesToWrite, + sal_uInt64 * pBytesWritten); + + oslFileError readLineAt( + LONGLONG 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(); + + /** Guard. + */ + class Guard + { + LPCRITICAL_SECTION m_mutex; + + public: + explicit Guard(LPCRITICAL_SECTION pMutex); + ~Guard(); + }; +}; + +} + +FileHandle_Impl::Guard::Guard(LPCRITICAL_SECTION pMutex) + : m_mutex (pMutex) +{ + assert(pMutex); + ::EnterCriticalSection (m_mutex); +} + +FileHandle_Impl::Guard::~Guard() +{ + ::LeaveCriticalSection (m_mutex); +} + +FileHandle_Impl::FileHandle_Impl(HANDLE hFile) + : m_hFile (hFile), + m_state (StateBits::Readable | StateBits::Writeable), + m_size (0), + m_offset (0), + m_filepos (0), + m_bufptr (-1), + m_buflen (0), + m_bufsiz (getpagesize()), + m_buffer (nullptr) +{ + ::InitializeCriticalSection (&m_mutex); + m_buffer = static_cast<sal_uInt8 *>(calloc(m_bufsiz, 1)); +} + +FileHandle_Impl::~FileHandle_Impl() +{ + free(m_buffer); + m_buffer = nullptr; + ::DeleteCriticalSection (&m_mutex); +} + +SIZE_T FileHandle_Impl::getpagesize() +{ + SYSTEM_INFO info; + ::GetSystemInfo(&info); + return sal::static_int_cast< SIZE_T >(info.dwPageSize); +} + +sal_uInt64 FileHandle_Impl::getPos() const +{ + return sal::static_int_cast< sal_uInt64 >(m_filepos.load()); +} + +oslFileError FileHandle_Impl::setPos(sal_uInt64 uPos) +{ + m_filepos = sal::static_int_cast< LONGLONG >(uPos); + return osl_File_E_None; +} + +sal_uInt64 FileHandle_Impl::getSize() const +{ + LONGLONG bufend = std::max(LONGLONG(0), m_bufptr) + m_buflen; + return std::max(m_size, sal::static_int_cast< sal_uInt64 >(bufend)); +} + +oslFileError FileHandle_Impl::setSize(sal_uInt64 uSize) +{ + LARGE_INTEGER nDstPos; nDstPos.QuadPart = sal::static_int_cast< LONGLONG >(uSize); + if (!::SetFilePointerEx(m_hFile, nDstPos, nullptr, FILE_BEGIN)) + return oslTranslateFileError(GetLastError()); + + if (!::SetEndOfFile(m_hFile)) + return oslTranslateFileError(GetLastError()); + m_size = uSize; + + nDstPos.QuadPart = m_offset; + if (!::SetFilePointerEx(m_hFile, nDstPos, nullptr, FILE_BEGIN)) + return oslTranslateFileError(GetLastError()); + + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::readAt( + LONGLONG nOffset, + void * pBuffer, + DWORD nBytesRequested, + sal_uInt64 * pBytesRead) +{ + SAL_WARN_IF(!(m_state & StateBits::Seekable), "sal.osl", "FileHandle_Impl::readAt(): not seekable"); + if (!(m_state & StateBits::Seekable)) + return osl_File_E_SPIPE; + + SAL_WARN_IF(!(m_state & StateBits::Readable), "sal.osl", "FileHandle_Impl::readAt(): not readable"); + if (!(m_state & StateBits::Readable)) + return osl_File_E_BADF; + + if (nOffset != m_offset) + { + LARGE_INTEGER liOffset; liOffset.QuadPart = nOffset; + if (!::SetFilePointerEx(m_hFile, liOffset, nullptr, FILE_BEGIN)) + return oslTranslateFileError(GetLastError()); + m_offset = nOffset; + } + + DWORD dwDone = 0; + if (!::ReadFile(m_hFile, pBuffer, nBytesRequested, &dwDone, nullptr)) + return oslTranslateFileError(GetLastError()); + m_offset += dwDone; + + *pBytesRead = dwDone; + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::writeAt( + LONGLONG nOffset, + void const * pBuffer, + DWORD nBytesToWrite, + sal_uInt64 * pBytesWritten) +{ + SAL_WARN_IF(!(m_state & StateBits::Seekable), "sal.osl", "FileHandle_Impl::writeAt(): not seekable"); + if (!(m_state & StateBits::Seekable)) + return osl_File_E_SPIPE; + + SAL_WARN_IF(!(m_state & StateBits::Writeable), "sal.osl", "FileHandle_Impl::writeAt(): not writeable"); + if (!(m_state & StateBits::Writeable)) + return osl_File_E_BADF; + + if (nOffset != m_offset) + { + LARGE_INTEGER liOffset; liOffset.QuadPart = nOffset; + if (!::SetFilePointerEx (m_hFile, liOffset, nullptr, FILE_BEGIN)) + return oslTranslateFileError(GetLastError()); + m_offset = nOffset; + } + + DWORD dwDone = 0; + if (!::WriteFile(m_hFile, pBuffer, nBytesToWrite, &dwDone, nullptr)) + return oslTranslateFileError(GetLastError()); + m_offset += dwDone; + + m_size = std::max(m_size, sal::static_int_cast< sal_uInt64 >(m_offset)); + + *pBytesWritten = dwDone; + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::readFileAt( + LONGLONG nOffset, + void * pBuffer, + sal_uInt64 uBytesRequested, + sal_uInt64 * pBytesRead) +{ + static sal_uInt64 const g_limit_dword = std::numeric_limits< DWORD >::max(); + if (g_limit_dword < uBytesRequested) + return osl_File_E_OVERFLOW; + DWORD nBytesRequested = sal::static_int_cast< DWORD >(uBytesRequested); + + if (!(m_state & StateBits::Seekable)) + { + // not seekable (pipe) + DWORD dwDone = 0; + if (!::ReadFile(m_hFile, pBuffer, nBytesRequested, &dwDone, nullptr)) + return oslTranslateFileError(GetLastError()); + *pBytesRead = dwDone; + return osl_File_E_None; + } + else if (!m_buffer) + { + // not buffered + return readAt (nOffset, pBuffer, nBytesRequested, pBytesRead); + } + else + { + sal_uInt8 * buffer = static_cast< sal_uInt8* >(pBuffer); + for (*pBytesRead = 0; nBytesRequested > 0;) + { + LONGLONG 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; + + nBytesRequested -= sal::static_int_cast< DWORD >(uDone); + *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 = sal::static_int_cast< SIZE_T >(uDone); + } + if (bufpos >= m_buflen) + { + // end of file + return osl_File_E_None; + } + + SIZE_T const bytes = std::min(m_buflen - bufpos, static_cast<SIZE_T>(nBytesRequested)); + memcpy(&(buffer[*pBytesRead]), &(m_buffer[bufpos]), bytes); + nBytesRequested -= bytes; + *pBytesRead += bytes; + nOffset += bytes; + } + return osl_File_E_None; + } +} + +oslFileError FileHandle_Impl::writeFileAt( + LONGLONG nOffset, + void const * pBuffer, + sal_uInt64 uBytesToWrite, + sal_uInt64 * pBytesWritten) +{ + static sal_uInt64 const g_limit_dword = std::numeric_limits< DWORD >::max(); + if (g_limit_dword < uBytesToWrite) + return osl_File_E_OVERFLOW; + DWORD nBytesToWrite = sal::static_int_cast< DWORD >(uBytesToWrite); + + if (!(m_state & StateBits::Seekable)) + { + // not seekable (pipe) + DWORD dwDone = 0; + if (!::WriteFile(m_hFile, pBuffer, nBytesToWrite, &dwDone, nullptr)) + return oslTranslateFileError(GetLastError()); + *pBytesWritten = dwDone; + return osl_File_E_None; + } + else if (!m_buffer) + { + // not buffered + return writeAt(nOffset, pBuffer, nBytesToWrite, pBytesWritten); + } + else + { + sal_uInt8 const * buffer = static_cast< sal_uInt8 const* >(pBuffer); + for (*pBytesWritten = 0; nBytesToWrite > 0;) + { + LONGLONG 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; + + nBytesToWrite -= sal::static_int_cast< DWORD >(uDone); + *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 = sal::static_int_cast< SIZE_T >(uDone); + } + + SIZE_T const bytes = std::min(m_bufsiz - bufpos, static_cast<SIZE_T>(nBytesToWrite)); + memcpy(&(m_buffer[bufpos]), &(buffer[*pBytesWritten]), bytes); + nBytesToWrite -= bytes; + *pBytesWritten += bytes; + nOffset += bytes; + + m_buflen = std::max(m_buflen, bufpos + bytes); + m_state |= StateBits::Modified; + } + return osl_File_E_None; + } +} + +oslFileError FileHandle_Impl::readLineAt( + LONGLONG nOffset, + sal_Sequence ** ppSequence, + sal_uInt64 * pBytesRead) +{ + oslFileError result = osl_File_E_None; + + LONGLONG 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 = sal::static_int_cast< SIZE_T >(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 = sal::static_int_cast< SIZE_T >(nOffset - m_bufptr), curpos = bufpos, dstpos = 0; + int state = (bufpos >= m_buflen) ? LINE_STATE_LF : LINE_STATE_BEGIN; + + for (; state != LINE_STATE_LF;) + { + if (curpos >= m_buflen) + { + /* buffer examined */ + if (curpos > bufpos) // actually, curpos can't become less than bufpos, so != could do + { + /* 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 = sal::static_int_cast< SIZE_T >(uDone); + } + + bufpos = sal::static_int_cast< SIZE_T >(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) + { + /* store (and eat) the newline char */ + m_buffer[curpos] = 0x0A; + 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) + { + /* 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 & StateBits::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 &= ~StateBits::Modified; + } + return result; +} + +extern "C" oslFileHandle osl_createFileHandleFromOSHandle( + HANDLE hFile, + sal_uInt32 uFlags) +{ + if (!IsValidHandle(hFile)) + return nullptr; // EINVAL + + FileHandle_Impl * pImpl = new FileHandle_Impl(hFile); + + /* check for regular file */ + if (GetFileType(hFile) == FILE_TYPE_DISK) + { + /* mark seekable */ + pImpl->m_state |= StateBits::Seekable; + + /* init current size */ + LARGE_INTEGER uSize = { { 0, 0 } }; + (void) ::GetFileSizeEx(hFile, &uSize); + pImpl->m_size = (sal::static_int_cast<sal_uInt64>(uSize.HighPart) << 32) + uSize.LowPart; + } + + if (!(uFlags & osl_File_OpenFlag_Read)) + pImpl->m_state &= ~StateBits::Readable; + if (!(uFlags & osl_File_OpenFlag_Write)) + pImpl->m_state &= ~StateBits::Writeable; + + SAL_WARN_IF( + !((uFlags & osl_File_OpenFlag_Read) || (uFlags & osl_File_OpenFlag_Write)), + "sal.osl", + "osl_createFileHandleFromOSHandle(): missing read/write access flags"); + return static_cast<oslFileHandle>(pImpl); +} + +oslFileError SAL_CALL osl_openFile( + rtl_uString * strPath, + oslFileHandle * pHandle, + sal_uInt32 uFlags) +{ + OUString strSysPath; + oslFileError result = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + if (result != osl_File_E_None) + return result; + + // tdf126742 use FILE_SHARE_WRITE to get closer to non-Windows platform behaviour, + // for details and discussion see task please + DWORD dwAccess = GENERIC_READ, dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE, dwCreation = 0; + + if (uFlags & osl_File_OpenFlag_Write) + dwAccess |= GENERIC_WRITE; + + if (uFlags & osl_File_OpenFlag_NoLock) + dwShare |= FILE_SHARE_DELETE; + + if (uFlags & osl_File_OpenFlag_Create) + dwCreation |= CREATE_NEW; + else + dwCreation |= OPEN_EXISTING; + + HANDLE hFile = CreateFileW( + o3tl::toW(strSysPath.getStr()), + dwAccess, dwShare, nullptr, dwCreation, 0, nullptr); + + // @@@ ERROR HANDLING @@@ + if (!IsValidHandle(hFile)) + result = oslTranslateFileError(GetLastError()); + + *pHandle = osl_createFileHandleFromOSHandle(hFile, uFlags | osl_File_OpenFlag_Read); + + return result; +} + +oslFileError SAL_CALL osl_syncFile(oslFileHandle Handle) +{ + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile)) + return osl_File_E_INVAL; + + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + + oslFileError result = pImpl->syncFile(); + if (result != osl_File_E_None) + return result; + + if (!FlushFileBuffers(pImpl->m_hFile)) + return oslTranslateFileError(GetLastError()); + + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_closeFile(oslFileHandle Handle) +{ + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile)) + return osl_File_E_INVAL; + + ::EnterCriticalSection(&(pImpl->m_mutex)); + + oslFileError result = pImpl->syncFile(); + if (result != osl_File_E_None) + { + /* ignore double failure */ + (void)::CloseHandle(pImpl->m_hFile); + } + else if (!::CloseHandle(pImpl->m_hFile)) + { + /* translate error code */ + result = oslTranslateFileError(GetLastError()); + } + + ::LeaveCriticalSection(&(pImpl->m_mutex)); + delete pImpl; + return result; +} + +namespace { + +// coverity[result_independent_of_operands] - crossplatform requirement +template<typename T> bool exceedsMaxSIZE_T(T n) +{ return n > std::numeric_limits< SIZE_T >::max(); } + +} + +oslFileError SAL_CALL osl_mapFile( + oslFileHandle Handle, + void** ppAddr, + sal_uInt64 uLength, + sal_uInt64 uOffset, + sal_uInt32 uFlags) +{ + struct FileMapping + { + HANDLE m_handle; + + explicit FileMapping(HANDLE hMap) + : m_handle(hMap) + {} + + ~FileMapping() + { + (void)::CloseHandle(m_handle); + } + }; + + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!ppAddr)) + return osl_File_E_INVAL; + *ppAddr = nullptr; + + if (exceedsMaxSIZE_T(uLength)) + return osl_File_E_OVERFLOW; + SIZE_T const nLength = sal::static_int_cast< SIZE_T >(uLength); + + FileMapping aMap(::CreateFileMappingW(pImpl->m_hFile, nullptr, SEC_COMMIT | PAGE_READONLY, 0, 0, nullptr)); + if (!IsValidHandle(aMap.m_handle)) + return oslTranslateFileError(GetLastError()); + + DWORD const dwOffsetHi = sal::static_int_cast<DWORD>(uOffset >> 32); + DWORD const dwOffsetLo = sal::static_int_cast<DWORD>(uOffset & 0xFFFFFFFF); + + *ppAddr = ::MapViewOfFile(aMap.m_handle, FILE_MAP_READ, dwOffsetHi, dwOffsetLo, nLength); + if (!*ppAddr) + return oslTranslateFileError(GetLastError()); + + if (uFlags & osl_File_MapFlag_RandomAccess) + { + // Determine memory pagesize. + SYSTEM_INFO info; + ::GetSystemInfo(&info); + DWORD const dwPageSize = info.dwPageSize; + + /* + * Pagein, touching first byte of each memory page. + * Note: volatile disables optimizing the loop away. + */ + BYTE volatile * pData(static_cast<BYTE*>(*ppAddr)); + SIZE_T nSize(nLength); + + while (nSize > dwPageSize) + { + pData[0]; + pData += dwPageSize; + nSize -= dwPageSize; + } + if (nSize > 0) + { + pData[0]; + } + } + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_unmapFile(void* pAddr, sal_uInt64 /* uLength */) +{ + if (!pAddr) + return osl_File_E_INVAL; + + if (!::UnmapViewOfFile(pAddr)) + return oslTranslateFileError(GetLastError()); + + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_unmapMappedFile(oslFileHandle /* Handle */, void* pAddr, sal_uInt64 uLength) +{ + return osl_unmapFile(pAddr, uLength); +} + +oslFileError +SAL_CALL osl_readLine( + oslFileHandle Handle, + sal_Sequence ** ppSequence) +{ + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!ppSequence)) + return osl_File_E_INVAL; + sal_uInt64 uBytesRead = 0; + + // read at current filepos; filepos += uBytesRead; + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + oslFileError result = pImpl->readLineAt( + pImpl->m_filepos, ppSequence, &uBytesRead); + if (result == osl_File_E_None) + pImpl->m_filepos += 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) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesRead)) + return osl_File_E_INVAL; + + // read at current filepos; filepos += *pBytesRead; + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + oslFileError result = pImpl->readFileAt( + pImpl->m_filepos, pBuffer, uBytesRequested, pBytesRead); + if (result == osl_File_E_None) + pImpl->m_filepos += *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) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesWritten)) + return osl_File_E_INVAL; + + // write at current filepos; filepos += *pBytesWritten; + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + oslFileError result = pImpl->writeFileAt( + pImpl->m_filepos, pBuffer, uBytesToWrite, pBytesWritten); + if (result == osl_File_E_None) + pImpl->m_filepos += *pBytesWritten; + return result; +} + +LONGLONG const g_limit_longlong = std::numeric_limits< LONGLONG >::max(); + +namespace { + +// coverity[result_independent_of_operands] - crossplatform requirement +template<typename T> bool exceedsMaxLONGLONG(T n) +{ return n > g_limit_longlong; } + +template<typename T> bool exceedsMinLONGLONG(T n) +{ return n < std::numeric_limits<LONGLONG>::min(); } + +} + +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) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesRead)) + return osl_File_E_INVAL; + if (!(pImpl->m_state & StateBits::Seekable)) + return osl_File_E_SPIPE; + + if (exceedsMaxLONGLONG(uOffset)) + return osl_File_E_OVERFLOW; + LONGLONG const nOffset = sal::static_int_cast< LONGLONG >(uOffset); + + // read at specified fileptr + FileHandle_Impl::Guard lock (&(pImpl->m_mutex)); + return pImpl->readFileAt(nOffset, pBuffer, uBytesRequested, 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) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesWritten)) + return osl_File_E_INVAL; + if (!(pImpl->m_state & StateBits::Seekable)) + return osl_File_E_SPIPE; + + if (exceedsMaxLONGLONG(uOffset)) + return osl_File_E_OVERFLOW; + LONGLONG const nOffset = sal::static_int_cast< LONGLONG >(uOffset); + + // write at specified fileptr + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + return pImpl->writeFileAt(nOffset, pBuffer, uBytesToWrite, pBytesWritten); +} + +oslFileError SAL_CALL osl_isEndOfFile(oslFileHandle Handle, sal_Bool *pIsEOF) +{ + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!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) || !IsValidHandle(pImpl->m_hFile) || (!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) || !IsValidHandle(pImpl->m_hFile)) + return osl_File_E_INVAL; + + if (exceedsMaxLONGLONG(uOffset) || exceedsMinLONGLONG(uOffset)) + return osl_File_E_OVERFLOW; + LONGLONG nPos = 0, nOffset = sal::static_int_cast< LONGLONG >(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< LONGLONG >(pImpl->getPos()); + if ((nOffset < 0) && (nPos < -1*nOffset)) + return osl_File_E_INVAL; + assert(nPos >= 0); + if (nOffset > g_limit_longlong - nPos) + return osl_File_E_OVERFLOW; + break; + + case osl_Pos_End: + nPos = sal::static_int_cast< LONGLONG >(pImpl->getSize()); + if ((nOffset < 0) && (nPos < -1*nOffset)) + return osl_File_E_INVAL; + assert(nPos >= 0); + if (nOffset > g_limit_longlong - nPos) + return osl_File_E_OVERFLOW; + break; + + default: + return osl_File_E_INVAL; + } + + return pImpl->setPos(nPos + nOffset); +} + +oslFileError SAL_CALL osl_getFileSize(oslFileHandle Handle, sal_uInt64 *pSize) +{ + FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle); + + if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!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) || !IsValidHandle(pImpl->m_hFile)) + return osl_File_E_INVAL; + if (!(pImpl->m_state & StateBits::Writeable)) + return osl_File_E_BADF; + + if (exceedsMaxLONGLONG(uSize)) + return osl_File_E_OVERFLOW; + + FileHandle_Impl::Guard lock(&(pImpl->m_mutex)); + oslFileError result = pImpl->syncFile(); + if (result != osl_File_E_None) + return result; + pImpl->m_bufptr = -1; + pImpl->m_buflen = 0; + + return pImpl->setSize(uSize); +} + +oslFileError SAL_CALL osl_removeFile(rtl_uString* strPath) +{ + OUString strSysPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if (error == osl_File_E_None) + { + if (DeleteFileW(o3tl::toW(strSysPath.getStr()))) + error = osl_File_E_None; + else + error = oslTranslateFileError(GetLastError()); + } + return error; +} + +oslFileError SAL_CALL osl_copyFile(rtl_uString* strPath, rtl_uString *strDestPath) +{ + OUString strSysPath, strSysDestPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if (error == osl_File_E_None) + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false); + + if (error == osl_File_E_None) + { + LPCWSTR src = o3tl::toW(strSysPath.getStr()); + LPCWSTR dst = o3tl::toW(strSysDestPath.getStr()); + + if (CopyFileW(src, dst, FALSE)) + error = osl_File_E_None; + else + error = oslTranslateFileError(GetLastError()); + } + + return error; +} + +oslFileError SAL_CALL osl_moveFile(rtl_uString* strPath, rtl_uString *strDestPath) +{ + OUString strSysPath, strSysDestPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if (error == osl_File_E_None) + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false); + + if (error == osl_File_E_None) + { + LPCWSTR src = o3tl::toW(strSysPath.getStr()); + LPCWSTR dst = o3tl::toW(strSysDestPath.getStr()); + + if (MoveFileExW(src, dst, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING)) + error = osl_File_E_None; + else + error = oslTranslateFileError(GetLastError()); + } + + return error; +} + +oslFileError SAL_CALL osl_replaceFile(rtl_uString* strPath, rtl_uString* strDestPath) +{ + OUString strSysPath, strSysDestPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if (error == osl_File_E_None) + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false); + + if (error == osl_File_E_None) + { + LPCWSTR src = o3tl::toW(strSysPath.getStr()); + LPCWSTR dst = o3tl::toW(strSysDestPath.getStr()); + + if (!ReplaceFileW(dst, src, nullptr, + REPLACEFILE_WRITE_THROUGH | REPLACEFILE_IGNORE_MERGE_ERRORS + | REPLACEFILE_IGNORE_ACL_ERRORS, + nullptr, nullptr)) + { + DWORD dwError = GetLastError(); + if (dwError == ERROR_FILE_NOT_FOUND // no strDestPath file? + || dwError == ERROR_UNABLE_TO_MOVE_REPLACEMENT // e.g., files on different volumes + || dwError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 + || dwError == ERROR_UNABLE_TO_REMOVE_REPLACED) + error = osl_moveFile(strPath, strDestPath); + else + error = oslTranslateFileError(dwError); + } + } + + return error; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file_dirvol.cxx b/sal/osl/w32/file_dirvol.cxx new file mode 100644 index 000000000..f9d821605 --- /dev/null +++ b/sal/osl/w32/file_dirvol.cxx @@ -0,0 +1,1691 @@ +/* -*- 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 <systools/win32/uwinapi.h> + +#include "file_url.hxx" +#include "filetime.hxx" +#include "file_error.hxx" + +#include "path_helper.hxx" + +#include <rtl/alloc.h> +#include <rtl/ustring.hxx> +#include <rtl/character.hxx> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +const wchar_t UNC_PREFIX[] = L"\\\\"; +const wchar_t BACKSLASH = '\\'; +const wchar_t SLASH = '/'; + +BOOL TimeValueToFileTime(const TimeValue *cpTimeVal, FILETIME *pFTime) +{ + SYSTEMTIME BaseSysTime; + FILETIME BaseFileTime; + FILETIME FTime; + bool fSuccess = false; + + BaseSysTime.wYear = 1970; + BaseSysTime.wMonth = 1; + BaseSysTime.wDayOfWeek = 0; + BaseSysTime.wDay = 1; + BaseSysTime.wHour = 0; + BaseSysTime.wMinute = 0; + BaseSysTime.wSecond = 0; + BaseSysTime.wMilliseconds = 0; + + if (cpTimeVal==nullptr) + return fSuccess; + + if ( SystemTimeToFileTime(&BaseSysTime, &BaseFileTime) ) + { + __int64 timeValue; + + __int64 localTime = cpTimeVal->Seconds*__int64(10000000)+cpTimeVal->Nanosec/100; + osl::detail::setFiletime(FTime, localTime); + fSuccess = 0 <= (timeValue= osl::detail::getFiletime(BaseFileTime) + osl::detail::getFiletime(FTime)); + if (fSuccess) + osl::detail::setFiletime(*pFTime, timeValue); + } + return fSuccess; +} + +BOOL FileTimeToTimeValue(const FILETIME *cpFTime, TimeValue *pTimeVal) +{ + SYSTEMTIME BaseSysTime; + FILETIME BaseFileTime; + bool fSuccess = false; /* Assume failure */ + + BaseSysTime.wYear = 1970; + BaseSysTime.wMonth = 1; + BaseSysTime.wDayOfWeek = 0; + BaseSysTime.wDay = 1; + BaseSysTime.wHour = 0; + BaseSysTime.wMinute = 0; + BaseSysTime.wSecond = 0; + BaseSysTime.wMilliseconds = 0; + + if ( SystemTimeToFileTime(&BaseSysTime, &BaseFileTime) ) + { + __int64 Value; + + fSuccess = 0 <= (Value = osl::detail::getFiletime(*cpFTime) - osl::detail::getFiletime(BaseFileTime)); + + if ( fSuccess ) + { + pTimeVal->Seconds = static_cast<unsigned long>(Value / 10000000L); + pTimeVal->Nanosec = static_cast<unsigned long>((Value % 10000000L) * 100); + } + } + return fSuccess; +} + +namespace +{ + + struct Component + { + Component() : + begin_(nullptr), end_(nullptr) + {} + + bool isPresent() const + { return (static_cast<sal_IntPtr>(end_ - begin_) > 0); } + + const sal_Unicode* begin_; + const sal_Unicode* end_; + }; + + struct UNCComponents + { + Component server_; + Component share_; + Component resource_; + }; + + bool is_UNC_path(const sal_Unicode* path) + { return (0 == wcsncmp(UNC_PREFIX, o3tl::toW(path), SAL_N_ELEMENTS(UNC_PREFIX) - 1)); } + + void parse_UNC_path(const sal_Unicode* path, UNCComponents* puncc) + { + OSL_PRECOND(is_UNC_path(path), "Precondition violated: No UNC path"); + OSL_PRECOND(rtl_ustr_indexOfChar(path, SLASH) == -1, "Path must not contain slashes"); + + const sal_Unicode* pend = path + rtl_ustr_getLength(path); + const sal_Unicode* ppos = path + 2; + + puncc->server_.begin_ = ppos; + while ((ppos < pend) && (*ppos != BACKSLASH)) + ppos++; + + puncc->server_.end_ = ppos; + + if (BACKSLASH == *ppos) + { + puncc->share_.begin_ = ++ppos; + while ((ppos < pend) && (*ppos != BACKSLASH)) + ppos++; + + puncc->share_.end_ = ppos; + + if (BACKSLASH == *ppos) + { + puncc->resource_.begin_ = ++ppos; + while (ppos < pend) + ppos++; + + puncc->resource_.end_ = ppos; + } + } + + SAL_WARN_IF(!puncc->server_.isPresent() || !puncc->share_.isPresent(), + "sal.osl", + "Postcondition violated: Invalid UNC path detected"); + } + + bool has_path_parent(const sal_Unicode* path) + { + // Has the given path a parent or are we already there, + // e.g. 'c:\' or '\\server\share\'? + + bool has_parent = false; + if (is_UNC_path(path)) + { + UNCComponents unc_comp; + parse_UNC_path(path, &unc_comp); + has_parent = unc_comp.resource_.isPresent(); + } + else + { + has_parent = !osl::systemPathIsLogicalDrivePattern(OUString(path)); + } + return has_parent; + } + + bool has_path_parent(const OUString& path) + { return has_path_parent(path.getStr()); } + +} + +oslFileError SAL_CALL osl_acquireVolumeDeviceHandle( oslVolumeDeviceHandle Handle ) +{ + if ( Handle ) + { + rtl_uString_acquire( static_cast<rtl_uString *>(Handle) ); + return osl_File_E_None; + } + else + return osl_File_E_INVAL; +} + +oslFileError SAL_CALL osl_releaseVolumeDeviceHandle( oslVolumeDeviceHandle Handle ) +{ + if ( Handle ) + { + rtl_uString_release( static_cast<rtl_uString *>(Handle) ); + return osl_File_E_None; + } + else + return osl_File_E_INVAL; +} + +oslFileError SAL_CALL osl_getVolumeDeviceMountPath( oslVolumeDeviceHandle Handle, rtl_uString **pstrPath ) +{ + if ( Handle && pstrPath ) + { + rtl_uString_assign( pstrPath, static_cast<rtl_uString *>(Handle) ); + return osl_File_E_None; + } + else + return osl_File_E_INVAL; +} + +#define DIRECTORYITEM_DRIVE 0 +#define DIRECTORYITEM_FILE 1 +#define DIRECTORYITEM_SERVER 2 + +namespace { + +struct DirectoryItem_Impl +{ + UINT uType = 0; + union { + WIN32_FIND_DATAW FindData; + WCHAR cDriveString[MAX_PATH]; + }; + OUString m_sFullPath; + bool bFullPathNormalized = false; + int nRefCount = 0; +}; + +} + +#define DIRECTORYTYPE_LOCALROOT 0 +#define DIRECTORYTYPE_NETROOT 1 +#define DIRECTORYTYPE_FILESYSTEM 3 + +namespace { + +struct Directory_Impl +{ + UINT uType = 0; + union { + HANDLE hDirectory = nullptr; + HANDLE hEnumDrives; + }; + OUString m_sDirectoryPath; +}; + +typedef struct tagDRIVEENUM +{ + LPCWSTR lpIdent; + WCHAR cBuffer[/*('Z' - 'A' + 1) * sizeof("A:\\") + 1*/256]; + LPCWSTR lpCurrent; +} DRIVEENUM, *PDRIVEENUM, *LPDRIVEENUM; + +} + +static HANDLE WINAPI OpenLogicalDrivesEnum() +{ + LPDRIVEENUM pEnum = static_cast<LPDRIVEENUM>(HeapAlloc( GetProcessHeap(), 0, sizeof(DRIVEENUM) )); + if ( pEnum ) + { + DWORD dwNumCopied = GetLogicalDriveStringsW( SAL_N_ELEMENTS(pEnum->cBuffer) - 1, pEnum->cBuffer ); + + if ( dwNumCopied && dwNumCopied < SAL_N_ELEMENTS(pEnum->cBuffer) ) + { + pEnum->lpCurrent = pEnum->cBuffer; + pEnum->lpIdent = L"tagDRIVEENUM"; + } + else + { + HeapFree( GetProcessHeap(), 0, pEnum ); + pEnum = nullptr; + } + } + return pEnum ? static_cast<HANDLE>(pEnum) : INVALID_HANDLE_VALUE; +} + +static bool WINAPI EnumLogicalDrives(HANDLE hEnum, LPWSTR lpBuffer) +{ + LPDRIVEENUM pEnum = static_cast<LPDRIVEENUM>(hEnum); + if ( !pEnum ) + { + SetLastError( ERROR_INVALID_HANDLE ); + return false; + } + + int nLen = wcslen( pEnum->lpCurrent ); + if ( !nLen ) + { + SetLastError( ERROR_NO_MORE_FILES ); + return false; + } + + CopyMemory( lpBuffer, pEnum->lpCurrent, (nLen + 1) * sizeof(WCHAR) ); + pEnum->lpCurrent += nLen + 1; + return true; +} + +static bool WINAPI CloseLogicalDrivesEnum(HANDLE hEnum) +{ + bool fSuccess = false; + LPDRIVEENUM pEnum = static_cast<LPDRIVEENUM>(hEnum); + + if ( pEnum ) + { + HeapFree( GetProcessHeap(), 0, pEnum ); + fSuccess = true; + } + else + SetLastError( ERROR_INVALID_HANDLE ); + + return fSuccess; +} + +namespace { + +typedef struct tagDIRECTORY +{ + HANDLE hFind; + WIN32_FIND_DATAW aFirstData; +} DIRECTORY, *PDIRECTORY, *LPDIRECTORY; + +} + +static HANDLE WINAPI OpenDirectory( rtl_uString* pPath) +{ + if ( !pPath ) + return nullptr; + + sal_uInt32 nLen = rtl_uString_getLength( pPath ); + if ( !nLen ) + return nullptr; + + const WCHAR* pSuffix = nullptr; + sal_uInt32 nSuffLen = 0; + if ( pPath->buffer[nLen - 1] != L'\\' ) + { + pSuffix = L"\\*.*"; + nSuffLen = 4; + } + else + { + pSuffix = L"*.*"; + nSuffLen = 3; + } + + WCHAR* szFileMask = static_cast< WCHAR* >( malloc( sizeof( WCHAR ) * ( nLen + nSuffLen + 1 ) ) ); + assert(szFileMask); // Don't handle OOM conditions + wcscpy( szFileMask, o3tl::toW(rtl_uString_getStr( pPath )) ); + wcscat( szFileMask, pSuffix ); + + LPDIRECTORY pDirectory = static_cast<LPDIRECTORY>(HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY))); + assert(pDirectory); // Don't handle OOM conditions + pDirectory->hFind = FindFirstFileW(szFileMask, &pDirectory->aFirstData); + + if (!IsValidHandle(pDirectory->hFind)) + { + if ( GetLastError() != ERROR_NO_MORE_FILES ) + { + HeapFree(GetProcessHeap(), 0, pDirectory); + pDirectory = nullptr; + } + } + free(szFileMask); + + return static_cast<HANDLE>(pDirectory); +} + +static bool WINAPI EnumDirectory(HANDLE hDirectory, LPWIN32_FIND_DATAW pFindData) +{ + LPDIRECTORY pDirectory = static_cast<LPDIRECTORY>(hDirectory); + if ( !pDirectory ) + { + SetLastError( ERROR_INVALID_HANDLE ); + return false; + } + + bool fSuccess = false; + bool fValid; + do + { + if ( pDirectory->aFirstData.cFileName[0] ) + { + *pFindData = pDirectory->aFirstData; + fSuccess = true; + pDirectory->aFirstData.cFileName[0] = 0; + } + else if ( IsValidHandle( pDirectory->hFind ) ) + fSuccess = FindNextFileW( pDirectory->hFind, pFindData ); + else + { + fSuccess = false; + SetLastError( ERROR_NO_MORE_FILES ); + } + + fValid = fSuccess && wcscmp( L".", pFindData->cFileName ) != 0 && wcscmp( L"..", pFindData->cFileName ) != 0; + + } while( fSuccess && !fValid ); + + return fSuccess; +} + +static bool WINAPI CloseDirectory(HANDLE hDirectory) +{ + bool fSuccess = false; + LPDIRECTORY pDirectory = static_cast<LPDIRECTORY>(hDirectory); + + if (pDirectory) + { + if (IsValidHandle(pDirectory->hFind)) + fSuccess = FindClose(pDirectory->hFind); + + fSuccess = HeapFree(GetProcessHeap(), 0, pDirectory) && fSuccess; + } + else + SetLastError(ERROR_INVALID_HANDLE); + + return fSuccess; +} + +static oslFileError osl_openLocalRoot( + rtl_uString *strDirectoryPath, oslDirectory *pDirectory) +{ + if ( !pDirectory ) + return osl_File_E_INVAL; + + *pDirectory = nullptr; + + OUString strSysPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDirectoryPath), &strSysPath.pData, false); + if ( osl_File_E_None != error ) + return error; + + Directory_Impl* pDirImpl = new (std::nothrow) Directory_Impl; + assert(pDirImpl); // Don't handle OOM conditions + pDirImpl->m_sDirectoryPath = strSysPath; + + /* Append backslash if necessary */ + + /* @@@ToDo + use function ensure backslash + */ + sal_uInt32 nLen = pDirImpl->m_sDirectoryPath.getLength(); + if ( nLen && pDirImpl->m_sDirectoryPath[nLen - 1] != L'\\' ) + { + pDirImpl->m_sDirectoryPath += "\\"; + } + + pDirImpl->uType = DIRECTORYTYPE_LOCALROOT; + pDirImpl->hEnumDrives = OpenLogicalDrivesEnum(); + + /* @@@ToDo + Use IsValidHandle(...) + */ + if ( pDirImpl->hEnumDrives != INVALID_HANDLE_VALUE ) + { + *pDirectory = static_cast<oslDirectory>(pDirImpl); + error = osl_File_E_None; + } + else + { + if ( pDirImpl ) + { + delete pDirImpl; + pDirImpl = nullptr; + } + + error = oslTranslateFileError( GetLastError() ); + } + return error; +} + +static oslFileError osl_openFileDirectory( + rtl_uString *strDirectoryPath, oslDirectory *pDirectory) +{ + oslFileError error = osl_File_E_None; + + if ( !pDirectory ) + return osl_File_E_INVAL; + *pDirectory = nullptr; + + Directory_Impl *pDirImpl = new (std::nothrow) Directory_Impl; + assert(pDirImpl); // Don't handle OOM conditions + pDirImpl->m_sDirectoryPath = strDirectoryPath; + + /* Append backslash if necessary */ + + /* @@@ToDo + use function ensure backslash + */ + sal_uInt32 nLen = pDirImpl->m_sDirectoryPath.getLength(); + if ( nLen && pDirImpl->m_sDirectoryPath[nLen - 1] != '\\' ) + pDirImpl->m_sDirectoryPath += "\\"; + + pDirImpl->uType = DIRECTORYTYPE_FILESYSTEM; + pDirImpl->hDirectory = OpenDirectory( pDirImpl->m_sDirectoryPath.pData ); + + if ( !pDirImpl->hDirectory ) + { + error = oslTranslateFileError( GetLastError() ); + + delete pDirImpl; + pDirImpl = nullptr; + } + + *pDirectory = static_cast<oslDirectory>(pDirImpl); + return error; +} + +static oslFileError osl_openNetworkServer( + rtl_uString *strSysDirPath, oslDirectory *pDirectory) +{ + NETRESOURCEW aNetResource; + HANDLE hEnum; + DWORD dwError; + + ZeroMemory( &aNetResource, sizeof(aNetResource) ); + + aNetResource.lpRemoteName = o3tl::toW(strSysDirPath->buffer); + + dwError = WNetOpenEnumW( + RESOURCE_GLOBALNET, + RESOURCETYPE_DISK, + RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER, + &aNetResource, + &hEnum ); + + if ( ERROR_SUCCESS == dwError ) + { + Directory_Impl *pDirImpl = new (std::nothrow) Directory_Impl; + assert(pDirImpl); // Don't handle OOM conditions + pDirImpl->uType = DIRECTORYTYPE_NETROOT; + pDirImpl->hDirectory = hEnum; + *pDirectory = static_cast<oslDirectory>(pDirImpl); + } + return oslTranslateFileError( dwError ); +} + +static DWORD create_dir_with_callback( + rtl_uString * dir_path, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + // Create the specified directory and call the + // user specified callback function. On success + // the function returns ERROR_SUCCESS else a Win32 error code. + + bool bCreated = CreateDirectoryW( o3tl::toW(rtl_uString_getStr( dir_path )), nullptr ); + + if ( bCreated ) + { + if (aDirectoryCreationCallbackFunc) + { + OUString url; + osl_getFileURLFromSystemPath(dir_path, &(url.pData)); + aDirectoryCreationCallbackFunc(pData, url.pData); + } + return ERROR_SUCCESS; + } + return GetLastError(); +} + +static int path_make_parent(sal_Unicode* path) +{ + /* Cut off the last part of the given path to + get the parent only, e.g. 'c:\dir\subdir' -> + 'c:\dir' or '\\share\sub\dir' -> '\\share\sub' + @return The position where the path has been cut + off (this is the position of the last backslash). + If there are no more parents 0 will be returned, + e.g. 'c:\' or '\\Share' have no more parents */ + + OSL_PRECOND(rtl_ustr_indexOfChar(path, SLASH) == -1, "Path must not contain slashes"); + OSL_PRECOND(has_path_parent(path), "Path must have a parent"); + + sal_Unicode* pos_last_backslash = path + rtl_ustr_lastIndexOfChar(path, BACKSLASH); + *pos_last_backslash = 0; + return (pos_last_backslash - path); +} + +static DWORD create_dir_recursively_( + rtl_uString * dir_path, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + OSL_PRECOND( + rtl_ustr_lastIndexOfChar_WithLength(dir_path->buffer, dir_path->length, BACKSLASH) != dir_path->length, + "Path must not end with a backslash"); + + DWORD w32_error = create_dir_with_callback( + dir_path, aDirectoryCreationCallbackFunc, pData); + if (w32_error == ERROR_SUCCESS) + return ERROR_SUCCESS; + + if ((w32_error != ERROR_PATH_NOT_FOUND) || !has_path_parent(dir_path->buffer)) + return w32_error; + + int pos = path_make_parent(dir_path->buffer); // dir_path->buffer[pos] = 0, restore below + + w32_error = create_dir_recursively_( + dir_path, aDirectoryCreationCallbackFunc, pData); + + dir_path->buffer[pos] = BACKSLASH; // restore + + if (ERROR_SUCCESS != w32_error && ERROR_ALREADY_EXISTS != w32_error) + return w32_error; + + return create_dir_recursively_(dir_path, aDirectoryCreationCallbackFunc, pData); +} + +oslFileError SAL_CALL osl_createDirectoryPath( + rtl_uString* aDirectoryUrl, + oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc, + void* pData) +{ + if (aDirectoryUrl == nullptr) + return osl_File_E_INVAL; + + OUString sys_path; + oslFileError osl_error = + osl_getSystemPathFromFileURL_(OUString::unacquired(&aDirectoryUrl), &sys_path.pData, false); + + if (osl_error != osl_File_E_None) + return osl_error; + + osl::systemPathRemoveSeparator(sys_path); + + // const_cast because sys_path is a local copy + // which we want to modify inplace instead of + // copy it into another buffer on the heap again + return oslTranslateFileError(create_dir_recursively_( + sys_path.pData, aDirectoryCreationCallbackFunc, pData)); +} + +oslFileError SAL_CALL osl_createDirectory(rtl_uString* strPath) +{ + return osl_createDirectoryWithFlags( + strPath, osl_File_OpenFlag_Read | osl_File_OpenFlag_Write); +} + +oslFileError osl_createDirectoryWithFlags(rtl_uString * strPath, sal_uInt32) +{ + OUString strSysPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if ( osl_File_E_None != error ) + return error; + + bool bCreated = CreateDirectoryW(o3tl::toW(strSysPath.getStr()), nullptr); + if ( !bCreated ) + { + /*@@@ToDo + The following case is a hack because the ucb or the webtop had some + problems with the error code that CreateDirectory returns in + case the path is only a logical drive, should be removed! + */ + + if ((strSysPath.getLength() == 2 || (strSysPath.getLength() == 3 && strSysPath[2] == '\\')) + && rtl::isAsciiAlpha(strSysPath[0]) && strSysPath[1] == ':') + SetLastError( ERROR_ALREADY_EXISTS ); + + error = oslTranslateFileError( GetLastError() ); + } + + return error; +} + +oslFileError SAL_CALL osl_removeDirectory(rtl_uString* strPath) +{ + OUString strSysPath; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false); + + if ( osl_File_E_None == error ) + { + if (RemoveDirectoryW(o3tl::toW(strSysPath.getStr()))) + error = osl_File_E_None; + else + error = oslTranslateFileError( GetLastError() ); + } + return error; +} + +oslFileError SAL_CALL osl_openDirectory(rtl_uString *strDirectoryPath, oslDirectory *pDirectory) +{ + oslFileError error; + + if ( 0 == rtl_ustr_ascii_compareIgnoreAsciiCase( strDirectoryPath->buffer, "file:///" ) ) + error = osl_openLocalRoot( strDirectoryPath, pDirectory ); + else + { + OUString strSysDirectoryPath; + DWORD dwPathType; + + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDirectoryPath), &strSysDirectoryPath.pData, false); + + if ( osl_File_E_None != error ) + return error; + + dwPathType = IsValidFilePath(strSysDirectoryPath, VALIDATEPATH_NORMAL, nullptr); + + if ( dwPathType & PATHTYPE_IS_SERVER ) + error = osl_openNetworkServer(strSysDirectoryPath.pData, pDirectory); + else + error = osl_openFileDirectory(strSysDirectoryPath.pData, pDirectory); + } + return error; +} + +static oslFileError osl_getNextNetResource( + oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/ ) +{ + Directory_Impl *pDirImpl = static_cast<Directory_Impl *>(Directory); + DirectoryItem_Impl *pItemImpl = nullptr; + BYTE buffer[16384]; + LPNETRESOURCEW lpNetResource = reinterpret_cast<LPNETRESOURCEW>(buffer); + DWORD dwError, dwCount, dwBufSize; + + if ( !pItem ) + return osl_File_E_INVAL; + *pItem = nullptr; + + if ( !pDirImpl ) + return osl_File_E_INVAL; + + dwCount = 1; + dwBufSize = sizeof(buffer); + dwError = WNetEnumResourceW( pDirImpl->hDirectory, &dwCount, lpNetResource, &dwBufSize ); + + switch ( dwError ) + { + case NO_ERROR: + case ERROR_MORE_DATA: + { + pItemImpl = new (std::nothrow) DirectoryItem_Impl; + if ( !pItemImpl ) + return osl_File_E_NOMEM; + + pItemImpl->uType = DIRECTORYITEM_DRIVE; + osl_acquireDirectoryItem( static_cast<oslDirectoryItem>(pItemImpl) ); + + wcscpy( pItemImpl->cDriveString, lpNetResource->lpRemoteName ); + + *pItem = pItemImpl; + } + return osl_File_E_None; + case ERROR_NO_MORE_ITEMS: + return osl_File_E_NOENT; + default: + return oslTranslateFileError( dwError ); + } +} + +static oslFileError osl_getNextDrive( + oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/ ) +{ + Directory_Impl *pDirImpl = static_cast<Directory_Impl *>(Directory); + DirectoryItem_Impl *pItemImpl = nullptr; + bool fSuccess; + + if ( !pItem ) + return osl_File_E_INVAL; + *pItem = nullptr; + + if ( !pDirImpl ) + return osl_File_E_INVAL; + + pItemImpl = new (std::nothrow) DirectoryItem_Impl; + if ( !pItemImpl ) + return osl_File_E_NOMEM; + + pItemImpl->uType = DIRECTORYITEM_DRIVE; + osl_acquireDirectoryItem( static_cast<oslDirectoryItem>(pItemImpl) ); + fSuccess = EnumLogicalDrives( pDirImpl->hEnumDrives, pItemImpl->cDriveString ); + + if ( fSuccess ) + { + *pItem = pItemImpl; + return osl_File_E_None; + } + else + { + delete pItemImpl; + return oslTranslateFileError( GetLastError() ); + } +} + +static oslFileError osl_getNextFileItem( + oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/) +{ + Directory_Impl *pDirImpl = static_cast<Directory_Impl *>(Directory); + DirectoryItem_Impl *pItemImpl = nullptr; + bool fFound; + + if ( !pItem ) + return osl_File_E_INVAL; + *pItem = nullptr; + + if ( !pDirImpl ) + return osl_File_E_INVAL; + + pItemImpl = new (std::nothrow) DirectoryItem_Impl; + if ( !pItemImpl ) + return osl_File_E_NOMEM; + + fFound = EnumDirectory( pDirImpl->hDirectory, &pItemImpl->FindData ); + if ( !fFound ) + { + delete pItemImpl; + return oslTranslateFileError( GetLastError() ); + } + + pItemImpl->uType = DIRECTORYITEM_FILE; + pItemImpl->nRefCount = 1; + + pItemImpl->m_sFullPath = pDirImpl->m_sDirectoryPath + o3tl::toU(pItemImpl->FindData.cFileName); + + pItemImpl->bFullPathNormalized = true; + *pItem = static_cast<oslDirectoryItem>(pItemImpl); + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_getNextDirectoryItem( + oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 uHint) +{ + Directory_Impl *pDirImpl = static_cast<Directory_Impl *>(Directory); + + /* Assume failure */ + + if ( !pItem ) + return osl_File_E_INVAL; + *pItem = nullptr; + + if ( !pDirImpl ) + return osl_File_E_INVAL; + + switch ( pDirImpl->uType ) + { + case DIRECTORYTYPE_LOCALROOT: + return osl_getNextDrive( Directory, pItem, uHint ); + case DIRECTORYTYPE_NETROOT: + return osl_getNextNetResource( Directory, pItem, uHint ); + case DIRECTORYTYPE_FILESYSTEM: + return osl_getNextFileItem( Directory, pItem, uHint ); + default: + return osl_File_E_INVAL; + } +} + +oslFileError SAL_CALL osl_closeDirectory(oslDirectory Directory) +{ + Directory_Impl *pDirImpl = static_cast<Directory_Impl *>(Directory); + oslFileError eError = osl_File_E_INVAL; + + if ( pDirImpl ) + { + switch ( pDirImpl->uType ) + { + case DIRECTORYTYPE_FILESYSTEM: + eError = CloseDirectory( pDirImpl->hDirectory ) ? osl_File_E_None : oslTranslateFileError( GetLastError() ); + break; + case DIRECTORYTYPE_LOCALROOT: + eError = CloseLogicalDrivesEnum( pDirImpl->hEnumDrives ) ? osl_File_E_None : oslTranslateFileError( GetLastError() ); + break; + case DIRECTORYTYPE_NETROOT: + { + DWORD err = WNetCloseEnum(pDirImpl->hDirectory); + eError = (err == NO_ERROR) ? osl_File_E_None : oslTranslateFileError(err); + } + break; + default: + OSL_FAIL( "Invalid directory type" ); + break; + } + + delete pDirImpl; + } + return eError; +} + +namespace { + +/* Different types of paths */ +enum PATHTYPE +{ + PATHTYPE_SYNTAXERROR = 0, + PATHTYPE_NETROOT, + PATHTYPE_NETSERVER, + PATHTYPE_VOLUME, + PATHTYPE_FILE +}; + +} + +oslFileError SAL_CALL osl_getDirectoryItem(rtl_uString *strFilePath, oslDirectoryItem *pItem) +{ + oslFileError error = osl_File_E_None; + OUString strSysFilePath; + PATHTYPE type = PATHTYPE_FILE; + DWORD dwPathType; + + /* Assume failure */ + + if ( !pItem ) + return osl_File_E_INVAL; + + *pItem = nullptr; + + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strFilePath), &strSysFilePath.pData, false); + + if ( osl_File_E_None != error ) + return error; + + dwPathType = IsValidFilePath( strSysFilePath, VALIDATEPATH_NORMAL, nullptr ); + + if ( dwPathType & PATHTYPE_IS_VOLUME ) + type = PATHTYPE_VOLUME; + else if ( dwPathType & PATHTYPE_IS_SERVER ) + type = PATHTYPE_NETSERVER; + else + type = PATHTYPE_FILE; + + switch ( type ) + { + case PATHTYPE_NETSERVER: + { + DirectoryItem_Impl* pItemImpl = new (std::nothrow) DirectoryItem_Impl; + + if ( !pItemImpl ) + error = osl_File_E_NOMEM; + + if ( osl_File_E_None == error ) + { + pItemImpl->uType = DIRECTORYITEM_SERVER; + + osl_acquireDirectoryItem( static_cast<oslDirectoryItem>(pItemImpl) ); + pItemImpl->m_sFullPath = strSysFilePath; + + // Assign a title anyway + { + int iSrc = 2; + int iDst = 0; + + while( iSrc < strSysFilePath.getLength() && strSysFilePath[iSrc] && strSysFilePath[iSrc] != '\\') + { + pItemImpl->FindData.cFileName[iDst++] = strSysFilePath[iSrc++]; + } + } + + *pItem = pItemImpl; + } + } + break; + case PATHTYPE_VOLUME: + { + DirectoryItem_Impl* pItemImpl = new (std::nothrow) DirectoryItem_Impl; + + if ( !pItemImpl ) + error = osl_File_E_NOMEM; + + if ( osl_File_E_None == error ) + { + pItemImpl->uType = DIRECTORYITEM_DRIVE; + + osl_acquireDirectoryItem( static_cast<oslDirectoryItem>(pItemImpl) ); + + wcscpy( pItemImpl->cDriveString, o3tl::toW(strSysFilePath.getStr()) ); + pItemImpl->cDriveString[0] = rtl::toAsciiUpperCase( pItemImpl->cDriveString[0] ); + + if ( pItemImpl->cDriveString[wcslen(pItemImpl->cDriveString) - 1] != '\\' ) + wcscat( pItemImpl->cDriveString, L"\\" ); + + *pItem = pItemImpl; + } + } + break; + case PATHTYPE_SYNTAXERROR: + case PATHTYPE_NETROOT: + case PATHTYPE_FILE: + { + HANDLE hFind; + WIN32_FIND_DATAW aFindData; + + if (!strSysFilePath.isEmpty() && strSysFilePath[strSysFilePath.getLength() - 1] == '\\') + strSysFilePath = strSysFilePath.copy(0, strSysFilePath.getLength() - 1); + + hFind = FindFirstFileW( o3tl::toW(strSysFilePath.getStr()), &aFindData ); + + if ( hFind != INVALID_HANDLE_VALUE ) + { + DirectoryItem_Impl *pItemImpl = new (std::nothrow) DirectoryItem_Impl; + if (!pItemImpl) + error = osl_File_E_NOMEM; + + if (osl_File_E_None == error) + { + osl_acquireDirectoryItem(static_cast<oslDirectoryItem>(pItemImpl)); + + CopyMemory(&pItemImpl->FindData, &aFindData, sizeof(WIN32_FIND_DATAW)); + pItemImpl->m_sFullPath = strSysFilePath; + + pItemImpl->uType = DIRECTORYITEM_FILE; + *pItem = pItemImpl; + } + + FindClose( hFind ); + } + else + error = oslTranslateFileError( GetLastError() ); + } + break; + } + + return error; +} + +oslFileError SAL_CALL osl_acquireDirectoryItem( oslDirectoryItem Item ) +{ + DirectoryItem_Impl *pItemImpl = static_cast<DirectoryItem_Impl *>(Item); + + if ( !pItemImpl ) + return osl_File_E_INVAL; + + pItemImpl->nRefCount++; + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_releaseDirectoryItem( oslDirectoryItem Item ) +{ + DirectoryItem_Impl *pItemImpl = static_cast<DirectoryItem_Impl *>(Item); + + if ( !pItemImpl ) + return osl_File_E_INVAL; + + if ( ! --pItemImpl->nRefCount ) + delete pItemImpl; + + return osl_File_E_None; +} + +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_sFullPath == pB->m_sFullPath) + return true; + + // FIXME: as/when/if this is used in anger on Windows we could + // do better here. + + return false; +} + +static bool is_floppy_A_present() +{ return (GetLogicalDrives() & 1); } + +static bool is_floppy_B_present() +{ return (GetLogicalDrives() & 2); } + +static bool is_floppy_volume_mount_point(const OUString& path) +{ + // determines if a volume mount point shows to a floppy + // disk by comparing the unique volume names + static const LPCWSTR FLOPPY_A = L"A:\\"; + static const LPCWSTR FLOPPY_B = L"B:\\"; + + OUString p(path); + osl::systemPathEnsureSeparator(p); + + WCHAR vn[51]; + if (GetVolumeNameForVolumeMountPointW(o3tl::toW(p.getStr()), vn, SAL_N_ELEMENTS(vn))) + { + WCHAR vnfloppy[51]; + if (is_floppy_A_present() && + GetVolumeNameForVolumeMountPointW(FLOPPY_A, vnfloppy, SAL_N_ELEMENTS(vnfloppy)) && + (0 == wcscmp(vn, vnfloppy))) + return true; + + if (is_floppy_B_present() && + GetVolumeNameForVolumeMountPointW(FLOPPY_B, vnfloppy, SAL_N_ELEMENTS(vnfloppy)) && + (0 == wcscmp(vn, vnfloppy))) + return true; + } + return false; +} + +static bool is_floppy_drive(const OUString& path) +{ + static const LPCWSTR FLOPPY_DRV_LETTERS = L"AaBb"; + + // we must take into account that even a floppy + // drive may be mounted to a directory so checking + // for the drive letter alone is not sufficient + // we must compare the unique volume name with + // that of the available floppy disks + + const sal_Unicode* pszPath = path.getStr(); + return ((wcschr(FLOPPY_DRV_LETTERS, pszPath[0]) && (L':' == pszPath[1])) || is_floppy_volume_mount_point(path)); +} + +static bool is_volume_mount_point(const OUString& path) +{ + OUString p(path); + osl::systemPathRemoveSeparator(p); + + if (is_floppy_drive(p)) + return false; + + DWORD fattr = GetFileAttributesW(o3tl::toW(p.getStr())); + if ((INVALID_FILE_ATTRIBUTES == fattr) || + !(FILE_ATTRIBUTE_REPARSE_POINT & fattr)) + return false; + + bool is_volume_root = false; + WIN32_FIND_DATAW find_data; + HANDLE h_find = FindFirstFileW(o3tl::toW(p.getStr()), &find_data); + + if (IsValidHandle(h_find) && + (FILE_ATTRIBUTE_REPARSE_POINT & find_data.dwFileAttributes) && + (IO_REPARSE_TAG_MOUNT_POINT == find_data.dwReserved0)) + { + is_volume_root = true; + } + if (IsValidHandle(h_find)) + FindClose(h_find); + return is_volume_root; +} + +static UINT get_volume_mount_point_drive_type(const OUString& path) +{ + if (0 == path.getLength()) + return GetDriveTypeW(nullptr); + + OUString p(path); + osl::systemPathEnsureSeparator(p); + + WCHAR vn[51]; + if (GetVolumeNameForVolumeMountPointW(o3tl::toW(p.getStr()), vn, SAL_N_ELEMENTS(vn))) + return GetDriveTypeW(vn); + + return DRIVE_NO_ROOT_DIR; +} + +static bool is_drivetype_request(sal_uInt32 field_mask) +{ + return (field_mask & osl_VolumeInfo_Mask_Attributes); +} + +static oslFileError osl_get_drive_type( + const OUString& path, oslVolumeInfo* pInfo) +{ + // GetDriveType fails on empty volume mount points + // see Knowledge Base Q244089 + UINT drive_type; + if (is_volume_mount_point(path)) + drive_type = get_volume_mount_point_drive_type(path); + else + drive_type = GetDriveTypeW(o3tl::toW(path.getStr())); + + if (DRIVE_NO_ROOT_DIR == drive_type) + return oslTranslateFileError(ERROR_INVALID_DRIVE); + + pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes; + + switch (drive_type) + { + case DRIVE_CDROM: + pInfo->uAttributes |= osl_Volume_Attribute_CompactDisc | osl_Volume_Attribute_Removeable; + break; + case DRIVE_REMOVABLE: + pInfo->uAttributes |= osl_Volume_Attribute_Removeable; + if (is_floppy_drive(path)) + pInfo->uAttributes |= osl_Volume_Attribute_FloppyDisk; + break; + case DRIVE_FIXED: + pInfo->uAttributes |= osl_Volume_Attribute_FixedDisk; + break; + case DRIVE_RAMDISK: + pInfo->uAttributes |= osl_Volume_Attribute_RAMDisk; + break; + case DRIVE_REMOTE: + pInfo->uAttributes |= osl_Volume_Attribute_Remote; + break; + case DRIVE_UNKNOWN: + pInfo->uAttributes = 0; + break; + default: + pInfo->uValidFields &= ~osl_VolumeInfo_Mask_Attributes; + pInfo->uAttributes = 0; + break; + } + return osl_File_E_None; +} + +static bool is_volume_space_info_request(sal_uInt32 field_mask) +{ + return (field_mask & + (osl_VolumeInfo_Mask_TotalSpace | + osl_VolumeInfo_Mask_UsedSpace | + osl_VolumeInfo_Mask_FreeSpace)); +} + +static void get_volume_space_information( + const OUString& path, oslVolumeInfo *pInfo) +{ + bool ret = GetDiskFreeSpaceExW( + o3tl::toW(path.getStr()), + reinterpret_cast<PULARGE_INTEGER>(&pInfo->uFreeSpace), + reinterpret_cast<PULARGE_INTEGER>(&pInfo->uTotalSpace), + nullptr); + + if (ret) + { + pInfo->uUsedSpace = pInfo->uTotalSpace - pInfo->uFreeSpace; + pInfo->uValidFields |= osl_VolumeInfo_Mask_TotalSpace | + osl_VolumeInfo_Mask_UsedSpace | + osl_VolumeInfo_Mask_FreeSpace; + } +} + +static bool is_filesystem_attributes_request(sal_uInt32 field_mask) +{ + return (field_mask & + (osl_VolumeInfo_Mask_MaxNameLength | + osl_VolumeInfo_Mask_MaxPathLength | + osl_VolumeInfo_Mask_FileSystemName | + osl_VolumeInfo_Mask_FileSystemCaseHandling)); +} + +static oslFileError get_filesystem_attributes( + const OUString& path, sal_uInt32 field_mask, oslVolumeInfo* pInfo) +{ + pInfo->uAttributes = 0; + + // osl_get_drive_type must be called first because + // this function resets osl_VolumeInfo_Mask_Attributes + // on failure + if (is_drivetype_request(field_mask)) + { + oslFileError osl_error = osl_get_drive_type(path, pInfo); + if (osl_File_E_None != osl_error) + return osl_error; + } + if (is_filesystem_attributes_request(field_mask)) + { + /* the following two parameters can not be longer than MAX_PATH+1 */ + WCHAR vn[MAX_PATH+1]; + WCHAR fsn[MAX_PATH+1]; + + DWORD serial; + DWORD mcl; + DWORD flags; + + LPCWSTR pszPath = o3tl::toW(path.getStr()); + if (GetVolumeInformationW(pszPath, vn, MAX_PATH+1, &serial, &mcl, &flags, fsn, MAX_PATH+1)) + { + // Currently sal does not use this value, instead MAX_PATH is used + pInfo->uValidFields |= osl_VolumeInfo_Mask_MaxNameLength; + pInfo->uMaxNameLength = mcl; + + // Should the uMaxPathLength be set to 32767, "\\?\" prefix allows it + pInfo->uValidFields |= osl_VolumeInfo_Mask_MaxPathLength; + pInfo->uMaxPathLength = MAX_PATH; + + pInfo->uValidFields |= osl_VolumeInfo_Mask_FileSystemName; + rtl_uString_newFromStr(&pInfo->ustrFileSystemName, o3tl::toU(fsn)); + + // volumes (even NTFS) will always be considered case + // insensitive because the Win32 API is not able to + // deal with case sensitive volumes see M$ Knowledge Base + // article 100625 that's why we never set the attribute + // osl_Volume_Attribute_Case_Sensitive + + if (flags & FS_CASE_IS_PRESERVED) + pInfo->uAttributes |= osl_Volume_Attribute_Case_Is_Preserved; + + pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes; + } + } + return osl_File_E_None; +} + +static bool path_get_parent(OUString& path) +{ + OSL_PRECOND(path.lastIndexOf(SLASH) == -1, "Path must not have slashes"); + + if (!has_path_parent(path)) + { + sal_Int32 i = path.lastIndexOf(BACKSLASH); + if (-1 < i) + { + path = OUString(path.getStr(), i); + return true; + } + } + return false; +} + +static void path_travel_to_volume_root(const OUString& system_path, OUString& volume_root) +{ + OUString sys_path(system_path); + + while(!is_volume_mount_point(sys_path) && path_get_parent(sys_path)) + /**/; + + volume_root = sys_path; + osl::systemPathEnsureSeparator(volume_root); +} + +oslFileError SAL_CALL osl_getVolumeInformation( + rtl_uString *ustrURL, oslVolumeInfo *pInfo, sal_uInt32 uFieldMask ) +{ + if (!pInfo) + return osl_File_E_INVAL; + + OUString system_path; + oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrURL), &system_path.pData, false); + + if (osl_File_E_None != error) + return error; + + OUString volume_root; + path_travel_to_volume_root(system_path, volume_root); + + pInfo->uValidFields = 0; + + if ((error = get_filesystem_attributes(volume_root, uFieldMask, pInfo)) != osl_File_E_None) + return error; + + if (is_volume_space_info_request(uFieldMask)) + get_volume_space_information(volume_root, pInfo); + + if (uFieldMask & osl_VolumeInfo_Mask_DeviceHandle) + { + error = osl_getFileURLFromSystemPath(volume_root.pData, reinterpret_cast<rtl_uString**>(&pInfo->pDeviceHandle)); + if (error != osl_File_E_None) + return error; + pInfo->uValidFields |= osl_VolumeInfo_Mask_DeviceHandle; + } + + return osl_File_E_None; +} + +static oslFileError osl_getDriveInfo( + oslDirectoryItem Item, oslFileStatus *pStatus, sal_uInt32 uFieldMask) +{ + DirectoryItem_Impl *pItemImpl = static_cast<DirectoryItem_Impl *>(Item); + WCHAR cDrive[3] = L"A:"; + WCHAR cRoot[4] = L"A:\\"; + + if ( !pItemImpl ) + return osl_File_E_INVAL; + + pStatus->uValidFields = 0; + + cDrive[0] = pItemImpl->cDriveString[0]; + cRoot[0] = pItemImpl->cDriveString[0]; + + if ( uFieldMask & osl_FileStatus_Mask_FileName ) + { + if ( pItemImpl->cDriveString[0] == '\\' && pItemImpl->cDriveString[1] == '\\' ) + { + LPCWSTR lpFirstBkSlash = wcschr( &pItemImpl->cDriveString[2], '\\' ); + + if ( lpFirstBkSlash && lpFirstBkSlash[1] ) + { + LPCWSTR lpLastBkSlash = wcschr( &lpFirstBkSlash[1], '\\' ); + + if ( lpLastBkSlash ) + rtl_uString_newFromStr_WithLength( &pStatus->ustrFileName, o3tl::toU(&lpFirstBkSlash[1]), lpLastBkSlash - lpFirstBkSlash - 1 ); + else + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(&lpFirstBkSlash[1]) ); + pStatus->uValidFields |= osl_FileStatus_Mask_FileName; + } + } + else switch ( GetDriveTypeW( cRoot ) ) + { + case DRIVE_REMOTE: + { + WCHAR szBuffer[1024]; + DWORD const dwBufsizeConst = SAL_N_ELEMENTS(szBuffer); + DWORD dwBufsize = dwBufsizeConst; + + DWORD dwResult = WNetGetConnectionW( cDrive, szBuffer, &dwBufsize ); + if ( NO_ERROR == dwResult ) + { + WCHAR szFileName[dwBufsizeConst + 16]; + + swprintf( szFileName, L"%s [%s]", cDrive, szBuffer ); + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(szFileName) ); + } + else + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cDrive) ); + } + pStatus->uValidFields |= osl_FileStatus_Mask_FileName; + break; + case DRIVE_FIXED: + { + WCHAR szVolumeNameBuffer[1024]; + DWORD const dwBufsizeConst = SAL_N_ELEMENTS(szVolumeNameBuffer); + + if ( GetVolumeInformationW( cRoot, szVolumeNameBuffer, dwBufsizeConst, nullptr, nullptr, nullptr, nullptr, 0 ) ) + { + WCHAR szFileName[dwBufsizeConst + 16]; + + swprintf( szFileName, L"%s [%s]", cDrive, szVolumeNameBuffer ); + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(szFileName) ); + } + else + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cDrive) ); + } + pStatus->uValidFields |= osl_FileStatus_Mask_FileName; + break; + case DRIVE_CDROM: + case DRIVE_REMOVABLE: + pStatus->uValidFields |= osl_FileStatus_Mask_FileName; + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cRoot) ); + break; + case DRIVE_UNKNOWN: + default: + break; + } + } + + pStatus->eType = osl_File_Type_Volume; + pStatus->uValidFields |= osl_FileStatus_Mask_Type; + + if ( uFieldMask & osl_FileStatus_Mask_FileURL ) + { + rtl_uString *ustrSystemPath = nullptr; + + rtl_uString_newFromStr( &ustrSystemPath, o3tl::toU(pItemImpl->cDriveString) ); + oslFileError error = osl_getFileURLFromSystemPath( ustrSystemPath, &pStatus->ustrFileURL ); + rtl_uString_release( ustrSystemPath ); + if (error != osl_File_E_None) + return error; + pStatus->uValidFields |= osl_FileStatus_Mask_FileURL; + } + return osl_File_E_None; +} + +static oslFileError osl_getServerInfo( + oslDirectoryItem Item, oslFileStatus *pStatus, sal_uInt32 uFieldMask ) +{ + DirectoryItem_Impl *pItemImpl = static_cast<DirectoryItem_Impl *>(Item); + if ( !pItemImpl ) + return osl_File_E_INVAL; + + pStatus->uValidFields = 0; + pStatus->eType = osl_File_Type_Directory; + pStatus->uValidFields |= osl_FileStatus_Mask_Type; + + if ( uFieldMask & osl_FileStatus_Mask_FileURL ) + { + oslFileError error = osl_getFileURLFromSystemPath( pItemImpl->m_sFullPath.pData, &pStatus->ustrFileURL ); + if (error != osl_File_E_None) + return error; + pStatus->uValidFields |= osl_FileStatus_Mask_FileURL; + } + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_getFileStatus( + oslDirectoryItem Item, + oslFileStatus *pStatus, + sal_uInt32 uFieldMask ) +{ + DirectoryItem_Impl *pItemImpl = static_cast<DirectoryItem_Impl *>(Item); + + if ( !pItemImpl ) + return osl_File_E_INVAL; + + switch ( pItemImpl->uType ) + { + case DIRECTORYITEM_DRIVE: + return osl_getDriveInfo( Item, pStatus, uFieldMask ); + case DIRECTORYITEM_SERVER: + return osl_getServerInfo( Item, pStatus, uFieldMask ); + default: + break; + } + + OUString sFullPath(pItemImpl->m_sFullPath); + + // Prefix long paths, windows API calls expect this prefix + // (only local paths starting with something like C: or D:) + if (sFullPath.getLength() >= MAX_PATH && isalpha(sFullPath[0]) && sFullPath[1] == ':') + sFullPath = "\\\\?\\" + sFullPath; + + if ( uFieldMask & osl_FileStatus_Mask_Validate ) + { + HANDLE hFind = FindFirstFileW( o3tl::toW(sFullPath.getStr() ), &pItemImpl->FindData ); + + if ( hFind != INVALID_HANDLE_VALUE ) + FindClose( hFind ); + else + return oslTranslateFileError( GetLastError() ); + + uFieldMask &= ~ osl_FileStatus_Mask_Validate; + } + + /* If no fields to retrieve left ignore pStatus */ + if ( !uFieldMask ) + return osl_File_E_None; + + /* Otherwise, this must be a valid pointer */ + if ( !pStatus ) + return osl_File_E_INVAL; + + if ( pStatus->uStructSize != sizeof(oslFileStatus) ) + return osl_File_E_INVAL; + + pStatus->uValidFields = 0; + + /* File time stamps */ + + if ( (uFieldMask & osl_FileStatus_Mask_ModifyTime) && + FileTimeToTimeValue( &pItemImpl->FindData.ftLastWriteTime, &pStatus->aModifyTime ) ) + pStatus->uValidFields |= osl_FileStatus_Mask_ModifyTime; + + if ( (uFieldMask & osl_FileStatus_Mask_AccessTime) && + FileTimeToTimeValue( &pItemImpl->FindData.ftLastAccessTime, &pStatus->aAccessTime ) ) + pStatus->uValidFields |= osl_FileStatus_Mask_AccessTime; + + if ( (uFieldMask & osl_FileStatus_Mask_CreationTime) && + FileTimeToTimeValue( &pItemImpl->FindData.ftCreationTime, &pStatus->aCreationTime ) ) + pStatus->uValidFields |= osl_FileStatus_Mask_CreationTime; + + /* Most of the fields are already set, regardless of required fields */ + + rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(pItemImpl->FindData.cFileName) ); + pStatus->uValidFields |= osl_FileStatus_Mask_FileName; + + if ((FILE_ATTRIBUTE_REPARSE_POINT & pItemImpl->FindData.dwFileAttributes) && + (IO_REPARSE_TAG_MOUNT_POINT == pItemImpl->FindData.dwReserved0)) + pStatus->eType = osl_File_Type_Volume; + else if (pItemImpl->FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + pStatus->eType = osl_File_Type_Directory; + else + pStatus->eType = osl_File_Type_Regular; + + pStatus->uValidFields |= osl_FileStatus_Mask_Type; + + pStatus->uAttributes = pItemImpl->FindData.dwFileAttributes; + pStatus->uValidFields |= osl_FileStatus_Mask_Attributes; + + pStatus->uFileSize = static_cast<sal_uInt64>(pItemImpl->FindData.nFileSizeLow) + (static_cast<sal_uInt64>(pItemImpl->FindData.nFileSizeHigh) << 32); + pStatus->uValidFields |= osl_FileStatus_Mask_FileSize; + + if ( uFieldMask & osl_FileStatus_Mask_LinkTargetURL ) + { + oslFileError error = osl_getFileURLFromSystemPath( sFullPath.pData, &pStatus->ustrLinkTargetURL ); + if (error != osl_File_E_None) + return error; + + pStatus->uValidFields |= osl_FileStatus_Mask_LinkTargetURL; + } + + if ( uFieldMask & osl_FileStatus_Mask_FileURL ) + { + if ( !pItemImpl->bFullPathNormalized ) + { + ::osl::LongPathBuffer<sal_Unicode> aBuffer(MAX_LONG_PATH); + sal_uInt32 nNewLen = GetLongPathNameW(o3tl::toW(sFullPath.getStr()), o3tl::toW(aBuffer), + aBuffer.getBufSizeInSymbols()); + + if ( nNewLen ) + { + /* Capitalizes drive name (single letter). Windows file paths are processed + case-sensitively. While parsing a path, function osl_DirectoryItem has case + PATHTYPE_VOLUME for drives, and capitalizes them. That can be overwritten by + function osl_getFileStatus, in it win32 api GetLongPathNameW does no + capitalization. Thus it needs to be postprocessed.*/ + sal_Int32 nIndex = rtl_ustr_indexOfChar(aBuffer, ':'); + if (nIndex > 0) { + aBuffer[nIndex - 1] = rtl::toAsciiUpperCase(aBuffer[nIndex - 1]); + } + + pItemImpl->m_sFullPath = OUString(&*aBuffer, nNewLen); + sFullPath = pItemImpl->m_sFullPath; + pItemImpl->bFullPathNormalized = true; + } + } + + oslFileError error = osl_getFileURLFromSystemPath( sFullPath.pData, &pStatus->ustrFileURL ); + if (error != osl_File_E_None) + return error; + pStatus->uValidFields |= osl_FileStatus_Mask_FileURL; + } + + return osl_File_E_None; +} + +oslFileError SAL_CALL osl_setFileAttributes( + rtl_uString *ustrFileURL, + sal_uInt64 uAttributes ) +{ + oslFileError error; + OUString ustrSysPath; + DWORD dwFileAttributes; + bool fSuccess; + + // Converts the normalized path into a systempath + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrFileURL), &ustrSysPath.pData, false); + + if ( osl_File_E_None != error ) + return error; + + dwFileAttributes = GetFileAttributesW(o3tl::toW(ustrSysPath.getStr())); + + if ( DWORD(-1) != dwFileAttributes ) + { + dwFileAttributes &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN); + + if ( uAttributes & osl_File_Attribute_ReadOnly ) + dwFileAttributes |= FILE_ATTRIBUTE_READONLY; + + if ( uAttributes & osl_File_Attribute_Hidden ) + dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN; + + fSuccess = SetFileAttributesW(o3tl::toW(ustrSysPath.getStr()), dwFileAttributes); + } + else + { + fSuccess = false; + } + + if ( !fSuccess ) + error = oslTranslateFileError( GetLastError() ); + + return error; +} + +oslFileError SAL_CALL osl_setFileTime( + rtl_uString *filePath, + const TimeValue *aCreationTime, + const TimeValue *aLastAccessTime, + const TimeValue *aLastWriteTime) +{ + oslFileError error; + OUString sysPath; + FILETIME *lpCreationTime=nullptr; + FILETIME *lpLastAccessTime=nullptr; + FILETIME *lpLastWriteTime=nullptr; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + HANDLE hFile; + bool fSuccess; + + error=osl_getSystemPathFromFileURL_(OUString::unacquired(&filePath), &sysPath.pData, false); + + if (error==osl_File_E_INVAL) + return error; + + hFile=CreateFileW(o3tl::toW(sysPath.getStr()), GENERIC_WRITE, 0, nullptr , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (hFile==INVALID_HANDLE_VALUE) + return osl_File_E_NOENT; + + if (TimeValueToFileTime(aCreationTime, &ftCreationTime)) + lpCreationTime=&ftCreationTime; + + if (TimeValueToFileTime(aLastAccessTime, &ftLastAccessTime)) + lpLastAccessTime=&ftLastAccessTime; + + if (TimeValueToFileTime(aLastWriteTime, &ftLastWriteTime)) + lpLastWriteTime=&ftLastWriteTime; + + fSuccess=SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); + + CloseHandle(hFile); + + if (!fSuccess) + return osl_File_E_INVAL; + else + return osl_File_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file_error.cxx b/sal/osl/w32/file_error.cxx new file mode 100644 index 000000000..feefaca38 --- /dev/null +++ b/sal/osl/w32/file_error.cxx @@ -0,0 +1,125 @@ +/* -*- 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 "file_error.hxx" +#include <winerror.h> + +namespace { + +/* OS error to oslFileError values mapping table */ +struct osl_file_error_entry +{ + unsigned long oscode; /* OS return value */ + int errnocode; /* oslFileError code */ +}; + +} + +const struct osl_file_error_entry errtable[] = { + { ERROR_SUCCESS, osl_File_E_None }, /* 0 */ + { ERROR_INVALID_FUNCTION, osl_File_E_INVAL }, /* 1 */ + { ERROR_FILE_NOT_FOUND, osl_File_E_NOENT }, /* 2 */ + { ERROR_PATH_NOT_FOUND, osl_File_E_NOENT }, /* 3 */ + { ERROR_TOO_MANY_OPEN_FILES, osl_File_E_MFILE }, /* 4 */ + { ERROR_ACCESS_DENIED, osl_File_E_ACCES }, /* 5 */ + { ERROR_INVALID_HANDLE, osl_File_E_BADF }, /* 6 */ + { ERROR_ARENA_TRASHED, osl_File_E_NOMEM }, /* 7 */ + { ERROR_NOT_ENOUGH_MEMORY, osl_File_E_NOMEM }, /* 8 */ + { ERROR_INVALID_BLOCK, osl_File_E_NOMEM }, /* 9 */ + { ERROR_BAD_ENVIRONMENT, osl_File_E_2BIG }, /* 10 */ + { ERROR_BAD_FORMAT, osl_File_E_NOEXEC }, /* 11 */ + { ERROR_INVALID_ACCESS, osl_File_E_INVAL }, /* 12 */ + { ERROR_INVALID_DATA, osl_File_E_INVAL }, /* 13 */ + { ERROR_INVALID_DRIVE, osl_File_E_NOENT }, /* 15 */ + { ERROR_CURRENT_DIRECTORY, osl_File_E_ACCES }, /* 16 */ + { ERROR_NOT_SAME_DEVICE, osl_File_E_XDEV }, /* 17 */ + { ERROR_NO_MORE_FILES, osl_File_E_NOENT }, /* 18 */ + { ERROR_NOT_READY, osl_File_E_NOTREADY }, /* 21 */ + { ERROR_SHARING_VIOLATION, osl_File_E_ACCES }, /* 32 */ + { ERROR_LOCK_VIOLATION, osl_File_E_ACCES }, /* 33 */ + { ERROR_BAD_NETPATH, osl_File_E_NOENT }, /* 53 */ + { ERROR_NETWORK_ACCESS_DENIED, osl_File_E_ACCES }, /* 65 */ + { ERROR_BAD_NET_NAME, osl_File_E_NOENT }, /* 67 */ + { ERROR_FILE_EXISTS, osl_File_E_EXIST }, /* 80 */ + { ERROR_CANNOT_MAKE, osl_File_E_ACCES }, /* 82 */ + { ERROR_FAIL_I24, osl_File_E_ACCES }, /* 83 */ + { ERROR_INVALID_PARAMETER, osl_File_E_INVAL }, /* 87 */ + { ERROR_NO_PROC_SLOTS, osl_File_E_AGAIN }, /* 89 */ + { ERROR_DRIVE_LOCKED, osl_File_E_ACCES }, /* 108 */ + { ERROR_BROKEN_PIPE, osl_File_E_PIPE }, /* 109 */ + { ERROR_DISK_FULL, osl_File_E_NOSPC }, /* 112 */ + { ERROR_INVALID_TARGET_HANDLE, osl_File_E_BADF }, /* 114 */ + { ERROR_INVALID_NAME, osl_File_E_NOENT }, /* 123 */ + { ERROR_INVALID_HANDLE, osl_File_E_INVAL }, /* 124 */ + { ERROR_WAIT_NO_CHILDREN, osl_File_E_CHILD }, /* 128 */ + { ERROR_CHILD_NOT_COMPLETE, osl_File_E_CHILD }, /* 129 */ + { ERROR_DIRECT_ACCESS_HANDLE, osl_File_E_BADF }, /* 130 */ + { ERROR_NEGATIVE_SEEK, osl_File_E_INVAL }, /* 131 */ + { ERROR_SEEK_ON_DEVICE, osl_File_E_ACCES }, /* 132 */ + { ERROR_DIR_NOT_EMPTY, osl_File_E_NOTEMPTY }, /* 145 */ + { ERROR_NOT_LOCKED, osl_File_E_ACCES }, /* 158 */ + { ERROR_BAD_PATHNAME, osl_File_E_NOENT }, /* 161 */ + { ERROR_MAX_THRDS_REACHED, osl_File_E_AGAIN }, /* 164 */ + { ERROR_LOCK_FAILED, osl_File_E_ACCES }, /* 167 */ + { ERROR_ALREADY_EXISTS, osl_File_E_EXIST }, /* 183 */ + { ERROR_FILENAME_EXCED_RANGE, osl_File_E_NOENT }, /* 206 */ + { ERROR_NESTING_NOT_ALLOWED, osl_File_E_AGAIN }, /* 215 */ + { ERROR_FILE_CHECKED_OUT, osl_File_E_ACCES }, /* 220 */ + { ERROR_DIRECTORY, osl_File_E_NOTDIR }, /* 267 */ + { ERROR_NOT_ENOUGH_QUOTA, osl_File_E_NOMEM }, /* 1816 */ + { ERROR_CANT_ACCESS_FILE, osl_File_E_ACCES }, /* 1920 */ + { ERROR_UNEXP_NET_ERR, osl_File_E_NETWORK } /* 59 */ +}; + +/* The following two constants must be the minimum and maximum + values in the (contiguous) range of osl_File_E_xec Failure errors. +*/ +#define MIN_EXEC_ERROR ERROR_INVALID_STARTING_CODESEG +#define MAX_EXEC_ERROR ERROR_INFLOOP_IN_RELOC_CHAIN + +/* These are the low and high value in the range of errors that are + access violations +*/ +#define MIN_EACCES_RANGE ERROR_WRITE_PROTECT +#define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED + +oslFileError oslTranslateFileError (/*DWORD*/ unsigned long dwError) +{ + static const int n = SAL_N_ELEMENTS(errtable); + + int i; + for (i = 0; i < n; ++i ) + { + if (dwError == errtable[i].oscode) + return static_cast<oslFileError>(errtable[i].errnocode); + } + + /* The error code wasn't in the table. We check for a range of + osl_File_E_ACCES errors or exec failure errors (ENOEXEC). + Otherwise osl_File_E_INVAL is returned. + */ + if ( (dwError >= MIN_EACCES_RANGE) && (dwError <= MAX_EACCES_RANGE) ) + return osl_File_E_ACCES; + else if ( (dwError >= MIN_EXEC_ERROR) && (dwError <= MAX_EXEC_ERROR) ) + return osl_File_E_NOEXEC; + else + return osl_File_E_INVAL; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file_error.hxx b/sal/osl/w32/file_error.hxx new file mode 100644 index 000000000..4da87c132 --- /dev/null +++ b/sal/osl/w32/file_error.hxx @@ -0,0 +1,29 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_W32_FILE_ERROR_HXX +#define INCLUDED_SAL_OSL_W32_FILE_ERROR_HXX + +#include <osl/file.h> + +oslFileError oslTranslateFileError(/*DWORD*/ unsigned long dwError); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file_url.cxx b/sal/osl/w32/file_url.cxx new file mode 100644 index 000000000..8c525042d --- /dev/null +++ b/sal/osl/w32/file_url.cxx @@ -0,0 +1,990 @@ +/* -*- 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 <sal/log.hxx> + +#include <algorithm> +#include <optional> +#include <stack> +#include <string_view> + +#include <systools/win32/uwinapi.h> + +#include "file_url.hxx" +#include "file_error.hxx" + +#include <rtl/alloc.h> +#include <rtl/character.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/mutex.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/string_view.hxx> + +#include "path_helper.hxx" + +// FileURL functions + +namespace +{ +constexpr std::u16string_view WSTR_SYSTEM_ROOT_PATH = u"\\\\.\\"; +constexpr std::u16string_view WSTR_LONG_PATH_PREFIX = u"\\\\?\\"; +constexpr std::u16string_view WSTR_LONG_PATH_PREFIX_UNC = u"\\\\?\\UNC\\"; + +// Internal functions that expect only backslashes as path separators + +bool startsWithDriveColon(std::u16string_view s) +{ + return s.length() >= 2 && rtl::isAsciiAlpha(s[0]) && s[1] == ':'; +} + +bool startsWithDriveColonSlash(std::u16string_view s) +{ + return s.length() >= 3 && startsWithDriveColon(s) && s[2] == '\\'; +} + +bool startsWithSlashSlash(std::u16string_view s) { return o3tl::starts_with(s, u"\\\\"); } + +// An absolute path starts either with \\ (an UNC or device path like \\.\ or \\?\) +// or with a ASCII alpha character followed by colon followed by backslash. +bool isAbsolute(std::u16string_view s) +{ + return startsWithSlashSlash(s) || startsWithDriveColonSlash(s); +} + +bool onSameDrive(std::u16string_view s1, std::u16string_view s2) +{ + assert(startsWithDriveColon(s1) && startsWithDriveColon(s2)); + return rtl::toAsciiUpperCase(s1[0]) == rtl::toAsciiUpperCase(s2[0]) && s1[1] == s2[1]; +} + +sal_Int32 getRootLength(std::u16string_view path) +{ + assert(isAbsolute(path)); + size_t nResult = 0; + if (startsWithSlashSlash(path)) + { + // Cases: + // 1. Device UNC: \\?\UNC\server\share or \\.\UNC\server\share + // 2. Non-device UNC: \\server\share + // 3. Device non-UNC: \\?\C: or \\.\C: + bool bUNC = false; + if (path.length() > 3 && (path[2] == '.' || path[2] == '?') && path[3] == '\\') + { + if (path.substr(4, 4) == u"UNC\\") + { + // \\?\UNC\server\share or \\.\UNC\server\share + nResult = 8; + bUNC = true; + } + else + { + // \\?\C: or \\.\C: + assert(startsWithDriveColon(path.substr(4))); + nResult = 6; + } + } + else + { + // \\server\share + nResult = 2; + bUNC = true; + } + if (bUNC) + { + // \\?\UNC\server\share or \\.\UNC\server\share or \\server\share + assert(nResult < path.length() && path[nResult] != '\\'); + // Skip server name and share name + for (int nSlashes = 0; nResult < path.length(); ++nResult) + { + if (path[nResult] == '\\' && ++nSlashes == 2) + break; + } + } + } + else + { + // C: + assert(startsWithDriveColon(path)); + nResult = 2; + } + return std::min(nResult, path.length()); +} + +std::u16string_view pathView(std::u16string_view path, bool bOnlyRoot) +{ + return bOnlyRoot ? path.substr(0, getRootLength(path)) : path; +} + +OUString combinePath(std::u16string_view basePath, std::u16string_view relPath) +{ + const bool needSep = !o3tl::ends_with(basePath, u'\\'); + const auto sSeparator = needSep ? std::u16string_view(u"\\") : std::u16string_view(); + if (o3tl::starts_with(relPath, u'\\')) + relPath.remove_prefix(1); // avoid two adjacent backslashes + return OUString::Concat(basePath) + sSeparator + relPath; +} + +OUString removeRelativeParts(const OUString& p) +{ + const sal_Int32 rootPos = getRootLength(p); + OUStringBuffer buf(p.getLength()); + buf.append(p.subView(0, rootPos)); + std::stack<sal_Int32> partPositions; + bool bAfterSlash = false; + for (sal_Int32 i = rootPos; i < p.getLength(); ++i) + { + sal_Unicode c = p[i]; + if (c == '\\') + { + if (i + 1 < p.getLength() && p[i + 1] == '.') + { + if (i + 2 == p.getLength() || p[i + 2] == '\\') + { + // 1. Skip current directory (\.\ or trailing \.) + ++i; // process next slash: it may start another "\.\" + } + else if (p[i + 2] == '.' && (i + 3 == p.getLength() || p[i + 3] == '\\')) + { + // 2. For parent directory (\..\), drop previous part and skip + if (bAfterSlash && partPositions.size()) + partPositions.pop(); + sal_Int32 nParentPos = partPositions.size() ? partPositions.top() : rootPos; + if (partPositions.size()) + partPositions.pop(); + buf.truncate(nParentPos); + bAfterSlash = false; // we have just removed slash after parent part + i += 2; // process next slash: it may start another "\.\" + } + } + if (bAfterSlash) + continue; // 3. Skip double backslashes (\\) + partPositions.push(buf.getLength()); + bAfterSlash = true; + } + else + bAfterSlash = false; + + buf.append(c); + } + return buf.makeStringAndClear(); +} +} + +static bool IsValidFilePathComponent( + std::optional<std::u16string_view>& roComponent, + DWORD dwFlags) +{ + assert(roComponent); + auto lpComponentEnd = roComponent->end(); + auto lpCurrent = roComponent->begin(); + bool bValid = lpCurrent != lpComponentEnd; // Empty components are not allowed + sal_Unicode cLast = 0; + + while (bValid) + { + /* Path component length must not exceed MAX_PATH even if long path with "\\?\" prefix is used */ + if (lpCurrent - roComponent->begin() >= MAX_PATH) + { + bValid = false; + break; + } + + switch ( *lpCurrent ) + { + /* Both backslash and slash determine the end of a path component */ + case '\0': + case '/': + case '\\': + switch ( cLast ) + { + /* Component must not end with '.' or blank and can't be empty */ + + case '.': + if ( dwFlags & VALIDATEPATH_ALLOW_ELLIPSE ) + { + if ( (dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD) || + 1 == lpCurrent - roComponent->begin() ) + { + /* Either do allow periods anywhere, or current directory */ + lpComponentEnd = lpCurrent; + break; + } + else if ( 2 == lpCurrent - roComponent->begin() && '.' == roComponent->front() ) + { + /* Parent directory is O.K. */ + lpComponentEnd = lpCurrent; + break; + } + } + [[fallthrough]]; + case 0: + case ' ': + if ( dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD ) + lpComponentEnd = lpCurrent; + else + bValid = false; + break; + default: + lpComponentEnd = lpCurrent; + break; + } + break; + /* The following characters are reserved */ + case '?': + case '*': + case '<': + case '>': + case '\"': + case '|': + case ':': + bValid = false; + break; + default: + /* Characters below ASCII 32 are not allowed */ + if ( *lpCurrent < ' ' ) + bValid = false; + break; + } + + if (lpCurrent != lpComponentEnd) + cLast = *lpCurrent++; + + if (lpCurrent == lpComponentEnd) + break; + } + + if ( bValid ) + { + // Empty components are not allowed + if (lpComponentEnd - roComponent->begin() < 1) + bValid = false; + // If we reached the end of the string nullopt is returned + else if (lpComponentEnd == roComponent->end()) + roComponent.reset(); + else + roComponent->remove_prefix(lpComponentEnd - roComponent->begin()); + } + + return bValid; +} + +static sal_Int32 countInitialSeparators(std::u16string_view path) { + size_t n = 0; + while (n < path.length() && (path[n] == '\\' || path[n] == '/')) + ++n; + return n; +} + +DWORD IsValidFilePath(const OUString& path, DWORD dwFlags, OUString* corrected) +{ + std::optional<std::u16string_view> oComponent = path; + bool bValid = true; + DWORD dwPathType = PATHTYPE_ERROR; + + if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE ) + dwFlags |= VALIDATEPATH_ALLOW_ELLIPSE; + + DWORD dwCandidatPathType = PATHTYPE_ERROR; + + if (path.matchIgnoreAsciiCase(WSTR_LONG_PATH_PREFIX_UNC)) + { + /* This is long path in UNC notation */ + oComponent = path.subView(WSTR_LONG_PATH_PREFIX_UNC.size()); + dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC | PATHTYPE_IS_LONGPATH; + } + else if (path.matchIgnoreAsciiCase(WSTR_LONG_PATH_PREFIX)) + { + /* This is long path */ + oComponent = path.subView(WSTR_LONG_PATH_PREFIX.size()); + + if (startsWithDriveColon(*oComponent)) + { + oComponent->remove_prefix(2); + dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL | PATHTYPE_IS_LONGPATH; + } + } + else if ( 2 == countInitialSeparators(path) ) + { + /* The UNC path notation */ + oComponent = path.subView(2); + dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC; + } + else if (startsWithDriveColon(path)) + { + /* Local path verification. Must start with <drive>: */ + oComponent = path.subView(2); + dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL; + } + + if ( ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_UNC ) + { + bValid = IsValidFilePathComponent(oComponent, VALIDATEPATH_ALLOW_ELLIPSE); + + /* So far we have a valid servername. Now let's see if we also have a network resource */ + + dwPathType = dwCandidatPathType; + + if ( bValid ) + { + if (oComponent) + { + oComponent->remove_prefix(1); + if (oComponent->empty()) + oComponent.reset(); + } + + if (!oComponent) + { + dwPathType |= PATHTYPE_IS_SERVER; + } + else + { + /* Now test the network resource */ + + bValid = IsValidFilePathComponent(oComponent, 0); + + /* If we now reached the end of the path, everything is O.K. */ + + if (bValid) + { + if (oComponent) + { + oComponent->remove_prefix(1); + if (oComponent->empty()) + oComponent.reset(); + } + if (!oComponent) + dwPathType |= PATHTYPE_IS_VOLUME; + } + } + } + } + else if ( ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_LOCAL ) + { + if (1 == countInitialSeparators(*oComponent)) + oComponent->remove_prefix(1); + else if (!oComponent->empty()) + bValid = false; + + dwPathType = dwCandidatPathType; + + /* Now we are behind the backslash or it was a simple drive without backslash */ + + if (bValid && oComponent->empty()) + { + oComponent.reset(); + dwPathType |= PATHTYPE_IS_VOLUME; + } + } + else if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE ) + { + /* Can be a relative path */ + oComponent = path; + + /* Relative path can start with a backslash */ + + if (1 == countInitialSeparators(*oComponent)) + { + oComponent->remove_prefix(1); + if (oComponent->empty()) + oComponent.reset(); + } + + dwPathType = PATHTYPE_RELATIVE; + } + else + { + /* Anything else is an error */ + bValid = false; + } + + /* Now validate each component of the path */ + OUString lastCorrected = path; + while (bValid && oComponent) + { + // Correct path by merging consecutive slashes: + if (o3tl::starts_with(*oComponent, u"\\") && corrected != nullptr) { + sal_Int32 i = oComponent->data() - lastCorrected.getStr(); + *corrected = lastCorrected.replaceAt(i, 1, {}); + //TODO: handle out-of-memory + lastCorrected = *corrected; + oComponent = lastCorrected.subView(i); + } + + bValid = IsValidFilePathComponent(oComponent, dwFlags | VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD); + + if (bValid && oComponent) + { + oComponent->remove_prefix(1); + + /* If the string behind the backslash is empty, we've done */ + + if (oComponent->empty()) + oComponent.reset(); + } + } + + /* The path can be longer than MAX_PATH only in case it has the longpath prefix */ + if (bValid && !(dwPathType & PATHTYPE_IS_LONGPATH) && path.getLength() >= MAX_PATH) + { + bValid = false; + } + + return bValid ? dwPathType : PATHTYPE_ERROR; +} + +static std::optional<OUString> osl_decodeURL_(const OString& sUTF8) +{ + const char *pSrcEnd; + const char *pSrc; + bool bValidEncoded = true; /* Assume success */ + + /* The resulting decoded string length is shorter or equal to the source length */ + + const sal_Int32 nSrcLen = sUTF8.getLength(); + OStringBuffer aBuffer(nSrcLen + 1); + + pSrc = sUTF8.getStr(); + pSrcEnd = pSrc + nSrcLen; + + /* Now decode the URL what should result in a UTF-8 string */ + while ( bValidEncoded && pSrc < pSrcEnd ) + { + switch ( *pSrc ) + { + case '%': + { + char aToken[3]; + char aChar; + + pSrc++; + aToken[0] = *pSrc++; + aToken[1] = *pSrc++; + aToken[2] = 0; + + aChar = static_cast<char>(strtoul( aToken, nullptr, 16 )); + + /* The chars are path delimiters and must not be encoded */ + + if ( 0 == aChar || '\\' == aChar || '/' == aChar || ':' == aChar ) + bValidEncoded = false; + else + aBuffer.append(aChar); + } + break; + case '\0': + case '#': + case '?': + bValidEncoded = false; + break; + default: + aBuffer.append(*pSrc++); + break; + } + } + + return bValidEncoded ? OUString(aBuffer.getStr(), aBuffer.getLength(), RTL_TEXTENCODING_UTF8) + : std::optional<OUString>(); +} + +static OUString osl_encodeURL_(std::u16string_view sURL) +{ + /* Encode non ascii characters within the URL */ + + const char *pURLScan; + sal_Int32 nURLScanLen; + sal_Int32 nURLScanCount; + + OString sUTF8 = OUStringToOString(sURL, RTL_TEXTENCODING_UTF8); + + OUStringBuffer sEncodedURL(sUTF8.getLength() * 3 + 1); + pURLScan = sUTF8.getStr(); + nURLScanLen = sUTF8.getLength(); + nURLScanCount = 0; + + while ( nURLScanCount < nURLScanLen ) + { + char cCurrent = *pURLScan; + switch ( cCurrent ) + { + default: + if (!( ( cCurrent >= 'a' && cCurrent <= 'z' ) || ( cCurrent >= 'A' && cCurrent <= 'Z' ) || ( cCurrent >= '0' && cCurrent <= '9' ) ) ) + { + char buf[3]; + sprintf( buf, "%02X", static_cast<unsigned char>(cCurrent) ); + sEncodedURL.append('%').appendAscii(buf, 2); + break; + } + [[fallthrough]]; + case '!': + case '\'': + case '(': + case ')': + case '*': + case '-': + case '.': + case '_': + case '~': + case '$': + case '&': + case '+': + case ',': + case '=': + case '@': + case ':': + case '/': + case '\\': + case '|': + sEncodedURL.appendAscii(&cCurrent, 1); + break; + case 0: + break; + } + + pURLScan++; + nURLScanCount++; + } + + return sEncodedURL.makeStringAndClear(); +} + +// A helper that makes sure that for existing part of the path, the case is correct. +// Unlike GetLongPathNameW that it wraps, this function does not require the path to exist. +static OUString GetCaseCorrectPathName(std::u16string_view sysPath) +{ + // Prepare a null-terminated string first. + // Neither OUString, nor u16string_view are guaranteed to be null-terminated + osl::LongPathBuffer<wchar_t> szPath(sysPath.size() + WSTR_LONG_PATH_PREFIX_UNC.size() + 1); + wchar_t* const pPath = szPath; + wchar_t* pEnd = pPath; + size_t sysPathOffset = 0; + if (sysPath.size() >= MAX_PATH && isAbsolute(sysPath) + && !o3tl::starts_with(sysPath, WSTR_LONG_PATH_PREFIX)) + { + // Allow GetLongPathNameW consume long paths + std::u16string_view prefix = WSTR_LONG_PATH_PREFIX; + if (startsWithSlashSlash(sysPath)) + { + sysPathOffset = 2; // skip leading "\\" + prefix = WSTR_LONG_PATH_PREFIX_UNC; + } + pEnd = std::copy(prefix.begin(), prefix.end(), pEnd); + } + wchar_t* const pStart = pEnd; + pEnd = std::copy(sysPath.begin() + sysPathOffset, sysPath.end(), pStart); + *pEnd = 0; + osl::LongPathBuffer<wchar_t> aBuf(MAX_LONG_PATH); + while (pEnd > pStart) + { + std::u16string_view curPath(o3tl::toU(pPath), pEnd - pPath); + if (curPath == u"\\\\" || curPath == WSTR_SYSTEM_ROOT_PATH + || curPath == WSTR_LONG_PATH_PREFIX + || o3tl::equalsIgnoreAsciiCase(curPath, WSTR_LONG_PATH_PREFIX_UNC)) + break; // Do not check if the special path prefix exists itself + + DWORD nNewLen = GetLongPathNameW(pPath, aBuf, aBuf.getBufSizeInSymbols()); + if (nNewLen == 0) + { + // Error? + const DWORD err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + { + // Check the base path; skip possible trailing separator + size_t sepPos = curPath.substr(0, curPath.size() - 1).rfind(u'\\'); + if (sepPos != std::u16string_view::npos) + { + pEnd = pPath + sepPos; + *pEnd = 0; + continue; + } + } + else + { + SAL_WARN("sal.osl", "GetLongPathNameW: Windows error code " + << err << " processing path " << OUString(curPath)); + } + break; // All other errors, or no separators left + } + assert(nNewLen < aBuf.getBufSizeInSymbols()); + // Combine the case-correct leading part with the non-existing trailing part + return OUString::Concat(std::u16string_view(o3tl::toU(aBuf), nNewLen)) + + sysPath.substr(pEnd - pStart + sysPathOffset); + }; + return OUString(sysPath); // We found no existing parts - just assume it's OK +} + +oslFileError osl_getSystemPathFromFileURL_(const OUString& strURL, rtl_uString **pustrPath, bool bAllowRelative) +{ + OUString sTempPath; + oslFileError nError = osl_File_E_INVAL; /* Assume failure */ + + /* If someone hasn't encoded the complete URL we convert it to UTF8 now to prevent from + having a mixed encoded URL later */ + + OString sUTF8 = OUStringToOString(strURL, RTL_TEXTENCODING_UTF8); + + /* If the length of strUTF8 and strURL differs it indicates that the URL was not correct encoded */ + + SAL_WARN_IF( + sUTF8.getLength() != strURL.getLength() && + strURL.matchIgnoreAsciiCase("file:\\") + , "sal.osl" + ,"osl_getSystemPathFromFileURL: \"" << strURL << "\" is not encoded !!!"); + + if (auto sDecodedURL = osl_decodeURL_(sUTF8)) + { + /* Replace backslashes and pipes */ + + sDecodedURL = sDecodedURL->replace('/', '\\').replace('|', ':'); + + /* Must start with "file:/" */ + if ( sDecodedURL->startsWithIgnoreAsciiCase("file:\\") ) + { + sal_uInt32 nSkip; + + if ( sDecodedURL->startsWithIgnoreAsciiCase("file:\\\\\\") ) + nSkip = 8; + else if ( + sDecodedURL->startsWithIgnoreAsciiCase("file:\\\\localhost\\") || + sDecodedURL->startsWithIgnoreAsciiCase("file:\\\\127.0.0.1\\") + ) + nSkip = 17; + else if ( sDecodedURL->startsWithIgnoreAsciiCase("file:\\\\") ) + nSkip = 5; + else + nSkip = 6; + + const sal_uInt32 nDecodedLen = sDecodedURL->getLength(); + + /* Indicates local root */ + if ( nDecodedLen == nSkip ) + sTempPath = WSTR_SYSTEM_ROOT_PATH; + else + { + /* do not separate the directory and file case, so the maximal path length without prefix is MAX_PATH-12 */ + if ( nDecodedLen - nSkip <= MAX_PATH - 12 ) + { + sTempPath = sDecodedURL->subView(nSkip); + } + else + { + sDecodedURL = GetCaseCorrectPathName(sDecodedURL->subView(nSkip)); + if (sDecodedURL->getLength() <= MAX_PATH - 12 + || sDecodedURL->startsWith(WSTR_SYSTEM_ROOT_PATH) + || sDecodedURL->startsWith(WSTR_LONG_PATH_PREFIX)) + { + sTempPath = *sDecodedURL; + } + else if (sDecodedURL->startsWith("\\\\")) + { + /* it should be an UNC path, use the according prefix */ + sTempPath = OUString::Concat(WSTR_LONG_PATH_PREFIX_UNC) + sDecodedURL->subView(2); + } + else + { + sTempPath = WSTR_LONG_PATH_PREFIX + *sDecodedURL; + } + } + } + + if (IsValidFilePath(sTempPath, VALIDATEPATH_ALLOW_ELLIPSE, &sTempPath)) + nError = osl_File_E_None; + } + else if ( bAllowRelative ) /* This maybe a relative file URL */ + { + /* In future the relative path could be converted to absolute if it is too long */ + sTempPath = *sDecodedURL; + + if (IsValidFilePath(sTempPath, VALIDATEPATH_ALLOW_RELATIVE | VALIDATEPATH_ALLOW_ELLIPSE, &sTempPath)) + nError = osl_File_E_None; + } + else + SAL_INFO_IF(nError, "sal.osl", + "osl_getSystemPathFromFileURL: \"" << strURL << "\" is not an absolute FileURL"); + + } + + if ( osl_File_E_None == nError ) + rtl_uString_assign(pustrPath, sTempPath.pData); + + SAL_INFO_IF(nError, "sal.osl", + "osl_getSystemPathFromFileURL: \"" << strURL << "\" is not a FileURL"); + + return nError; +} + +oslFileError osl_getFileURLFromSystemPath( rtl_uString* strPath, rtl_uString** pstrURL ) +{ + oslFileError nError = osl_File_E_INVAL; /* Assume failure */ + OUString sTempURL; + DWORD dwPathType = PATHTYPE_ERROR; + + if (strPath) + dwPathType = IsValidFilePath(OUString::unacquired(&strPath), VALIDATEPATH_ALLOW_RELATIVE, nullptr); + + if (dwPathType) + { + OUString sTempPath; + const OUString& sPath = OUString::unacquired(&strPath); + + if ( dwPathType & PATHTYPE_IS_LONGPATH ) + { + /* the path has the longpath prefix, lets remove it */ + switch ( dwPathType & PATHTYPE_MASK_TYPE ) + { + case PATHTYPE_ABSOLUTE_UNC: + static_assert(WSTR_LONG_PATH_PREFIX_UNC.size() == 8, + "Unexpected long path UNC prefix!"); + + /* generate the normal UNC path */ + sTempPath = "\\\\" + sPath.copy(8).replace('\\', '/'); + break; + + case PATHTYPE_ABSOLUTE_LOCAL: + static_assert(WSTR_LONG_PATH_PREFIX.size() == 4, + "Unexpected long path prefix!"); + + /* generate the normal path */ + sTempPath = sPath.copy(4).replace('\\', '/'); + break; + + default: + OSL_FAIL( "Unexpected long path format!" ); + sTempPath = sPath.replace('\\', '/'); + break; + } + } + else + { + /* Replace backslashes */ + sTempPath = sPath.replace('\\', '/'); + } + + switch ( dwPathType & PATHTYPE_MASK_TYPE ) + { + case PATHTYPE_RELATIVE: + sTempURL = sTempPath; + nError = osl_File_E_None; + break; + case PATHTYPE_ABSOLUTE_UNC: + sTempURL = "file:" + sTempPath; + nError = osl_File_E_None; + break; + case PATHTYPE_ABSOLUTE_LOCAL: + sTempURL = "file:///" + sTempPath; + nError = osl_File_E_None; + break; + default: + break; + } + } + + if ( osl_File_E_None == nError ) + { + /* Encode the URL */ + rtl_uString_assign(pstrURL, osl_encodeURL_(sTempURL).pData); + OSL_ASSERT(*pstrURL != nullptr); + } + + SAL_INFO_IF(nError, "sal.osl", + "osl_getFileURLFromSystemPath: \"" << OUString::unacquired(&strPath) << "\" is not a systemPath"); + return nError; +} + +oslFileError SAL_CALL osl_getSystemPathFromFileURL( + rtl_uString *ustrURL, rtl_uString **pustrPath) +{ + return osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrURL), pustrPath, true); +} + +oslFileError SAL_CALL osl_searchFileURL( + rtl_uString *ustrFileName, + rtl_uString *ustrSystemSearchPath, + rtl_uString **pustrPath) +{ + OUString ustrUNCPath; + OUString ustrSysPath; + oslFileError error; + + /* First try to interpret the file name as a URL even a relative one */ + error = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrFileName), &ustrUNCPath.pData, true); + + /* So far we either have an UNC path or something invalid + Now create a system path */ + if ( osl_File_E_None == error ) + error = osl_getSystemPathFromFileURL_(ustrUNCPath, &ustrSysPath.pData, true); + + if ( osl_File_E_None == error ) + { + DWORD nBufferLength; + DWORD dwResult; + LPWSTR lpBuffer = nullptr; + LPWSTR lpszFilePart; + + /* Repeat calling SearchPath ... + Start with MAX_PATH for the buffer. In most cases this + will be enough and does not force the loop to run twice */ + dwResult = MAX_PATH; + + do + { + /* If search path is empty use a nullptr pointer instead according to MSDN documentation of SearchPath */ + LPCWSTR lpszSearchPath = ustrSystemSearchPath && ustrSystemSearchPath->length ? o3tl::toW(ustrSystemSearchPath->buffer) : nullptr; + LPCWSTR lpszSearchFile = o3tl::toW(ustrSysPath.getStr()); + + /* Allocate space for buffer according to previous returned count of required chars */ + /* +1 is not necessary if we follow MSDN documentation but for robustness we do so */ + nBufferLength = dwResult + 1; + lpBuffer = lpBuffer ? + static_cast<LPWSTR>(realloc(lpBuffer, nBufferLength * sizeof(WCHAR))) : + static_cast<LPWSTR>(malloc(nBufferLength * sizeof(WCHAR))); + + dwResult = SearchPathW( lpszSearchPath, lpszSearchFile, nullptr, nBufferLength, lpBuffer, &lpszFilePart ); + } while ( dwResult && dwResult >= nBufferLength ); + + /* ... until an error occurs or buffer is large enough. + dwResult == nBufferLength can not happen according to documentation but lets be robust ;-) */ + + if ( dwResult ) + { + ustrSysPath = o3tl::toU(lpBuffer); + error = osl_getFileURLFromSystemPath(ustrSysPath.pData, pustrPath); + } + else + { + WIN32_FIND_DATAW aFindFileData; + HANDLE hFind; + + /* something went wrong, perhaps the path was absolute */ + error = oslTranslateFileError( GetLastError() ); + + hFind = FindFirstFileW(o3tl::toW(ustrSysPath.getStr()), &aFindFileData); + + if ( IsValidHandle(hFind) ) + { + error = osl_getFileURLFromSystemPath(ustrSysPath.pData, pustrPath); + FindClose( hFind ); + } + } + + free( lpBuffer ); + } + + return error; +} + +oslFileError SAL_CALL osl_getAbsoluteFileURL( rtl_uString* ustrBaseURL, rtl_uString* ustrRelativeURL, rtl_uString** pustrAbsoluteURL ) +{ + oslFileError eError = osl_File_E_None; + OUString ustrRelSysPath; + OUString ustrBaseSysPath; + + if ( ustrBaseURL && ustrBaseURL->length ) + { + eError = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrBaseURL), &ustrBaseSysPath.pData, false); + OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with relative or invalid base URL" ); + } + if (eError == osl_File_E_None) + { + eError = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrRelativeURL), &ustrRelSysPath.pData, + !ustrBaseSysPath.isEmpty()); + OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with empty base URL and/or invalid relative URL" ); + } + + if ( !eError ) + { + OUString sResultPath; +/*@@@ToDo + The whole FileURL implementation should be merged + with the rtl/uri class. +*/ + // If ustrRelSysPath is absolute, we don't need ustrBaseSysPath. + if (!ustrBaseSysPath.isEmpty() && !isAbsolute(ustrRelSysPath)) + { + // ustrBaseSysPath is known here to be a valid absolute path -> its first two characters + // are ASCII (either alpha + colon, or double backslashes) + + // Don't use SetCurrentDirectoryW together with GetFullPathNameW, because: + // (a) it needs synchronization and may affect threads that may access relative paths; + // (b) it would give wrong results for non-existing base path (allowed by RFC2396). + + if (startsWithDriveColon(ustrRelSysPath)) + { + // Special case: a path relative to a specific drive's current directory. + // Should we error out here? + + // If ustrBaseSysPath is on the same drive as ustrRelSysPath, then take base path + // as is; otherwise, use current directory on ustrRelSysPath's drive as base path + if (onSameDrive(ustrRelSysPath, ustrBaseSysPath)) + { + sResultPath = combinePath(ustrBaseSysPath, ustrRelSysPath.subView(2)); + } + else + { + // Call GetFullPathNameW to get current directory on ustrRelSysPath's drive + wchar_t baseDrive[3] = { ustrRelSysPath[0], ':' }; // just "C:" + osl::LongPathBuffer<wchar_t> aBuf(MAX_LONG_PATH); + DWORD dwResult + = GetFullPathNameW(baseDrive, aBuf.getBufSizeInSymbols(), aBuf, nullptr); + if (dwResult) + { + if (dwResult >= aBuf.getBufSizeInSymbols()) + eError = osl_File_E_INVAL; + else + sResultPath = combinePath(o3tl::toU(aBuf), ustrRelSysPath.subView(2)); + } + else + eError = oslTranslateFileError(GetLastError()); + } + } + else + { + // Is this a rooted relative path (starting with a backslash)? + // Then we need only root from base. E.g., + // ustrBaseSysPath is "\\server\share\path1\" and ustrRelSysPath is "\path2\to\file" + // => \\server\share\path2\to\file + // ustrBaseSysPath is "D:\path1\" and ustrRelSysPath is "\path2\to\file" + // => D:\path2\to\file + auto sBaseView(pathView(ustrBaseSysPath, ustrRelSysPath.startsWith("\\"))); + sResultPath = combinePath(sBaseView, ustrRelSysPath); + } + } + else + sResultPath = ustrRelSysPath; + + if (eError == osl_File_E_None) + { + sResultPath = removeRelativeParts(sResultPath); + eError = osl_getFileURLFromSystemPath(sResultPath.pData, pustrAbsoluteURL); + } + } + + return eError; +} + +oslFileError SAL_CALL osl_getCanonicalName( rtl_uString *strRequested, rtl_uString **strValid ) +{ + rtl_uString_newFromString(strValid, strRequested); + return osl_File_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/file_url.hxx b/sal/osl/w32/file_url.hxx new file mode 100644 index 000000000..86ce27060 --- /dev/null +++ b/sal/osl/w32/file_url.hxx @@ -0,0 +1,63 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_W32_FILE_URL_HXX +#define INCLUDED_SAL_OSL_W32_FILE_URL_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <osl/file.h> +#include <osl/mutex.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#define PATHTYPE_ERROR 0 +#define PATHTYPE_RELATIVE 1 +#define PATHTYPE_ABSOLUTE_UNC 2 +#define PATHTYPE_ABSOLUTE_LOCAL 3 +#define PATHTYPE_MASK_TYPE 0xFF +#define PATHTYPE_IS_VOLUME 0x0100 +#define PATHTYPE_IS_SERVER 0x0200 +#define PATHTYPE_IS_LONGPATH 0x0400 + +#define VALIDATEPATH_NORMAL 0x0000 +#define VALIDATEPATH_ALLOW_ELLIPSE 0x0002 +#define VALIDATEPATH_ALLOW_RELATIVE 0x0004 +#define VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD 0x0010 + +#define MAX_LONG_PATH 32767 + +DWORD IsValidFilePath ( + const OUString& path, + DWORD dwFlags, + OUString* corrected +); + +oslFileError osl_getSystemPathFromFileURL_ ( + const OUString& strURL, + rtl_uString** pustrPath, + bool bAllowRelative +); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/filetime.hxx b/sal/osl/w32/filetime.hxx new file mode 100644 index 000000000..dc355591a --- /dev/null +++ b/sal/osl/w32/filetime.hxx @@ -0,0 +1,40 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_W32_FILETIME_HXX +#define INCLUDED_SAL_OSL_W32_FILETIME_HXX + +#include <sal/config.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <osl/time.h> + +BOOL TimeValueToFileTime(TimeValue const* cpTimeVal, FILETIME* pFTime); + +BOOL FileTimeToTimeValue(FILETIME const* cpFTime, TimeValue* pTimeVal); + +namespace osl::detail +{ +inline __int64 getFiletime(FILETIME const& ft) +{ + return (DWORD64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +} + +inline void setFiletime(FILETIME& ft, __int64 value) +{ + ft.dwHighDateTime = value >> 32; + ft.dwLowDateTime = value & 0xFFFFFFFF; +} +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/w32/interlck.cxx b/sal/osl/w32/interlck.cxx new file mode 100644 index 000000000..b9ecf6d78 --- /dev/null +++ b/sal/osl/w32/interlck.cxx @@ -0,0 +1,34 @@ +/* -*- 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 "system.h" + +#include <osl/interlck.h> + +oslInterlockedCount SAL_CALL osl_incrementInterlockedCount(oslInterlockedCount* pCount) +{ + return InterlockedIncrement(pCount); +} + +oslInterlockedCount SAL_CALL osl_decrementInterlockedCount(oslInterlockedCount* pCount) +{ + return InterlockedDecrement(pCount); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/memory.cxx b/sal/osl/w32/memory.cxx new file mode 100644 index 000000000..1f1a5b516 --- /dev/null +++ b/sal/osl/w32/memory.cxx @@ -0,0 +1,24 @@ +/* -*- 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/. + */ + +#include <oslmemory.h> + +#include <malloc.h> + +void* osl_aligned_alloc( sal_Size align, sal_Size size ) +{ + return _aligned_malloc(size, align); +} + +void osl_aligned_free( void* p ) +{ + _aligned_free(p); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/module.cxx b/sal/osl/w32/module.cxx new file mode 100644 index 000000000..8379e14b2 --- /dev/null +++ b/sal/osl/w32/module.cxx @@ -0,0 +1,202 @@ +/* -*- 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 "system.h" + +#include "file_url.hxx" +#include "path_helper.hxx" + +#include <osl/module.h> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/file.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <vector> + +/* + under WIN32, we use the void* oslModule + as a WIN32 HANDLE (which is also a 32-bit value) +*/ + +oslModule SAL_CALL osl_loadModule(rtl_uString *strModuleName, sal_Int32 /*nRtldMode*/ ) +{ + HMODULE h; +#if OSL_DEBUG_LEVEL < 2 + UINT errorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); +#endif + rtl_uString* Module = nullptr; + oslModule ret = nullptr; + oslFileError nError; + + SAL_INFO( "sal.osl", "osl_loadModule: " << OUString(strModuleName) ); + OSL_ASSERT(strModuleName); + + nError = osl_getSystemPathFromFileURL(strModuleName, &Module); + + if ( osl_File_E_None != nError ) + rtl_uString_assign(&Module, strModuleName); + + h = LoadLibraryW(o3tl::toW(Module->buffer)); + + if (h == nullptr) + h = LoadLibraryExW(o3tl::toW(Module->buffer), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + + // In case of long path names (\\?\c:\...) try to shorten the filename. + // LoadLibrary cannot handle file names which exceed 260 letters. + // In case the path is too long, the function will fail. However, the error + // code can be different. For example, it returned ERROR_FILENAME_EXCED_RANGE + // on Windows XP and ERROR_INSUFFICIENT_BUFFER on Windows 7 (64bit) + if (h == nullptr && Module->length > 260) + { + std::vector<WCHAR> vec(Module->length + 1); + DWORD len = GetShortPathNameW(o3tl::toW(Module->buffer), vec.data(), Module->length + 1); + if (len ) + { + h = LoadLibraryW(vec.data()); + + if (h == nullptr) + h = LoadLibraryExW(vec.data(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } + + ret = static_cast<oslModule>(h); + rtl_uString_release(Module); +#if OSL_DEBUG_LEVEL < 2 + SetErrorMode(errorMode); +#endif + + return ret; +} + +oslModule SAL_CALL osl_loadModuleAscii(const char *pModuleName, sal_Int32 ) +{ + HMODULE h; + UINT errorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); + oslModule ret = nullptr; + + SAL_INFO( "sal.osl", "osl_loadModule: " << pModuleName ); + OSL_ASSERT(pModuleName); + + h = LoadLibraryA(pModuleName); + if (h == nullptr) + h = LoadLibraryExA(pModuleName, nullptr, + LOAD_WITH_ALTERED_SEARCH_PATH); + + ret = static_cast<oslModule>(h); + SetErrorMode(errorMode); + + return ret; +} + +oslModule osl_loadModuleRelativeAscii( + oslGenericFunction baseModule, char const * relativePath, sal_Int32 mode) +{ + return osl_loadModuleRelative(baseModule, OUString::createFromAscii(relativePath).pData, mode); +} + +sal_Bool SAL_CALL +osl_getModuleHandle(rtl_uString *pModuleName, oslModule *pResult) +{ + LPCWSTR pName = pModuleName ? o3tl::toW(pModuleName->buffer) : nullptr; + HMODULE h = GetModuleHandleW(pName); + if( h ) + { + *pResult = static_cast<oslModule>(h); + return true; + } + + return false; +} + +void SAL_CALL osl_unloadModule(oslModule Module) +{ + FreeLibrary(static_cast<HMODULE>(Module)); +} + +void* SAL_CALL osl_getSymbol(oslModule Module, rtl_uString *strSymbolName) +{ + /* casting from a function pointer to a data pointer is invalid + be in this case unavoidable because the API has to stay + compatible. We need to keep this function which returns a + void* by definition */ + return reinterpret_cast<void*>(osl_getFunctionSymbol(Module, strSymbolName)); +} + +oslGenericFunction SAL_CALL osl_getFunctionSymbol( oslModule Module, rtl_uString *strSymbolName ) +{ + rtl_String *symbolName = nullptr; + oslGenericFunction address; + + OSL_ASSERT(Module); + OSL_ASSERT(strSymbolName); + + rtl_uString2String( + &symbolName, + strSymbolName->buffer, + strSymbolName->length, + RTL_TEXTENCODING_UTF8, + OUSTRING_TO_OSTRING_CVTFLAGS + ); + + address=osl_getAsciiFunctionSymbol(Module, rtl_string_getStr(symbolName)); + rtl_string_release(symbolName); + + return address; +} + +oslGenericFunction SAL_CALL +osl_getAsciiFunctionSymbol( oslModule Module, const char *pSymbol ) +{ + oslGenericFunction fncAddr = nullptr; + + if( pSymbol ) + fncAddr=reinterpret_cast<oslGenericFunction>(GetProcAddress(static_cast<HMODULE>(Module), pSymbol)); + + return fncAddr; +} + +sal_Bool SAL_CALL osl_getModuleURLFromAddress( void *pv, rtl_uString **pustrURL ) +{ + HMODULE hModule{}; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast<LPCWSTR>(pv), &hModule); + if (!hModule) + return false; + + ::osl::LongPathBuffer<sal_Unicode> aBuffer(MAX_LONG_PATH); + + DWORD nch = GetModuleFileNameW(hModule, o3tl::toW(aBuffer), aBuffer.getBufSizeInSymbols()); + + OUString ustrSysPath(aBuffer, nch); + return osl_getFileURLFromSystemPath(ustrSysPath.pData, pustrURL) == osl_File_E_None; +} + +sal_Bool SAL_CALL osl_getModuleURLFromFunctionAddress( oslGenericFunction addr, rtl_uString ** ppLibraryUrl ) +{ + /* casting a function pointer to a data pointer (void*) is + not allowed according to the C/C++ standards. In this case + it is unavoidable because we have to stay compatible we + cannot remove any function. */ + return osl_getModuleURLFromAddress(reinterpret_cast<void*>(addr), ppLibraryUrl); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/mutex.cxx b/sal/osl/w32/mutex.cxx new file mode 100644 index 000000000..7de4c41ee --- /dev/null +++ b/sal/osl/w32/mutex.cxx @@ -0,0 +1,88 @@ +/* -*- 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 "system.h" + +#include <osl/mutex.h> +#include <osl/diagnose.h> + +/* + Implementation notes: + The void* hidden by oslMutex points to a WIN32 + CRITICAL_SECTION structure. +*/ + +oslMutex SAL_CALL osl_createMutex(void) +{ + CRITICAL_SECTION* pMutexImpl; + + pMutexImpl = static_cast<CRITICAL_SECTION*>(calloc(sizeof(CRITICAL_SECTION), 1)); + + OSL_ASSERT(pMutexImpl); /* alloc successful? */ + + InitializeCriticalSection(pMutexImpl); + + return reinterpret_cast<oslMutex>(pMutexImpl); +} + +void SAL_CALL osl_destroyMutex(oslMutex Mutex) +{ + CRITICAL_SECTION* pMutexImpl = reinterpret_cast<CRITICAL_SECTION*>(Mutex); + + if (pMutexImpl) + { + DeleteCriticalSection(pMutexImpl); + free(pMutexImpl); + } +} + +sal_Bool SAL_CALL osl_acquireMutex(oslMutex Mutex) +{ + CRITICAL_SECTION* pMutexImpl = reinterpret_cast<CRITICAL_SECTION*>(Mutex); + + OSL_ASSERT(Mutex); + + EnterCriticalSection(pMutexImpl); + + return true; +} + +sal_Bool SAL_CALL osl_tryToAcquireMutex(oslMutex Mutex) +{ + CRITICAL_SECTION* pMutexImpl = reinterpret_cast<CRITICAL_SECTION*>(Mutex); + + OSL_ASSERT(Mutex); + + return TryEnterCriticalSection(pMutexImpl) != FALSE; +} + +sal_Bool SAL_CALL osl_releaseMutex(oslMutex Mutex) +{ + CRITICAL_SECTION* pMutexImpl = reinterpret_cast<CRITICAL_SECTION*>(Mutex); + + OSL_ASSERT(Mutex); + + LeaveCriticalSection(pMutexImpl); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/nlsupport.cxx b/sal/osl/w32/nlsupport.cxx new file mode 100644 index 000000000..f27b97aa7 --- /dev/null +++ b/sal/osl/w32/nlsupport.cxx @@ -0,0 +1,202 @@ +/* -*- 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 . + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <wchar.h> + +#include "nlsupport.hxx" + +#include <osl/mutex.h> +#include <osl/nlsupport.h> +#include <osl/diagnose.h> +#include <osl/process.h> +#include <rtl/tencinfo.h> +#include <o3tl/char16_t2wchar_t.hxx> + +/* XXX NOTE: + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd373848.aspx + * (retrieved 2013-02-13) has some weird description for the LOCALE_SISO* + * constants: "The maximum number of characters allowed for this string is + * nine, including a terminating null character." NINE?!? In ISO 639 and ISO + * 3166? + */ +#define ELP_LANGUAGE_FIELD_LENGTH 4 +#define ELP_COUNTRY_FIELD_LENGTH 3 + +namespace { + +/** Struct used in EnumLocalesProcW() called via EnumSystemLocalesW() to obtain + available locales. +*/ +struct EnumLocalesParams +{ + WCHAR Language[ELP_LANGUAGE_FIELD_LENGTH]; + WCHAR Country[ELP_COUNTRY_FIELD_LENGTH]; + LCID Locale; +}; + +} + +static DWORD g_dwTLSLocaleEncId = DWORD(-1); + +/***************************************************************************** + * callback function test + *****************************************************************************/ + +static BOOL CALLBACK EnumLocalesProcW( LPWSTR lpLocaleStringW ) +{ + /* check params received via TLS */ + EnumLocalesParams * params = static_cast<EnumLocalesParams *>(TlsGetValue( g_dwTLSLocaleEncId )); + if( nullptr == params || '\0' == params->Language[0] ) + return FALSE; + + LPWSTR pszEnd; + WCHAR langCode[ELP_LANGUAGE_FIELD_LENGTH]; + + /* convert hex-string to LCID */ + LCID localeId = wcstol(lpLocaleStringW, &pszEnd, 16); + + /* + get the ISO language code for this locale + */ + if( !GetLocaleInfoW( localeId, LOCALE_SISO639LANGNAME , langCode, ELP_LANGUAGE_FIELD_LENGTH ) ) + /* retry by going on */ + return TRUE; + + WCHAR ctryCode[ELP_COUNTRY_FIELD_LENGTH]; + + /* continue if language code does not match */ + if( 0 != wcscmp( langCode, params->Language ) ) + return TRUE; + + /* check if country code is set and equals the current locale */ + if( '\0' != params->Country[0] && GetLocaleInfoW( localeId, + LOCALE_SISO3166CTRYNAME , ctryCode, ELP_COUNTRY_FIELD_LENGTH ) ) + { + /* save return value in TLS and break if found desired locale */ + if( 0 == wcscmp( ctryCode, params->Country ) ) + { + params->Locale = localeId; + return FALSE; + } + } + else + { + /* fill with default values for that language */ + LANGID langId = LANGIDFROMLCID( localeId ); + + /* exchange sublanguage with SUBLANG_NEUTRAL */ + langId = MAKELANGID( PRIMARYLANGID( langId ), SUBLANG_NEUTRAL ); + + /* and use default sorting order */ + params->Locale = MAKELCID( langId, SORT_DEFAULT ); + + return FALSE; + } + + /* retry by going on */ + return TRUE; +} + +static rtl_TextEncoding GetTextEncodingFromLCID( LCID localeId ) +{ + /* query ansi codepage for given locale */ + WCHAR ansiCP[6]; + if( !localeId || !GetLocaleInfoW( localeId, LOCALE_IDEFAULTANSICODEPAGE, ansiCP, 6 ) ) + return RTL_TEXTENCODING_DONTKNOW; + + /* if GetLocaleInfo returns "0", it is a UNICODE only locale */ + if( 0 == wcscmp( ansiCP, L"0" ) ) + return RTL_TEXTENCODING_UNICODE; + + /* values returned from GetLocaleInfo are decimal based */ + WCHAR *pwcEnd; + UINT codepage = wcstol( ansiCP, &pwcEnd, 10 ); + + /* find matching rtl encoding */ + return rtl_getTextEncodingFromWindowsCodePage( codepage ); +} + +rtl_TextEncoding SAL_CALL osl_getTextEncodingFromLocale( rtl_Locale * pLocale ) +{ + struct EnumLocalesParams params = { L"", L"", 0 }; + + /* initialise global TLS id */ + if( DWORD(-1) == g_dwTLSLocaleEncId ) + { + oslMutex globalMutex = * osl_getGlobalMutex(); + + /* initializing must be thread save */ + osl_acquireMutex( globalMutex ); + + if( DWORD(-1) == g_dwTLSLocaleEncId ) + g_dwTLSLocaleEncId = TlsAlloc(); + + osl_releaseMutex( globalMutex ); + } + + /* if pLocale is NULL, use process locale as default */ + if( nullptr == pLocale ) + osl_getProcessLocale( &pLocale ); + + /* copy in parameters to structure */ + if( !pLocale || !pLocale->Language || pLocale->Language->length >= ELP_LANGUAGE_FIELD_LENGTH ) + return RTL_TEXTENCODING_DONTKNOW; + + wcscpy( params.Language, o3tl::toW(pLocale->Language->buffer) ); + + if( pLocale->Country && pLocale->Country->length < ELP_COUNTRY_FIELD_LENGTH ) + wcscpy( params.Country, o3tl::toW(pLocale->Country->buffer) ); + + /* save pointer to local structure in TLS */ + TlsSetValue( g_dwTLSLocaleEncId, ¶ms ); + + /* enum all locales known to Windows */ + EnumSystemLocalesW( EnumLocalesProcW, LCID_SUPPORTED ); + + /* use the LCID found in iteration */ + return GetTextEncodingFromLCID( params.Locale ); +} + +void imp_getProcessLocale( rtl_Locale ** ppLocale ) +{ + WCHAR langCode[ELP_LANGUAGE_FIELD_LENGTH]; + WCHAR ctryCode[ELP_COUNTRY_FIELD_LENGTH]; + LCID localeId; + + OSL_ASSERT( ppLocale ); + + /* get the LCID to retrieve information from */ + localeId = GetUserDefaultLCID(); + + /* call GetLocaleInfo to retrieve the iso codes */ + if( GetLocaleInfoW( localeId, LOCALE_SISO639LANGNAME , langCode, ELP_LANGUAGE_FIELD_LENGTH ) && + GetLocaleInfoW( localeId, LOCALE_SISO3166CTRYNAME , ctryCode, ELP_COUNTRY_FIELD_LENGTH ) ) + { + *ppLocale = rtl_locale_register( o3tl::toU(langCode), o3tl::toU(ctryCode), u"" ); + } + else + { + *ppLocale = rtl_locale_register( u"C", u"", u"" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/nlsupport.hxx b/sal/osl/w32/nlsupport.hxx new file mode 100644 index 000000000..2d818c378 --- /dev/null +++ b/sal/osl/w32/nlsupport.hxx @@ -0,0 +1,21 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_W32_NLSUPPORT_HXX +#define INCLUDED_SAL_OSL_W32_NLSUPPORT_HXX + +#include <sal/config.h> + +#include <rtl/locale.h> + +void imp_getProcessLocale(rtl_Locale** ppLocale); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/w32/path_helper.cxx b/sal/osl/w32/path_helper.cxx new file mode 100644 index 000000000..a2dd134fd --- /dev/null +++ b/sal/osl/w32/path_helper.cxx @@ -0,0 +1,89 @@ +/* -*- 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 "path_helper.hxx" +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <algorithm> +#include <wchar.h> + +constexpr OUStringLiteral BACKSLASH (u"\\"); +constexpr OUStringLiteral SLASH (u"/"); + +void osl_systemPathEnsureSeparator(/*inout*/ rtl_uString** ppustrPath) +{ + OSL_PRECOND(ppustrPath && (nullptr != *ppustrPath), + "osl_systemPathEnsureSeparator: Invalid parameter"); + + OUString path(*ppustrPath); + sal_Int32 i = std::max<sal_Int32>(path.lastIndexOf(BACKSLASH), path.lastIndexOf(SLASH)); + + if (i < (path.getLength()-1)) + { + path += BACKSLASH; + rtl_uString_assign(ppustrPath, path.pData); + } + + SAL_WARN_IF( !path.endsWith(BACKSLASH), + "sal.osl", + "osl_systemPathEnsureSeparator: Post condition failed"); +} + +void osl_systemPathRemoveSeparator(/*inout*/ rtl_uString** ppustrPath) +{ + OUString path(*ppustrPath); + + if (!osl::systemPathIsLogicalDrivePattern(path)) + { + sal_Int32 i = std::max<sal_Int32>(path.lastIndexOf(BACKSLASH), path.lastIndexOf(SLASH)); + + if (i > -1 && (i == (path.getLength() - 1))) + { + path = OUString(path.getStr(), path.getLength() - 1); + rtl_uString_assign(ppustrPath, path.pData); + } + } +} + +// is [A-Za-z]:[/|\]\0 +const char* const LDP = ":"; +const char* const LDP_WITH_BACKSLASH = ":\\"; +const char* const LDP_WITH_SLASH = ":/"; + +// degenerated case returned by the Windows FileOpen dialog +// when someone enters for instance "x:filename", the Win32 +// API accepts this case +const char* const LDP_WITH_DOT_BACKSLASH = ":.\\"; + +bool osl_systemPathIsLogicalDrivePattern(/*in*/ const rtl_uString* pustrPath) +{ + const sal_Unicode* p = rtl_uString_getStr(const_cast<rtl_uString*>(pustrPath)); + if (iswalpha(*p++)) + { + return ((0 == rtl_ustr_ascii_compare(p, LDP)) || + (0 == rtl_ustr_ascii_compare(p, LDP_WITH_BACKSLASH)) || + (0 == rtl_ustr_ascii_compare(p, LDP_WITH_SLASH)) || + (0 == rtl_ustr_ascii_compare(p, LDP_WITH_DOT_BACKSLASH))); + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/path_helper.hxx b/sal/osl/w32/path_helper.hxx new file mode 100644 index 000000000..bcb4f83af --- /dev/null +++ b/sal/osl/w32/path_helper.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_W32_PATH_HELPER_HXX +#define INCLUDED_SAL_OSL_W32_PATH_HELPER_HXX + +#include <sal/config.h> + +#include <osl/diagnose.h> +#include <osl/file.h> +#include <rtl/alloc.h> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +/** + Adds a trailing path separator to the given system path if not + already there and if the path is not the root path or a logical + drive alone +*/ + +void osl_systemPathEnsureSeparator(/*inout*/ rtl_uString** ppustrPath); + +/** + Removes the last separator from the given system path if any and + if the path is not the root path '\' +*/ + +void osl_systemPathRemoveSeparator(/*inout*/ rtl_uString** ppustrPath); + +/** + Returns whether a given path is only a logical drive pattern or not. + A logical drive pattern is something like "a:\", "c:\". + No logical drive pattern is something like "c:\test" +*/ + +bool osl_systemPathIsLogicalDrivePattern(/*in*/ const rtl_uString* pustrPath); + +namespace osl +{ + +/** + Adds a trailing path separator to the given system path if not + already there and if the path is not the root path or a logical + drive alone +*/ + +inline void systemPathEnsureSeparator(/*inout*/ OUString& Path) +{ + osl_systemPathEnsureSeparator(&Path.pData); +} + +/** + Removes the last separator from the given system path if any and + if the path is not the root path '\' +*/ + +inline void systemPathRemoveSeparator(/*inout*/ OUString& Path) +{ + osl_systemPathRemoveSeparator(&Path.pData); +} + +inline bool systemPathIsLogicalDrivePattern(/*in*/ const OUString& path) +{ + return osl_systemPathIsLogicalDrivePattern(path.pData); +} + +template< class T > +class LongPathBuffer +{ + T* m_pBuffer; + sal_uInt32 m_nCharNum; + + LongPathBuffer(); + LongPathBuffer( const LongPathBuffer& ); + LongPathBuffer& operator=( const LongPathBuffer& ); + +public: + explicit LongPathBuffer( sal_uInt32 nCharNum ) + : m_pBuffer( static_cast<T*>( malloc( nCharNum * sizeof( T ) ) ) ) + , m_nCharNum( nCharNum ) + { + OSL_ENSURE( m_pBuffer, "Can not allocate the buffer!" ); + } + + ~LongPathBuffer() + { + if ( m_pBuffer ) + free( m_pBuffer ); + m_pBuffer = nullptr; + } + + sal_uInt32 getBufSizeInSymbols() + { + return m_nCharNum; + } + + operator T* () + { + return m_pBuffer; + } + +}; + +} // end namespace osl + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/pipe.cxx b/sal/osl/w32/pipe.cxx new file mode 100644 index 000000000..c94441e63 --- /dev/null +++ b/sal/osl/w32/pipe.cxx @@ -0,0 +1,489 @@ +/* -*- 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 "system.h" + +#include <osl/pipe.h> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/mutex.h> +#include <osl/conditn.h> +#include <osl/interlck.h> +#include <osl/process.h> +#include <rtl/alloc.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <cassert> +#include <string.h> + +#define PIPESYSTEM "\\\\.\\pipe\\" +#define PIPEPREFIX "OSL_PIPE_" + +struct oslPipeImpl +{ + oslInterlockedCount m_Reference; + HANDLE m_File; + HANDLE m_NamedObject; + PSECURITY_ATTRIBUTES m_Security; + HANDLE m_ReadEvent; + HANDLE m_WriteEvent; + HANDLE m_AcceptEvent; + rtl_uString* m_Name; + oslPipeError m_Error; + bool m_bClosed; +}; + +static oslPipe osl_createPipeImpl(void) +{ + oslPipe pPipe; + + pPipe = static_cast< oslPipe >(rtl_allocateZeroMemory(sizeof(struct oslPipeImpl))); + + pPipe->m_bClosed = false; + pPipe->m_Reference = 0; + pPipe->m_Name = nullptr; + pPipe->m_File = INVALID_HANDLE_VALUE; + pPipe->m_NamedObject = nullptr; + + pPipe->m_ReadEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + pPipe->m_WriteEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + pPipe->m_AcceptEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + + return pPipe; +} + +static void osl_destroyPipeImpl(oslPipe pPipe) +{ + if (!pPipe) + return; + + if (pPipe->m_NamedObject) + CloseHandle(pPipe->m_NamedObject); + + if (pPipe->m_Security) + { + free(pPipe->m_Security->lpSecurityDescriptor); + free(pPipe->m_Security); + } + + CloseHandle(pPipe->m_ReadEvent); + CloseHandle(pPipe->m_WriteEvent); + CloseHandle(pPipe->m_AcceptEvent); + + if (pPipe->m_Name) + rtl_uString_release(pPipe->m_Name); + + free(pPipe); +} + +oslPipe SAL_CALL osl_createPipe(rtl_uString *strPipeName, oslPipeOptions Options, + oslSecurity Security) +{ + rtl_uString* name = nullptr; + rtl_uString* path = nullptr; + rtl_uString* temp = nullptr; + oslPipe pPipe; + + PSECURITY_ATTRIBUTES pSecAttr = nullptr; + + rtl_uString_newFromAscii(&path, PIPESYSTEM); + rtl_uString_newFromAscii(&name, PIPEPREFIX); + + if (Security) + { + rtl_uString *Ident = nullptr; + rtl_uString *Delim = nullptr; + + OSL_VERIFY(osl_getUserIdent(Security, &Ident)); + rtl_uString_newFromAscii(&Delim, "_"); + + rtl_uString_newConcat(&temp, name, Ident); + rtl_uString_newConcat(&name, temp, Delim); + + rtl_uString_release(Ident); + rtl_uString_release(Delim); + } + else + { + if (Options & osl_Pipe_CREATE) + { + PSECURITY_DESCRIPTOR pSecDesc; + + pSecDesc = static_cast< PSECURITY_DESCRIPTOR >(malloc(SECURITY_DESCRIPTOR_MIN_LENGTH)); + assert(pSecDesc); // Don't handle OOM conditions + + /* add a NULL disc. ACL to the security descriptor */ + OSL_VERIFY(InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)); + OSL_VERIFY(SetSecurityDescriptorDacl(pSecDesc, TRUE, nullptr, FALSE)); + + pSecAttr = static_cast< PSECURITY_ATTRIBUTES >(malloc(sizeof(SECURITY_ATTRIBUTES))); + assert(pSecAttr); // Don't handle OOM conditions + pSecAttr->nLength = sizeof(SECURITY_ATTRIBUTES); + pSecAttr->lpSecurityDescriptor = pSecDesc; + pSecAttr->bInheritHandle = TRUE; + } + } + + rtl_uString_assign(&temp, name); + rtl_uString_newConcat(&name, temp, strPipeName); + + /* alloc memory */ + pPipe = osl_createPipeImpl(); + + assert(pPipe); // if osl_createPipeImpl() cannot init. a new pipe, this is a failure + + osl_atomic_increment(&(pPipe->m_Reference)); + + /* build system pipe name */ + rtl_uString_assign(&temp, path); + rtl_uString_newConcat(&path, temp, name); + rtl_uString_release(temp); + temp = nullptr; + + if (Options & osl_Pipe_CREATE) + { + SetLastError(ERROR_SUCCESS); + + pPipe->m_NamedObject = CreateMutexW(nullptr, FALSE, o3tl::toW(name->buffer)); + + if (pPipe->m_NamedObject) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + pPipe->m_Security = pSecAttr; + rtl_uString_assign(&pPipe->m_Name, name); + + /* try to open system pipe */ + pPipe->m_File = CreateNamedPipeW( + o3tl::toW(path->buffer), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_WAIT | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, + 4096, 4096, + NMPWAIT_WAIT_FOREVER, + pPipe->m_Security); + + if (pPipe->m_File != INVALID_HANDLE_VALUE) + { + rtl_uString_release( name ); + rtl_uString_release( path ); + + return pPipe; + } + } + else + { + CloseHandle(pPipe->m_NamedObject); + pPipe->m_NamedObject = nullptr; + } + } + } + else + { + bool bPipeAvailable; + + do + { + /* free instance should be available first */ + bPipeAvailable = WaitNamedPipeW(o3tl::toW(path->buffer), NMPWAIT_WAIT_FOREVER); + + /* first try to open system pipe */ + if (bPipeAvailable) + { + pPipe->m_File = CreateFileW( + o3tl::toW(path->buffer), + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + nullptr); + + if (pPipe->m_File != INVALID_HANDLE_VALUE) + { + // We got it ! + rtl_uString_release(name); + rtl_uString_release(path); + + // We should try to transfer our privilege to become foreground process + // to the other process, so that it may show popups (otherwise, they might + // be blocked by SPI_GETFOREGROUNDLOCKTIMEOUT setting - + // see SystemParametersInfo function at MSDN + ULONG ServerProcessId = 0; + if (GetNamedPipeServerProcessId(pPipe->m_File, &ServerProcessId)) + AllowSetForegroundWindow(ServerProcessId); + + return pPipe; + } + else + { + // Pipe instance maybe caught by another client -> try again + } + } + } while (bPipeAvailable); + } + + /* if we reach here something went wrong */ + osl_destroyPipeImpl(pPipe); + + return nullptr; +} + +void SAL_CALL osl_acquirePipe(oslPipe pPipe) +{ + osl_atomic_increment(&(pPipe->m_Reference)); +} + +void SAL_CALL osl_releasePipe(oslPipe pPipe) +{ + if (!pPipe) + return; + + if (osl_atomic_decrement(&(pPipe->m_Reference)) == 0) + { + if (!pPipe->m_bClosed) + osl_closePipe(pPipe); + + osl_destroyPipeImpl(pPipe); + } +} + +void SAL_CALL osl_closePipe(oslPipe pPipe) +{ + if (pPipe && !pPipe->m_bClosed) + { + pPipe->m_bClosed = true; + /* if we have a system pipe close it */ + if (pPipe->m_File != INVALID_HANDLE_VALUE) + { + DisconnectNamedPipe(pPipe->m_File); + CloseHandle(pPipe->m_File); + } + } +} + +oslPipe SAL_CALL osl_acceptPipe(oslPipe pPipe) +{ + oslPipe pAcceptedPipe = nullptr; + + OVERLAPPED os = {}; + + DWORD nBytesTransferred; + rtl_uString* path = nullptr; + rtl_uString* temp = nullptr; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_acceptPipe: invalid pipe"); + if (!pPipe) + return nullptr; + + SAL_WARN_IF(pPipe->m_File == INVALID_HANDLE_VALUE, "sal.osl.pipe", "osl_acceptPipe: invalid handle"); + + os.hEvent = pPipe->m_AcceptEvent; + ResetEvent(pPipe->m_AcceptEvent); + + if (!ConnectNamedPipe(pPipe->m_File, &os)) + { + switch (GetLastError()) + { + case ERROR_PIPE_CONNECTED: // Client already connected to pipe + case ERROR_NO_DATA: // Client was connected but has already closed pipe end + // should only appear in nonblocking mode but in fact does + // in blocking asynchronous mode. + break; + case ERROR_PIPE_LISTENING: // Only for nonblocking mode but see ERROR_NO_DATA + case ERROR_IO_PENDING: // This is normal if not client is connected yet + case ERROR_MORE_DATA: // Should not happen + // blocking call to accept + if( !GetOverlappedResult(pPipe->m_File, &os, &nBytesTransferred, TRUE)) + { + // Possible error could be that between ConnectNamedPipe and + // GetOverlappedResult a connect took place. + + switch (GetLastError()) + { + case ERROR_PIPE_CONNECTED: // Pipe was already connected + case ERROR_NO_DATA: // Pipe was connected but client has already closed -> ver fast client ;-) + break; // Everything's fine !!! + default: + // Something went wrong + return nullptr; + } + } + break; + default: // All other error say that somethings going wrong. + return nullptr; + } + } + + pAcceptedPipe = osl_createPipeImpl(); + assert(pAcceptedPipe); // should never be the case that an oslPipe cannot be initialized + + osl_atomic_increment(&(pAcceptedPipe->m_Reference)); + rtl_uString_assign(&pAcceptedPipe->m_Name, pPipe->m_Name); + pAcceptedPipe->m_File = pPipe->m_File; + + rtl_uString_newFromAscii(&temp, PIPESYSTEM); + rtl_uString_newConcat(&path, temp, pPipe->m_Name); + rtl_uString_release(temp); + + // prepare for next accept + pPipe->m_File = + CreateNamedPipeW(o3tl::toW(path->buffer), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_WAIT | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, + 4096, 4096, + NMPWAIT_WAIT_FOREVER, + pAcceptedPipe->m_Security); + rtl_uString_release(path); + + return pAcceptedPipe; +} + +sal_Int32 SAL_CALL osl_receivePipe(oslPipe pPipe, + void* pBuffer, + sal_Int32 BytesToRead) +{ + DWORD nBytes; + OVERLAPPED os = {}; + + assert(pPipe); + + os.hEvent = pPipe->m_ReadEvent; + + ResetEvent(pPipe->m_ReadEvent); + + if (!ReadFile(pPipe->m_File, pBuffer, BytesToRead, &nBytes, &os) && + ((GetLastError() != ERROR_IO_PENDING) || + !GetOverlappedResult(pPipe->m_File, &os, &nBytes, TRUE))) + { + DWORD lastError = GetLastError(); + + if (lastError == ERROR_MORE_DATA) + { + nBytes = BytesToRead; + } + else + { + if (lastError == ERROR_PIPE_NOT_CONNECTED) + nBytes = 0; + else + nBytes = DWORD(-1); + + pPipe->m_Error = osl_Pipe_E_ConnectionAbort; + } + } + + return nBytes; +} + +sal_Int32 SAL_CALL osl_sendPipe(oslPipe pPipe, + const void* pBuffer, + sal_Int32 BytesToSend) +{ + DWORD nBytes; + OVERLAPPED os = {}; + + assert(pPipe); + + os.hEvent = pPipe->m_WriteEvent; + ResetEvent(pPipe->m_WriteEvent); + + if (!WriteFile(pPipe->m_File, pBuffer, BytesToSend, &nBytes, &os) && + ((GetLastError() != ERROR_IO_PENDING) || + !GetOverlappedResult(pPipe->m_File, &os, &nBytes, TRUE))) + { + if (GetLastError() == ERROR_PIPE_NOT_CONNECTED) + nBytes = 0; + else + nBytes = DWORD(-1); + + pPipe->m_Error = osl_Pipe_E_ConnectionAbort; + } + + return nBytes; +} + +sal_Int32 SAL_CALL osl_writePipe(oslPipe pPipe, const void *pBuffer , sal_Int32 n) +{ + /* loop until all desired bytes were send or an error occurred */ + sal_Int32 BytesSend = 0; + sal_Int32 BytesToSend = n; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_writePipe: invalid pipe"); + while (BytesToSend > 0) + { + sal_Int32 RetVal; + + RetVal= osl_sendPipe(pPipe, pBuffer, BytesToSend); + + /* error occurred? */ + if (RetVal <= 0) + break; + + BytesToSend -= RetVal; + BytesSend += RetVal; + pBuffer= static_cast< char const* >(pBuffer) + RetVal; + } + + return BytesSend; +} + +sal_Int32 SAL_CALL osl_readPipe(oslPipe pPipe, void *pBuffer, sal_Int32 n) +{ + /* loop until all desired bytes were read or an error occurred */ + sal_Int32 BytesRead = 0; + sal_Int32 BytesToRead = n; + + SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_readPipe: invalid pipe"); + while (BytesToRead > 0) + { + sal_Int32 RetVal; + RetVal= osl_receivePipe(pPipe, pBuffer, BytesToRead); + + /* error occurred? */ + if(RetVal <= 0) + break; + + BytesToRead -= RetVal; + BytesRead += RetVal; + pBuffer= static_cast< char* >(pBuffer) + RetVal; + } + return BytesRead; +} + +oslPipeError SAL_CALL osl_getLastPipeError(oslPipe pPipe) +{ + oslPipeError Error; + + if (pPipe) + { + Error = pPipe->m_Error; + pPipe->m_Error = osl_Pipe_E_None; + } + else + { + Error = osl_Pipe_E_NotFound; + } + + return Error; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/process.cxx b/sal/osl/w32/process.cxx new file mode 100644 index 000000000..065415f2c --- /dev/null +++ b/sal/osl/w32/process.cxx @@ -0,0 +1,525 @@ +/* -*- 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 "system.h" +#include <string.h> + +#include <shellapi.h> + +#include <cassert> + +#include <osl/mutex.hxx> +#include <osl/nlsupport.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include "filetime.hxx" +#include "nlsupport.hxx" +#include "procimpl.hxx" +#include "file_url.hxx" +#include "path_helper.hxx" +#include <rtl/alloc.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +oslProcessError SAL_CALL osl_terminateProcess(oslProcess Process) +{ + if (Process == nullptr) + return osl_Process_E_Unknown; + + HANDLE hProcess = static_cast<oslProcessImpl*>(Process)->m_hProcess; + DWORD dwPID = GetProcessId(hProcess); + + // cannot be System Process (0x00000000) + if (dwPID == 0x0) + return osl_Process_E_InvalidError; + + // Test to see if we can create a thread in a process... adapted from: + // * https://support.microsoft.com/en-us/help/178893/how-to-terminate-an-application-cleanly-in-win32 + // * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547 + + // TODO: we really should firstly check to see if we have access to create threads and only + // duplicate the handle with elevated access if we don't have access... this can be done, but + // it's not exactly easy - an example can be found here: + // http://windowsitpro.com/site-files/windowsitpro.com/files/archive/windowsitpro.com/content/content/15989/listing_01.txt + + HANDLE hDupProcess = nullptr; + + + // we need to make sure we can create a thread in the remote process, if the handle was created + // in something that doesn't give us appropriate levels of access then we will need to give it the + // desired level of access - if the process handle was grabbed from OpenProcess it's quite possible + // that the handle doesn't have the appropriate level of access... + + // see https://msdn.microsoft.com/en-au/library/windows/desktop/ms684880(v=vs.85).aspx + DWORD const dwAccessFlags = (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE | PROCESS_VM_READ); + + bool bHaveDuplHdl = DuplicateHandle(GetCurrentProcess(), // handle to process that has handle + hProcess, // handle to be duplicated + GetCurrentProcess(), // process that will get the dup handle + &hDupProcess, // store duplicate process handle here + dwAccessFlags, // desired access + FALSE, // handle can't be inherited + 0); // zero means no additional action needed + + if (bHaveDuplHdl) + hProcess = hDupProcess; // so we were able to duplicate the handle, all good... + else + SAL_WARN("sal.osl", "Could not duplicate process handle, let's hope for the best..."); + + DWORD dwProcessStatus = 0; + HANDLE hRemoteThread = nullptr; + + if (GetExitCodeProcess(hProcess, &dwProcessStatus) && (dwProcessStatus == STILL_ACTIVE)) + { + // We need to get the address of the Win32 procedure ExitProcess, can't call it + // directly because we'll be calling the thunk and that will probably lead to an + // access violation. Once we have the address, then we need to create a new + // thread in the process (which we might need to run in the address space of + // another process) and then call on ExitProcess to try to cleanly terminate that + // process + + DWORD dwTID = 0; // dummy variable as we don't need to track the thread ID + + // Note: we want to call on ExitProcess() and not TerminateProcess() - this is + // because with ExitProcess() Windows notifies all attached dlls that the process + // is detaching from the dll, but TerminateProcess() terminates all threads + // immediately, doesn't call any termination handlers and doesn't notify any dlls + // that it is detaching from them + + HINSTANCE hKernel = GetModuleHandleW(L"kernel32.dll"); + FARPROC pfnExitProc = GetProcAddress(hKernel, "ExitProcess"); + hRemoteThread = CreateRemoteThread( + hProcess, /* process handle */ + nullptr, /* default security descriptor */ + 0, /* initial size of stack in bytes is default + size for executable */ + reinterpret_cast<LPTHREAD_START_ROUTINE>(pfnExitProc), /* Win32 ExitProcess() */ + reinterpret_cast<PVOID>(UINT(0)), /* ExitProcess(UINT uExitCode) argument */ + 0, /* value of 0 tells thread to run immediately + after creation */ + &dwTID); /* new remote thread's identifier */ + + } + + bool bHasExited = false; + + if (hRemoteThread) + { + WaitForSingleObject(hProcess, INFINITE); // wait for process to terminate, never stop waiting... + CloseHandle(hRemoteThread); // close the thread handle to allow the process to exit + bHasExited = true; + } + + // need to close this duplicated process handle... + if (bHaveDuplHdl) + CloseHandle(hProcess); + + if (bHasExited) + return osl_Process_E_None; + + // fallback - given that we wait for an infinite time on WaitForSingleObject, this should + // never occur... unless CreateRemoteThread failed + SAL_WARN("sal.osl", "TerminateProcess(hProcess, 0) called - we should never get here!"); + return (TerminateProcess(hProcess, 0) == FALSE) ? osl_Process_E_Unknown : osl_Process_E_None; +} + +oslProcess SAL_CALL osl_getProcess(oslProcessIdentifier Ident) +{ + oslProcessImpl* pProcImpl; + HANDLE hProcess = OpenProcess( + STANDARD_RIGHTS_REQUIRED | PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, static_cast<DWORD>(Ident)); + + if (hProcess) + { + pProcImpl = static_cast< oslProcessImpl*>( malloc(sizeof(oslProcessImpl)) ); + pProcImpl->m_hProcess = hProcess; + pProcImpl->m_IdProcess = Ident; + } + else + pProcImpl = nullptr; + + return pProcImpl; +} + +void SAL_CALL osl_freeProcessHandle(oslProcess Process) +{ + if (Process != nullptr) + { + CloseHandle(static_cast<oslProcessImpl*>(Process)->m_hProcess); + + free(Process); + } +} + +oslProcessError SAL_CALL osl_getProcessInfo(oslProcess Process, oslProcessData Fields, + oslProcessInfo* pInfo) +{ + HANDLE hProcess; + DWORD IdProcess; + + if (Process == nullptr) + { + hProcess = GetCurrentProcess(); + IdProcess = GetCurrentProcessId(); + } + else + { + hProcess = static_cast<oslProcessImpl*>(Process)->m_hProcess; + IdProcess = static_cast<oslProcessImpl*>(Process)->m_IdProcess; + } + + if (! pInfo || (pInfo->Size != sizeof(oslProcessInfo))) + return osl_Process_E_Unknown; + + pInfo->Fields = 0; + + if (Fields & osl_Process_IDENTIFIER) + { + pInfo->Ident = IdProcess; + pInfo->Fields |= osl_Process_IDENTIFIER; + } + + if (Fields & osl_Process_EXITCODE) + { + if (GetExitCodeProcess(hProcess, &(pInfo->Code)) && (pInfo->Code != STILL_ACTIVE)) + pInfo->Fields |= osl_Process_EXITCODE; + } + + if (Fields & osl_Process_HEAPUSAGE) + { + void* lpAddress=nullptr; + MEMORY_BASIC_INFORMATION Info; + + pInfo->HeapUsage = 0; + + do + { + if (VirtualQueryEx(hProcess, lpAddress, &Info, sizeof(Info)) == 0) + break; + + if ((Info.State == MEM_COMMIT) && (Info.Type == MEM_PRIVATE)) + pInfo->HeapUsage += Info.RegionSize; + + lpAddress = static_cast<LPBYTE>(lpAddress) + Info.RegionSize; + } + while (reinterpret_cast<uintptr_t>(lpAddress) <= 0x7FFFFFFFU); // 2GB address space + + pInfo->Fields |= osl_Process_HEAPUSAGE; + } + + if (Fields & osl_Process_CPUTIMES) + { + FILETIME CreationTime, ExitTime, KernelTime, UserTime; + + if (GetProcessTimes(hProcess, &CreationTime, &ExitTime, + &KernelTime, &UserTime)) + { + __int64 Value; + + Value = osl::detail::getFiletime(UserTime); + pInfo->UserTime.Seconds = static_cast<unsigned long>(Value / 10000000L); + pInfo->UserTime.Nanosec = static_cast<unsigned long>((Value % 10000000L) * 100); + + Value = osl::detail::getFiletime(KernelTime); + pInfo->SystemTime.Seconds = static_cast<unsigned long>(Value / 10000000L); + pInfo->SystemTime.Nanosec = static_cast<unsigned long>((Value % 10000000L) * 100); + + pInfo->Fields |= osl_Process_CPUTIMES; + } + } + + return (pInfo->Fields == Fields) ? osl_Process_E_None : osl_Process_E_Unknown; +} + +oslProcessError SAL_CALL osl_joinProcess(oslProcess Process) +{ + return osl_joinProcessWithTimeout(Process, nullptr); +} + +oslProcessError SAL_CALL osl_joinProcessWithTimeout(oslProcess Process, const TimeValue* pTimeout) +{ + DWORD timeout = INFINITE; + oslProcessError osl_error = osl_Process_E_None; + DWORD ret; + + if (nullptr == Process) + return osl_Process_E_Unknown; + + if (pTimeout) + timeout = pTimeout->Seconds * 1000 + pTimeout->Nanosec / 1000000L; + + ret = WaitForSingleObject(static_cast<oslProcessImpl*>(Process)->m_hProcess, timeout); + + if (WAIT_FAILED == ret) + osl_error = osl_Process_E_Unknown; + else if (WAIT_TIMEOUT == ret) + osl_error = osl_Process_E_TimedOut; + + return osl_error; +} + +namespace { + +oslProcessError bootstrap_getExecutableFile(rtl_uString ** ppFileURL) +{ + oslProcessError result = osl_Process_E_NotFound; + + ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH ); + DWORD buflen = 0; + + if ((buflen = GetModuleFileNameW (nullptr, o3tl::toW(aBuffer), aBuffer.getBufSizeInSymbols())) > 0) + { + rtl_uString * pAbsPath = nullptr; + rtl_uString_newFromStr_WithLength (&pAbsPath, aBuffer, buflen); + if (pAbsPath) + { + /* Convert from path to url. */ + if (osl_getFileURLFromSystemPath (pAbsPath, ppFileURL) == osl_File_E_None) + { + /* Success. */ + result = osl_Process_E_None; + } + rtl_uString_release (pAbsPath); + } + } + + return result; +} + +struct CommandArgs_Impl +{ + sal_uInt32 m_nCount; + rtl_uString ** m_ppArgs; +}; + +} + +static struct CommandArgs_Impl g_command_args = +{ + 0, + nullptr +}; + +static rtl_uString ** osl_createCommandArgs_Impl (int argc, char **) +{ + rtl_uString ** ppArgs = + static_cast<rtl_uString**>(rtl_allocateZeroMemory (argc * sizeof(rtl_uString*))); + if (ppArgs != nullptr) + { + int i; + int nArgs; + LPWSTR *wargv = CommandLineToArgvW( GetCommandLineW(), &nArgs ); + assert( nArgs == argc ); + for (i = 0; i < nArgs; i++) + { + /* Convert to unicode */ + rtl_uString_newFromStr( &(ppArgs[i]), o3tl::toU(wargv[i]) ); + } + if (ppArgs[0] != nullptr) + { + /* Ensure absolute path */ + ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH ); + DWORD dwResult + = GetModuleFileNameW(nullptr, o3tl::toW(aBuffer), aBuffer.getBufSizeInSymbols()); + if ((0 < dwResult) && (dwResult < aBuffer.getBufSizeInSymbols())) + { + /* Replace argv[0] with its absolute path */ + rtl_uString_newFromStr_WithLength( + &(ppArgs[0]), aBuffer, dwResult); + } + } + if (ppArgs[0] != nullptr) + { + /* Convert to FileURL, see @ osl_getExecutableFile() */ + rtl_uString * pResult = nullptr; + osl_getFileURLFromSystemPath (ppArgs[0], &pResult); + if (pResult != nullptr) + { + rtl_uString_assign (&(ppArgs[0]), pResult); + rtl_uString_release (pResult); + } + } + } + return ppArgs; + +} + +oslProcessError SAL_CALL osl_getExecutableFile( rtl_uString **ppustrFile ) +{ + { + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + if (g_command_args.m_nCount > 0) + { + /* CommandArgs set. Obtain arv[0]. */ + rtl_uString_assign(ppustrFile, g_command_args.m_ppArgs[0]); + return osl_Process_E_None; + } + } + return bootstrap_getExecutableFile(ppustrFile); +} + +sal_uInt32 SAL_CALL osl_getCommandArgCount() +{ + sal_uInt32 result = 0; + + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + SAL_INFO_IF( + g_command_args.m_nCount == 0, "sal.osl", + "osl_getCommandArgCount w/o prior call to osl_setCommandArgs"); + if (g_command_args.m_nCount > 0) + { + /* We're not counting argv[0] here. */ + result = g_command_args.m_nCount - 1; + } + + return result; +} + +oslProcessError SAL_CALL osl_getCommandArg( sal_uInt32 nArg, rtl_uString **strCommandArg) +{ + oslProcessError result = osl_Process_E_NotFound; + + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + assert(g_command_args.m_nCount > 0); + if (g_command_args.m_nCount > (nArg + 1)) + { + /* We're not counting argv[0] here. */ + rtl_uString_assign (strCommandArg, g_command_args.m_ppArgs[nArg + 1]); + result = osl_Process_E_None; + } + + return result; +} + +void SAL_CALL osl_setCommandArgs (int argc, char ** argv) +{ + assert(argc > 0); + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + SAL_WARN_IF(g_command_args.m_nCount != 0, "sal.osl", "args already set"); + if (g_command_args.m_nCount == 0) + { + rtl_uString** ppArgs = osl_createCommandArgs_Impl (argc, argv); + if (ppArgs != nullptr) + { + g_command_args.m_nCount = argc; + g_command_args.m_ppArgs = ppArgs; + } + } +} + +/* TODO because of an issue with GetEnvironmentVariableW we have to + allocate a buffer large enough to hold the requested environment + variable instead of testing for the required size. This wastes + some stack space, maybe we should revoke this work around if + this is no longer a problem */ +#define ENV_BUFFER_SIZE (32*1024-1) + +oslProcessError SAL_CALL osl_getEnvironment(rtl_uString *ustrVar, rtl_uString **ustrValue) +{ + WCHAR buff[ENV_BUFFER_SIZE]; + + if (GetEnvironmentVariableW(o3tl::toW(ustrVar->buffer), buff, ENV_BUFFER_SIZE) > 0) + { + rtl_uString_newFromStr(ustrValue, o3tl::toU(buff)); + return osl_Process_E_None; + } + return osl_Process_E_Unknown; +} + +oslProcessError SAL_CALL osl_setEnvironment(rtl_uString *ustrVar, rtl_uString *ustrValue) +{ + // set Windows environment variable + if (SetEnvironmentVariableW(o3tl::toW(ustrVar->buffer), o3tl::toW(ustrValue->buffer))) + { + OUString sAssign = OUString::unacquired(&ustrVar) + "=" + OUString::unacquired(&ustrValue); + _wputenv(o3tl::toW(sAssign.getStr())); + return osl_Process_E_None; + } + return osl_Process_E_Unknown; +} + +oslProcessError SAL_CALL osl_clearEnvironment(rtl_uString *ustrVar) +{ + // delete the variable from the current process environment + // by setting SetEnvironmentVariable's second parameter to NULL + if (SetEnvironmentVariableW(o3tl::toW(ustrVar->buffer), nullptr)) + { + OUString sAssign = OUString::unacquired(&ustrVar) + "="; + _wputenv(o3tl::toW(sAssign.getStr())); + return osl_Process_E_None; + } + return osl_Process_E_Unknown; +} + +oslProcessError SAL_CALL osl_getProcessWorkingDir( rtl_uString **pustrWorkingDir ) +{ + ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH ); + DWORD dwLen = GetCurrentDirectoryW(aBuffer.getBufSizeInSymbols(), o3tl::toW(aBuffer)); + + if ( dwLen && dwLen < aBuffer.getBufSizeInSymbols() ) + { + oslFileError eError; + rtl_uString *ustrTemp = nullptr; + + rtl_uString_newFromStr_WithLength( &ustrTemp, aBuffer, dwLen ); + eError = osl_getFileURLFromSystemPath( ustrTemp, pustrWorkingDir ); + + rtl_uString_release( ustrTemp ); + + if ( osl_File_E_None != eError ) + return osl_Process_E_Unknown; + else + return osl_Process_E_None; + } + else + return osl_Process_E_Unknown; +} + +static rtl_Locale * g_theProcessLocale = nullptr; + +oslProcessError SAL_CALL osl_getProcessLocale( rtl_Locale ** ppLocale ) +{ + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + + /* determine the users default locale */ + if( nullptr == g_theProcessLocale ) + imp_getProcessLocale( &g_theProcessLocale ); + + /* or return the cached value */ + *ppLocale = g_theProcessLocale; + + return osl_Process_E_None; +} + +oslProcessError SAL_CALL osl_setProcessLocale( rtl_Locale * pLocale ) +{ + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + + /* check if locale is supported */ + if( RTL_TEXTENCODING_DONTKNOW == osl_getTextEncodingFromLocale( pLocale ) ) + return osl_Process_E_Unknown; + + /* just remember the locale here */ + g_theProcessLocale = pLocale; + + return osl_Process_E_None; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/procimpl.cxx b/sal/osl/w32/procimpl.cxx new file mode 100644 index 000000000..5fb9a4634 --- /dev/null +++ b/sal/osl/w32/procimpl.cxx @@ -0,0 +1,596 @@ +/* -*- 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 . + */ + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# undef WIN32_LEAN_AND_MEAN +#endif + +#include "file-impl.hxx" +#include "procimpl.hxx" +#include <rtl/alloc.h> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include "secimpl.hxx" +#include <osl/file.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <vector> +#include <algorithm> + +namespace /* private */ +{ + /* Function object that compares two strings that are + expected to be environment variables in the form + "name=value". Only the 'name' part will be compared. + The comparison is in upper case and returns true + if the first of both strings is less than the + second one. */ + struct less_environment_variable + { + bool operator() (const OUString& lhs, const OUString& rhs) const + { + OSL_ENSURE((lhs.indexOf(L'=') > -1) && + (rhs.indexOf(L'=') > -1), + "Malformed environment variable"); + + // Windows compares environment variables uppercase + // so we do it, too + return (rtl_ustr_compare_WithLength( + lhs.toAsciiUpperCase().pData->buffer, + lhs.indexOf(L'='), + rhs.toAsciiUpperCase().pData->buffer, + rhs.indexOf(L'=')) < 0); + } + }; + + /* Function object used by for_each algorithm to + calculate the sum of the length of all strings + in a string container. */ + class sum_of_string_lengths + { + public: + + sum_of_string_lengths() : sum_(0) {} + + void operator() (const OUString& string) + { + OSL_ASSERT(string.getLength()); + + // always include the terminating '\0' + if (string.getLength()) + sum_ += string.getLength() + 1; + } + + operator size_t () const + { + return sum_; + } + private: + size_t sum_; + }; + + size_t calc_sum_of_string_lengths(const std::vector<OUString>& string_cont) + { + return std::for_each( + string_cont.begin(), string_cont.end(), sum_of_string_lengths()); + } + + void read_environment(/*out*/ std::vector<OUString>* environment) + { + // GetEnvironmentStrings returns a sorted list, Windows + // sorts environment variables upper case + LPWSTR env = GetEnvironmentStringsW(); + LPWSTR p = env; + + while (size_t l = wcslen(p)) + { + environment->push_back(OUString(o3tl::toU(p))); + p += l + 1; + } + FreeEnvironmentStringsW(env); + + // it is apparently possible that the environment is not completely + // sorted; Cygwin may append entries, which breaks the equal_range + std::stable_sort(environment->begin(), environment->end(), + less_environment_variable()); + } + + /* the environment list must be sorted, new values + should either replace existing ones or should be + added to the list, environment variables will + be handled case-insensitive */ + bool create_merged_environment( + rtl_uString* env_vars[], + sal_uInt32 env_vars_count, + /*in|out*/ std::vector<OUString>* merged_env) + { + OSL_ASSERT(env_vars && env_vars_count > 0 && merged_env); + + read_environment(merged_env); + + for (sal_uInt32 i = 0; i < env_vars_count; i++) + { + OUString env_var(env_vars[i]); + + if (env_var.getLength() == 0) + return false; + + auto iter_pair = std::equal_range( + merged_env->begin(), + merged_env->end(), + env_var, + less_environment_variable()); + + if (env_var.indexOf(L'=') == -1) + { + merged_env->erase(iter_pair.first, iter_pair.second); + } + else + { + if (iter_pair.first != iter_pair.second) // found + *iter_pair.first = env_var; + else // not found + merged_env->insert(iter_pair.first, env_var); + } + } + return true; + } + + /* Create a merged environment */ + bool setup_process_environment( + rtl_uString* environment_vars[], + sal_uInt32 n_environment_vars, + /*in|out*/ std::vector<sal_Unicode>& environment) + { + std::vector<OUString> merged_env; + if (!create_merged_environment(environment_vars, n_environment_vars, &merged_env)) + return false; + + // allocate enough space for the '\0'-separated environment strings and + // a final '\0' + environment.resize(calc_sum_of_string_lengths(merged_env) + 1); + + sal_uInt32 pos = 0; + for (auto& envv : merged_env) + { + OSL_ASSERT(envv.getLength()); + + sal_uInt32 n = envv.getLength() + 1; // copy the final '\0', too + memcpy(&environment[pos], envv.getStr(), n * sizeof(sal_Unicode)); + pos += n; + } + environment[pos] = 0; // append a final '\0' + + return true; + } + + /* In contrast to the Win32 API function CreatePipe with + this function the caller is able to determine separately + which handle of the pipe is inheritable. */ + bool create_pipe( + PHANDLE p_read_pipe, + bool b_read_pipe_inheritable, + PHANDLE p_write_pipe, + bool b_write_pipe_inheritable, + LPVOID p_security_descriptor = nullptr, + DWORD pipe_size = 0) + { + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = p_security_descriptor; + sa.bInheritHandle = b_read_pipe_inheritable || b_write_pipe_inheritable; + + bool bRet = false; + HANDLE hTemp = nullptr; + + if (!b_read_pipe_inheritable && b_write_pipe_inheritable) + { + bRet = CreatePipe(&hTemp, p_write_pipe, &sa, pipe_size); + + if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp, + GetCurrentProcess(), p_read_pipe, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) + { + CloseHandle(hTemp); + CloseHandle(*p_read_pipe); + return false; + } + } + else if (b_read_pipe_inheritable && !b_write_pipe_inheritable) + { + bRet = CreatePipe(p_read_pipe, &hTemp, &sa, pipe_size); + + if (bRet && !DuplicateHandle(GetCurrentProcess(), hTemp, + GetCurrentProcess(), p_write_pipe, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) + { + CloseHandle(hTemp); + CloseHandle(*p_write_pipe); + return false; + } + } + else + { + bRet = CreatePipe(p_read_pipe, p_write_pipe, &sa, pipe_size); + } + return bRet; + } + + // Add a quote sign to the start and the end of a string + // if not already present + OUString quote_string(const OUString& string) + { + OUStringBuffer quoted; + if (string.indexOf(L'"') != 0) + quoted.append('"'); + + quoted.append(string); + + if (string.lastIndexOf(L'"') != (string.getLength() - 1)) + quoted.append('"'); + + return quoted.makeStringAndClear(); + } + + // The parameter path must be a system path. If it is longer than 260 characters + // then it is shortened using the GetShortPathName function. This function only + // works if the path exists. Because "path" can be the path to an executable, it + // may not have the file extension ".exe". However, if the file on disk has the + // ".exe" extension, then the function will fail. In this case a second attempt + // is started by adding the parameter "extension" to "path". + OUString getShortPath(OUString const & path, OUString const & extension) + { + OUString ret(path); + if (path.getLength() > 260) + { + std::vector<sal_Unicode> vec(path.getLength() + 1); + //GetShortPathNameW only works if the file can be found! + const DWORD len = GetShortPathNameW( + o3tl::toW(path.getStr()), o3tl::toW(vec.data()), path.getLength() + 1); + + if (!len && GetLastError() == ERROR_FILE_NOT_FOUND + && extension.getLength()) + { + const OUString extPath(path + extension); + std::vector<sal_Unicode> vec2( + extPath.getLength() + 1); + const DWORD len2 = GetShortPathNameW( + o3tl::toW(extPath.getStr()), o3tl::toW(vec2.data()), extPath.getLength() + 1); + ret = OUString(vec2.data(), len2); + } + else + { + ret = OUString(vec.data(), len); + } + } + return ret; + } + + // Returns the system path of the executable which can either + // be provided via the strImageName parameter or as first + // element of the strArguments list. + // The returned path will be quoted if it contains spaces. + OUString get_executable_path( + rtl_uString* image_name, + rtl_uString* cmdline_args[], + sal_uInt32 n_cmdline_args, + bool search_path) + { + OUString exe_name; + + if (image_name) + exe_name = image_name; + else if (n_cmdline_args) + exe_name = OUString(cmdline_args[0]); + + OUString exe_url = exe_name; + if (search_path) + osl_searchFileURL(exe_name.pData, nullptr, &exe_url.pData); + + OUString exe_path; + if (osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(exe_url, exe_path)) + return OUString(); + + exe_path = getShortPath(exe_path, ".exe"); + + if (exe_path.indexOf(' ') != -1) + exe_path = quote_string(exe_path); + + return exe_path; + } + + OUString get_file_extension(const OUString& file_name) + { + // Quoted file name + if ((file_name.indexOf(L'"') == 0) && (file_name.lastIndexOf(L'"') == (file_name.getLength() - 1))) + { + sal_Int32 index = file_name.lastIndexOf('.'); + if ((index != -1) && ((index + 2) < file_name.getLength())) + return file_name.copy(index + 1, file_name.getLength() - (index + 2)); + } + // Unquoted file name + else + { + sal_Int32 index = file_name.lastIndexOf('.'); + if ((index != -1) && ((index + 1) < file_name.getLength())) + return file_name.copy(index + 1); + } + return OUString(); + } + + bool is_batch_file(const OUString& file_name) + { + OUString ext = get_file_extension(file_name); + return (ext.equalsIgnoreAsciiCase("bat") || + ext.equalsIgnoreAsciiCase("cmd") || + ext.equalsIgnoreAsciiCase("btm")); + } + + const OUString ENV_COMSPEC ("COMSPEC"); + OUString get_batch_processor() + { + OUString comspec; + osl_getEnvironment(ENV_COMSPEC.pData, &comspec.pData); + + OSL_ASSERT(comspec.getLength()); + + /* check if comspec path contains blanks and quote it if any */ + if (comspec.indexOf(' ') != -1) + comspec = quote_string(comspec); + + return comspec; + } + +} // namespace private + +oslProcessError SAL_CALL osl_executeProcess( + rtl_uString *strImageName, + rtl_uString *strArguments[], + sal_uInt32 nArguments, + oslProcessOption Options, + oslSecurity Security, + rtl_uString *strDirectory, + rtl_uString *strEnvironmentVars[], + sal_uInt32 nEnvironmentVars, + oslProcess *pProcess +) +{ + return osl_executeProcess_WithRedirectedIO( + strImageName, + strArguments, + nArguments, + Options, + Security, + strDirectory, + strEnvironmentVars, + nEnvironmentVars, + pProcess, + nullptr, nullptr, nullptr ); +} + +oslProcessError SAL_CALL osl_executeProcess_WithRedirectedIO( + rtl_uString *ustrImageName, + rtl_uString *ustrArguments[], + sal_uInt32 nArguments, + oslProcessOption Options, + oslSecurity Security, + rtl_uString *ustrDirectory, + rtl_uString *ustrEnvironmentVars[], + sal_uInt32 nEnvironmentVars, + oslProcess *pProcess, + oslFileHandle *pProcessInputWrite, + oslFileHandle *pProcessOutputRead, + oslFileHandle *pProcessErrorRead) +{ + OUString exe_path = get_executable_path( + ustrImageName, ustrArguments, nArguments, (Options & osl_Process_SEARCHPATH) != 0); + + if (0 == exe_path.getLength()) + return osl_Process_E_NotFound; + + if (pProcess == nullptr) + return osl_Process_E_InvalidError; + + DWORD flags = NORMAL_PRIORITY_CLASS; + OUStringBuffer command_line; + + if (is_batch_file(exe_path)) + { + OUString batch_processor = get_batch_processor(); + + if (batch_processor.getLength()) + { + /* cmd.exe does not work without a console window */ + if (!(Options & osl_Process_WAIT) || (Options & osl_Process_DETACHED)) + flags |= CREATE_NEW_CONSOLE; + + command_line.append(batch_processor); + command_line.append(" /c "); + } + else + // should we return here in case of error? + return osl_Process_E_Unknown; + } + + command_line.append(exe_path); + + /* Add remaining arguments to command line. If ustrImageName is nullptr + the first parameter is the name of the executable so we have to + start at 1 instead of 0 */ + for (sal_uInt32 n = (nullptr != ustrImageName) ? 0 : 1; n < nArguments; n++) + { + command_line.append(" "); + + /* Quote arguments containing blanks */ + if (OUString(ustrArguments[n]).indexOf(' ') != -1) + command_line.append(quote_string(ustrArguments[n])); + else + command_line.append(ustrArguments[n]); + } + + std::vector<sal_Unicode> environment; + LPVOID p_environment = nullptr; + + if (nEnvironmentVars && ustrEnvironmentVars) + { + if (!setup_process_environment( + ustrEnvironmentVars, nEnvironmentVars, environment)) + return osl_Process_E_InvalidError; + + flags |= CREATE_UNICODE_ENVIRONMENT; + p_environment = environment.data(); + } + + OUString cwd; + if (ustrDirectory && ustrDirectory->length && (osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(ustrDirectory, cwd))) + return osl_Process_E_InvalidError; + + LPCWSTR p_cwd = (cwd.getLength()) ? o3tl::toW(cwd.getStr()) : nullptr; + + if ((Options & osl_Process_DETACHED) && !(flags & CREATE_NEW_CONSOLE)) + flags |= DETACHED_PROCESS; + + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.lpDesktop = const_cast<LPWSTR>(L""); + + /* Create pipes for redirected IO */ + HANDLE hInputRead = nullptr; + HANDLE hInputWrite = nullptr; + if (pProcessInputWrite && create_pipe(&hInputRead, true, &hInputWrite, false)) + startup_info.hStdInput = hInputRead; + + HANDLE hOutputRead = nullptr; + HANDLE hOutputWrite = nullptr; + if (pProcessOutputRead && create_pipe(&hOutputRead, false, &hOutputWrite, true)) + startup_info.hStdOutput = hOutputWrite; + + HANDLE hErrorRead = nullptr; + HANDLE hErrorWrite = nullptr; + if (pProcessErrorRead && create_pipe(&hErrorRead, false, &hErrorWrite, true)) + startup_info.hStdError = hErrorWrite; + + bool b_inherit_handles = false; + if (pProcessInputWrite || pProcessOutputRead || pProcessErrorRead) + { + startup_info.dwFlags |= STARTF_USESTDHANDLES; + b_inherit_handles = true; + } + + switch(Options & (osl_Process_NORMAL | osl_Process_HIDDEN | osl_Process_MINIMIZED | osl_Process_MAXIMIZED | osl_Process_FULLSCREEN)) + { + case osl_Process_HIDDEN: + startup_info.wShowWindow = SW_HIDE; + flags |= CREATE_NO_WINDOW; // ignored for non-console + // applications; ignored on + // Win9x + break; + + case osl_Process_MINIMIZED: + startup_info.wShowWindow = SW_MINIMIZE; + break; + + case osl_Process_MAXIMIZED: + case osl_Process_FULLSCREEN: + startup_info.wShowWindow = SW_MAXIMIZE; + break; + + default: + startup_info.wShowWindow = SW_NORMAL; + } + + OUString cmdline = command_line.makeStringAndClear(); + PROCESS_INFORMATION process_info; + bool bRet = false; + + if ((Security != nullptr) && (static_cast<oslSecurityImpl*>(Security)->m_hToken != nullptr)) + { + bRet = CreateProcessAsUserW( + static_cast<oslSecurityImpl*>(Security)->m_hToken, + nullptr, const_cast<LPWSTR>(o3tl::toW(cmdline.getStr())), nullptr, nullptr, + b_inherit_handles, flags, p_environment, p_cwd, + &startup_info, &process_info); + } + else + { + bRet = CreateProcessW( + nullptr, const_cast<LPWSTR>(o3tl::toW(cmdline.getStr())), nullptr, nullptr, + b_inherit_handles, flags, p_environment, p_cwd, + &startup_info, &process_info); + } + + /* Now we can close the pipe ends that are used by the child process */ + + if (hInputRead) + CloseHandle(hInputRead); + + if (hOutputWrite) + CloseHandle(hOutputWrite); + + if (hErrorWrite) + CloseHandle(hErrorWrite); + + if (bRet) + { + CloseHandle(process_info.hThread); + + oslProcessImpl* pProcImpl = static_cast<oslProcessImpl*>( + malloc(sizeof(oslProcessImpl))); + + if (pProcImpl != nullptr) + { + pProcImpl->m_hProcess = process_info.hProcess; + pProcImpl->m_IdProcess = process_info.dwProcessId; + + *pProcess = static_cast<oslProcess>(pProcImpl); + + if (Options & osl_Process_WAIT) + WaitForSingleObject(pProcImpl->m_hProcess, INFINITE); + + if (pProcessInputWrite) + *pProcessInputWrite = osl_createFileHandleFromOSHandle(hInputWrite, osl_File_OpenFlag_Write); + + if (pProcessOutputRead) + *pProcessOutputRead = osl_createFileHandleFromOSHandle(hOutputRead, osl_File_OpenFlag_Read); + + if (pProcessErrorRead) + *pProcessErrorRead = osl_createFileHandleFromOSHandle(hErrorRead, osl_File_OpenFlag_Read); + + return osl_Process_E_None; + } + } + + /* if an error occurred we have to close the server side pipe ends too */ + + if (hInputWrite) + CloseHandle(hInputWrite); + + if (hOutputRead) + CloseHandle(hOutputRead); + + if (hErrorRead) + CloseHandle(hErrorRead); + + return osl_Process_E_Unknown; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/procimpl.hxx b/sal/osl/w32/procimpl.hxx new file mode 100644 index 000000000..fb1263fa7 --- /dev/null +++ b/sal/osl/w32/procimpl.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_W32_PROCIMPL_HXX +#define INCLUDED_SAL_OSL_W32_PROCIMPL_HXX + +#include <osl/process.h> + +struct oslProcessImpl +{ + HANDLE m_hProcess; + DWORD m_IdProcess; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/profile.cxx b/sal/osl/w32/profile.cxx new file mode 100644 index 000000000..a0790c3de --- /dev/null +++ b/sal/osl/w32/profile.cxx @@ -0,0 +1,2341 @@ +/* -*- 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 "system.h" + +#include "file_url.hxx" +#include "path_helper.hxx" + +#include <string.h> +#include <osl/diagnose.h> +#include <osl/profile.h> +#include <osl/process.h> +#include <osl/thread.h> +#include <osl/file.h> +#include <rtl/alloc.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <algorithm> +#include <vector> +using std::min; +static void copy_ustr_n( void *dest, const void *source, size_t length ) { memcpy(dest, source, length*sizeof(sal_Unicode)); } + +#define LINES_INI 32 +#define LINES_ADD 10 +#define SECTIONS_INI 5 +#define SECTIONS_ADD 3 +#define ENTRIES_INI 5 +#define ENTRIES_ADD 3 + +#define STR_INI_EXTENSION L".ini" +#define STR_INI_METAHOME "?~" +#define STR_INI_METASYS "?$" +#define STR_INI_METACFG "?^" +#define STR_INI_METAINS "?#" + +#define STR_INI_BOOLYES "yes" +#define STR_INI_BOOLON "on" +#define STR_INI_BOOLONE "1" +#define STR_INI_BOOLNO "no" +#define STR_INI_BOOLOFF "off" +#define STR_INI_BOOLZERO "0" + +#define FLG_USER 0x00FF +#define FLG_AUTOOPEN 0x0100 +#define FLG_MODIFIED 0x0200 + +#define SVERSION_LOCATION STR_INI_METACFG +#define SVERSION_FALLBACK STR_INI_METASYS +#define SVERSION_NAME "sversion" +#define SVERSION_SECTION "Versions" +#define SVERSION_SOFFICE "StarOffice" +#define SVERSION_PROFILE "soffice.ini" +#define SVERSION_DIRS { "bin", "program" } +#define SVERSION_USER "user" + +/*#define DEBUG_OSL_PROFILE 1*/ + +typedef FILETIME osl_TStamp; + +namespace { + +enum osl_TLockMode +{ + un_lock, read_lock, write_lock +}; + +struct osl_TFile +{ + HANDLE m_Handle; + char* m_pReadPtr; + char m_ReadBuf[512]; + char* m_pWriteBuf; + sal_uInt32 m_nWriteBufLen; + sal_uInt32 m_nWriteBufFree; +}; + +struct osl_TProfileEntry +{ + sal_uInt32 m_Line; + sal_uInt32 m_Offset; + sal_uInt32 m_Len; +}; + +struct osl_TProfileSection +{ + sal_uInt32 m_Line; + sal_uInt32 m_Offset; + sal_uInt32 m_Len; + sal_uInt32 m_NoEntries; + sal_uInt32 m_MaxEntries; + osl_TProfileEntry* m_Entries; +}; + +/* + Profile-data structure hidden behind oslProfile: +*/ +struct osl_TProfileImpl +{ + sal_uInt32 m_Flags; + osl_TFile* m_pFile; + osl_TStamp m_Stamp; + sal_uInt32 m_NoLines; + sal_uInt32 m_MaxLines; + sal_uInt32 m_NoSections; + sal_uInt32 m_MaxSections; + char** m_Lines; + rtl_uString *m_strFileName; + osl_TProfileSection* m_Sections; +}; + +} + +static osl_TFile* openFileImpl(rtl_uString * strFileName, oslProfileOption ProfileFlags ); +static osl_TStamp closeFileImpl(osl_TFile* pFile); +static bool lockFile(const osl_TFile* pFile, osl_TLockMode eMode); +static bool rewindFile(osl_TFile* pFile, bool bTruncate); +static osl_TStamp getFileStamp(osl_TFile* pFile); + +static bool getLine(osl_TFile* pFile, char *pszLine, int MaxLen); +static bool putLine(osl_TFile* pFile, const char *pszLine); +static const char* stripBlanks(const char* String, sal_uInt32* pLen); +static const char* addLine(osl_TProfileImpl* pProfile, const char* Line); +static const char* insertLine(osl_TProfileImpl* pProfile, const char* Line, sal_uInt32 LineNo); +static void removeLine(osl_TProfileImpl* pProfile, sal_uInt32 LineNo); +static void setEntry(osl_TProfileImpl* pProfile, osl_TProfileSection* pSection, + sal_uInt32 NoEntry, sal_uInt32 Line, + const char* Entry, sal_uInt32 Len); +static bool addEntry(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection, + int Line, const char* Entry, sal_uInt32 Len); +static void removeEntry(osl_TProfileSection *pSection, sal_uInt32 NoEntry); +static bool addSection(osl_TProfileImpl* pProfile, int Line, const char* Section, sal_uInt32 Len); +static void removeSection(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection); +static osl_TProfileSection* findEntry(osl_TProfileImpl* pProfile, const char* Section, + const char* Entry, sal_uInt32 *pNoEntry); +static bool loadProfile(osl_TFile* pFile, osl_TProfileImpl* pProfile); +static bool storeProfile(osl_TProfileImpl* pProfile, bool bCleanup); +static osl_TProfileImpl* acquireProfile(oslProfile Profile, bool bWriteable); +static bool releaseProfile(osl_TProfileImpl* pProfile); +static bool lookupProfile(const sal_Unicode *strPath, const sal_Unicode *strFile, sal_Unicode *strProfile); + +static bool writeProfileImpl (osl_TFile* pFile); +static osl_TFile* osl_openTmpProfileImpl(osl_TProfileImpl*); +static bool osl_ProfileSwapProfileNames(osl_TProfileImpl*); +static rtl_uString* osl_ProfileGenerateExtension(rtl_uString* ustrFileName, rtl_uString* ustrExtension); + +static bool osl_getProfileName(rtl_uString* strPath, rtl_uString* strName, rtl_uString** strProfileName); + +oslProfile SAL_CALL osl_openProfile(rtl_uString *strProfileName, oslProfileOption Flags) +{ + osl_TFile* pFile = nullptr; + osl_TProfileImpl* pProfile; + rtl_uString *FileName=nullptr; + + OSL_VERIFY(strProfileName); + + if (rtl_uString_getLength(strProfileName) == 0 ) + { + OSL_VERIFY(osl_getProfileName(nullptr, nullptr, &FileName)); + } + else + { + rtl_uString_assign(&FileName, strProfileName); + } + + osl_getSystemPathFromFileURL(FileName, &FileName); + +#ifdef DEBUG_OSL_PROFILE + Flags=osl_Profile_FLUSHWRITE; + + if ( Flags == osl_Profile_DEFAULT ) + { + SAL_INFO("sal.osl", "with osl_Profile_DEFAULT"); + } + if ( Flags & osl_Profile_SYSTEM ) + { + SAL_INFO("sal.osl", "with osl_Profile_SYSTEM"); + } + if ( Flags & osl_Profile_READLOCK ) + { + SAL_INFO("sal.osl", "with osl_Profile_READLOCK"); + } + if ( Flags & osl_Profile_WRITELOCK ) + { + SAL_INFO("sal.osl", "with osl_Profile_WRITELOCK"); + } + if ( Flags & osl_Profile_FLUSHWRITE ) + { + SAL_INFO("sal.osl", "with osl_Profile_FLUSHWRITE"); + } +#endif + + if ( (! (Flags & osl_Profile_SYSTEM)) && ( (pFile = openFileImpl(FileName, Flags) ) == nullptr ) ) + { + if( FileName) + rtl_uString_release( FileName); + + return nullptr; + } + + pProfile = static_cast<osl_TProfileImpl*>(calloc(1, sizeof(osl_TProfileImpl))); + if (!pProfile) + return nullptr; + + pProfile->m_Flags = Flags & FLG_USER; + osl_getSystemPathFromFileURL(strProfileName, &pProfile->m_strFileName); + + if (Flags & (osl_Profile_READLOCK | osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE )) + pProfile->m_pFile = pFile; + + pProfile->m_Stamp = getFileStamp(pFile); + + loadProfile(pFile, pProfile); + + if (pProfile->m_pFile == nullptr) + closeFileImpl(pFile); + + if( FileName) + rtl_uString_release( FileName); + + return pProfile; +} + +sal_Bool SAL_CALL osl_closeProfile(oslProfile Profile) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + + + if ( Profile == nullptr ) + { + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + pProfile = acquireProfile(Profile,true); + + if ( pProfile != nullptr ) + { + if ( !( pProfile->m_Flags & osl_Profile_READLOCK ) && ( pProfile->m_Flags & FLG_MODIFIED ) ) + { + storeProfile(pProfile, false); + } + } + else + { + pProfile = acquireProfile(Profile,false); + } + + if ( pProfile == nullptr ) + { + return false; + } + + if (pProfile->m_pFile != nullptr) + closeFileImpl(pProfile->m_pFile); + } + + pProfile->m_pFile = nullptr; + rtl_uString_release(pProfile->m_strFileName); + pProfile->m_strFileName = nullptr; + + /* release whole profile data types memory */ + if ( pProfile->m_NoLines > 0) + { + unsigned int index=0; + if ( pProfile->m_Lines != nullptr ) + { + for ( index = 0 ; index < pProfile->m_NoLines ; ++index) + { + if ( pProfile->m_Lines[index] != nullptr ) + { + free(pProfile->m_Lines[index]); + } + } + free(pProfile->m_Lines); + } + if ( pProfile->m_Sections != nullptr ) + { + for ( index = 0 ; index < pProfile->m_NoSections ; ++index ) + { + if ( pProfile->m_Sections[index].m_Entries != nullptr ) + free(pProfile->m_Sections[index].m_Entries); + } + free(pProfile->m_Sections); + } + + } + free(pProfile); + + return true; +} + +sal_Bool SAL_CALL osl_flushProfile(oslProfile Profile) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + osl_TFile* pFile; + bool bRet = false; + + + if ( pProfile == nullptr ) + { + return false; + } + + pFile = pProfile->m_pFile; + if ( pFile == nullptr || pFile->m_Handle == INVALID_HANDLE_VALUE ) + { + return false; + } + + if ( pProfile->m_Flags & FLG_MODIFIED ) + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "swapping to storeprofile"); +#endif + bRet = storeProfile(pProfile,false); + } + + return bRet; +} + +static bool writeProfileImpl(osl_TFile* pFile) +{ + DWORD BytesWritten=0; + bool bRet; + + if ( pFile == nullptr || pFile->m_Handle == INVALID_HANDLE_VALUE || ( pFile->m_pWriteBuf == nullptr ) ) + { + return false; + } + + bRet=WriteFile(pFile->m_Handle, pFile->m_pWriteBuf, pFile->m_nWriteBufLen - pFile->m_nWriteBufFree,&BytesWritten,nullptr); + + if ( !bRet || BytesWritten == 0 ) + { + OSL_ENSURE(bRet,"WriteFile failed!!!"); + SAL_WARN("sal.osl", "write failed " << strerror(errno)); + + return false; + } + + free(pFile->m_pWriteBuf); + pFile->m_pWriteBuf=nullptr; + pFile->m_nWriteBufLen=0; + pFile->m_nWriteBufFree=0; + + return true; +} + +namespace { +// Use Unicode version of GetPrivateProfileString, to work with Multi-language paths +DWORD GetPrivateProfileStringWrapper(const osl_TProfileImpl* pProfile, + const char* pszSection, const char* pszEntry, + char* pszString, sal_uInt32 MaxLen, + const char* pszDefault) +{ + OSL_ASSERT(pProfile && (!MaxLen || pszString)); + + rtl_uString *pSection = nullptr, *pEntry = nullptr, *pDefault = nullptr; + if (pszSection) + { + rtl_string2UString(&pSection, pszSection, strlen(pszSection), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pSection); + } + if (pszEntry) + { + rtl_string2UString(&pEntry, pszEntry, strlen(pszEntry), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pEntry); + } + if (pszDefault) + { + rtl_string2UString(&pDefault, pszDefault, strlen(pszDefault), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pDefault); + } + + LPCWSTR pWSection = (pSection ? o3tl::toW(rtl_uString_getStr(pSection)) : nullptr), + pWEntry = (pEntry ? o3tl::toW(rtl_uString_getStr(pEntry)) : nullptr), + pWDefault = (pDefault ? o3tl::toW(rtl_uString_getStr(pDefault)) : nullptr); + + std::vector<wchar_t> aBuf(MaxLen + 1); + GetPrivateProfileStringW(pWSection, pWEntry, pWDefault, aBuf.data(), MaxLen, o3tl::toW(rtl_uString_getStr(pProfile->m_strFileName))); + + if (pDefault) + rtl_uString_release(pDefault); + if (pEntry) + rtl_uString_release(pEntry); + if (pSection) + rtl_uString_release(pSection); + + return WideCharToMultiByte(CP_ACP, 0, aBuf.data(), -1, pszString, MaxLen, nullptr, nullptr); +} + +// Use Unicode version of WritePrivateProfileString, to work with Multi-language paths +bool WritePrivateProfileStringWrapper(const osl_TProfileImpl* pProfile, + const char* pszSection, const char* pszEntry, + const char* pszString) +{ + OSL_ASSERT(pProfile && pszSection); + rtl_uString *pSection, *pEntry = nullptr, *pString = nullptr; + rtl_string2UString(&pSection, pszSection, strlen(pszSection), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pSection); + if (pszEntry) + { + rtl_string2UString(&pEntry, pszEntry, strlen(pszEntry), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pEntry); + } + if (pszString) + { + rtl_string2UString(&pString, pszString, strlen(pszString), osl_getThreadTextEncoding(), OSTRING_TO_OUSTRING_CVTFLAGS); + OSL_ASSERT(pString); + } + + LPCWSTR pWSection = o3tl::toW(pSection->buffer), + pWEntry = (pEntry ? o3tl::toW(rtl_uString_getStr(pEntry)) : nullptr), + pWString = (pString ? o3tl::toW(rtl_uString_getStr(pString)) : nullptr); + + bool bResult = WritePrivateProfileStringW(pWSection, pWEntry, pWString, o3tl::toW(rtl_uString_getStr(pProfile->m_strFileName))); + + if (pString) + rtl_uString_release(pString); + if (pEntry) + rtl_uString_release(pEntry); + rtl_uString_release(pSection); + + return bResult; +} +} + +sal_Bool SAL_CALL osl_readProfileString(oslProfile Profile, + const char* pszSection, const char* pszEntry, + char* pszString, sal_uInt32 MaxLen, + const char* pszDefault) +{ + sal_uInt32 NoEntry; + const char* pStr = nullptr; + osl_TProfileImpl* pProfile = nullptr; + + pProfile = acquireProfile(Profile, false); + + if (pProfile == nullptr) + { + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec; + if (((pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry)) != nullptr) && + (NoEntry < pSec->m_NoEntries) && + ((pStr = strchr(pProfile->m_Lines[pSec->m_Entries[NoEntry].m_Line], + '=')) != nullptr)) + pStr++; + else + pStr = pszDefault; + + if ( pStr != nullptr ) + { + pStr = stripBlanks(pStr, nullptr); + MaxLen = (MaxLen - 1 < strlen(pStr)) ? (MaxLen - 1) : strlen(pStr); + pStr = stripBlanks(pStr, &MaxLen); + strncpy(pszString, pStr, MaxLen); + pszString[MaxLen] = '\0'; + } + } + else + { + if (GetPrivateProfileStringWrapper(pProfile, pszSection, pszEntry, pszString, MaxLen, pszDefault) > 0) + pStr = pszString; // required to return true below + } + + releaseProfile(pProfile); + + if ( pStr == nullptr ) + { + return false; + } + + return true; +} + +sal_Bool SAL_CALL osl_readProfileBool(oslProfile Profile, + const char* pszSection, const char* pszEntry, + sal_Bool Default) +{ + char Line[32]; + + if (osl_readProfileString(Profile, pszSection, pszEntry, Line, sizeof(Line), "")) + { + if ((stricmp(Line, STR_INI_BOOLYES) == 0) || + (stricmp(Line, STR_INI_BOOLON) == 0) || + (stricmp(Line, STR_INI_BOOLONE) == 0)) + Default = true; + else + if ((stricmp(Line, STR_INI_BOOLNO) == 0) || + (stricmp(Line, STR_INI_BOOLOFF) == 0) || + (stricmp(Line, STR_INI_BOOLZERO) == 0)) + Default = false; + } + + return Default; +} + +sal_uInt32 SAL_CALL osl_readProfileIdent(oslProfile Profile, + const char* pszSection, const char* pszEntry, + sal_uInt32 FirstId, const char* Strings[], + sal_uInt32 Default) +{ + sal_uInt32 i; + char Line[256]; + + if (osl_readProfileString(Profile, pszSection, pszEntry, Line, sizeof(Line), "")) + { + i = 0; + while (Strings[i] != nullptr) + { + if (stricmp(Line, Strings[i]) == 0) + { + Default = i + FirstId; + break; + } + i++; + } + } + + return Default; +} + +sal_Bool SAL_CALL osl_writeProfileString(oslProfile Profile, + const char* pszSection, const char* pszEntry, + const char* pszString) +{ + sal_uInt32 i; + bool bRet = false; + sal_uInt32 NoEntry; + const char* pStr; + char Line[4096]; + osl_TProfileSection* pSec; + osl_TProfileImpl* pProfile = nullptr; + + pProfile = acquireProfile(Profile, true); + + if (pProfile == nullptr) + { + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if ((pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry)) == nullptr) + { + Line[0] = '\0'; + addLine(pProfile, Line); + + Line[0] = '['; + strcpy(&Line[1], pszSection); + Line[1 + strlen(pszSection)] = ']'; + Line[2 + strlen(pszSection)] = '\0'; + + if (((pStr = addLine(pProfile, Line)) == nullptr) || + (! addSection(pProfile, pProfile->m_NoLines - 1, &pStr[1], strlen(pszSection)))) + { + releaseProfile(pProfile); + return false; + } + + pSec = &pProfile->m_Sections[pProfile->m_NoSections - 1]; + NoEntry = pSec->m_NoEntries; + } + + Line[0] = '\0'; + strcpy(&Line[0], pszEntry); + Line[0 + strlen(pszEntry)] = '='; + strcpy(&Line[1 + strlen(pszEntry)], pszString); + + if (NoEntry >= pSec->m_NoEntries) + { + if (pSec->m_NoEntries > 0) + i = pSec->m_Entries[pSec->m_NoEntries - 1].m_Line + 1; + else + i = pSec->m_Line + 1; + + if (((pStr = insertLine(pProfile, Line, i)) == nullptr) || + (! addEntry(pProfile, pSec, i, pStr, strlen(pszEntry)))) + { + releaseProfile(pProfile); + return false; + } + + pProfile->m_Flags |= FLG_MODIFIED; + } + else + { + i = pSec->m_Entries[NoEntry].m_Line; + free(pProfile->m_Lines[i]); + pProfile->m_Lines[i] = strdup(Line); + setEntry(pProfile, pSec, NoEntry, i, pProfile->m_Lines[i], strlen(pszEntry)); + + pProfile->m_Flags |= FLG_MODIFIED; + } + } + else + { + WritePrivateProfileStringWrapper(pProfile, pszSection, pszEntry, pszString); + } + + bRet = releaseProfile(pProfile); + return bRet; +} + +sal_Bool SAL_CALL osl_writeProfileBool(oslProfile Profile, + const char* pszSection, const char* pszEntry, + sal_Bool Value) +{ + bool bRet = false; + + if (Value) + bRet=osl_writeProfileString(Profile, pszSection, pszEntry, STR_INI_BOOLONE); + else + bRet=osl_writeProfileString(Profile, pszSection, pszEntry, STR_INI_BOOLZERO); + + return bRet; +} + +sal_Bool SAL_CALL osl_writeProfileIdent(oslProfile Profile, + const char* pszSection, const char* pszEntry, + sal_uInt32 FirstId, const char* Strings[], + sal_uInt32 Value) +{ + int i, n; + bool bRet = false; + + for (n = 0; Strings[n] != nullptr; n++); + + if ((i = Value - FirstId) >= n) + bRet=false; + else + bRet=osl_writeProfileString(Profile, pszSection, pszEntry, Strings[i]); + + return bRet; +} + +sal_Bool SAL_CALL osl_removeProfileEntry(oslProfile Profile, + const char *pszSection, const char *pszEntry) +{ + sal_uInt32 NoEntry; + osl_TProfileImpl* pProfile = nullptr; + bool bRet = false; + + pProfile = acquireProfile(Profile, true); + + if (pProfile == nullptr) + return false; + + if (!(pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec; + if (((pSec = findEntry(pProfile, pszSection, pszEntry, &NoEntry)) != nullptr) && + (NoEntry < pSec->m_NoEntries)) + { + removeLine(pProfile, pSec->m_Entries[NoEntry].m_Line); + removeEntry(pSec, NoEntry); + if (pSec->m_NoEntries == 0) + { + removeLine(pProfile, pSec->m_Line); + + /* remove any empty separation line */ + if ((pSec->m_Line > 0) && (pProfile->m_Lines[pSec->m_Line - 1][0] == '\0')) + removeLine(pProfile, pSec->m_Line - 1); + + removeSection(pProfile, pSec); + } + + pProfile->m_Flags |= FLG_MODIFIED; + } + } + else + { + WritePrivateProfileStringWrapper(pProfile, pszSection, pszEntry, nullptr); + } + + bRet = releaseProfile(pProfile); + return bRet; +} + +sal_uInt32 SAL_CALL osl_getProfileSectionEntries(oslProfile Profile, const char *pszSection, + char* pszBuffer, sal_uInt32 MaxLen) +{ + sal_uInt32 i, n = 0; + sal_uInt32 NoEntry; + osl_TProfileImpl* pProfile = nullptr; + + pProfile = acquireProfile(Profile, false); + + if (pProfile == nullptr) + return 0; + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + osl_TProfileSection* pSec; + if ((pSec = findEntry(pProfile, pszSection, "", &NoEntry)) != nullptr) + { + if (MaxLen != 0) + { + for (i = 0; i < pSec->m_NoEntries; i++) + { + if ((n + pSec->m_Entries[i].m_Len + 1) < MaxLen) + { + strncpy(&pszBuffer[n], &pProfile->m_Lines[pSec->m_Entries[i].m_Line] + [pSec->m_Entries[i].m_Offset], pSec->m_Entries[i].m_Len); + n += pSec->m_Entries[i].m_Len; + pszBuffer[n++] = '\0'; + } + else + { + break; + } + + } + + pszBuffer[n++] = '\0'; + } + else + { + for (i = 0; i < pSec->m_NoEntries; i++) + { + n += pSec->m_Entries[i].m_Len + 1; + } + + n += 1; + } + } + else + { + n = 0; + } + } + else + { + n = GetPrivateProfileStringWrapper(pProfile, pszSection, nullptr, pszBuffer, MaxLen, nullptr); + } + + releaseProfile(pProfile); + + return n; +} + +bool osl_getProfileName(rtl_uString* strPath, rtl_uString* strName, rtl_uString** strProfileName) +{ + bool bFailed; + ::osl::LongPathBuffer< sal_Unicode > aFile( MAX_LONG_PATH ); + ::osl::LongPathBuffer< sal_Unicode > aPath( MAX_LONG_PATH ); + sal_uInt32 nFileLen = 0; + sal_uInt32 nPathLen = 0; + + rtl_uString * strTmp = nullptr; + oslFileError nError; + + /* build file name */ + if (strName && strName->length) + { + if( ::sal::static_int_cast< sal_uInt32 >( strName->length ) >= aFile.getBufSizeInSymbols() ) + return false; + + copy_ustr_n( aFile, strName->buffer, strName->length+1); + nFileLen = strName->length; + + if (rtl_ustr_indexOfChar( aFile, L'.' ) == -1) + { + if (nFileLen + wcslen(STR_INI_EXTENSION) >= aFile.getBufSizeInSymbols()) + return false; + + /* add default extension */ + copy_ustr_n( aFile + nFileLen, STR_INI_EXTENSION, wcslen(STR_INI_EXTENSION)+1 ); + nFileLen += wcslen(STR_INI_EXTENSION); + } + } + else + { + rtl_uString *strProgName = nullptr; + sal_Unicode *pProgName; + sal_Int32 nOffset = 0; + sal_Int32 nLen; + sal_Int32 nPos; + + if (osl_getExecutableFile(&strProgName) != osl_Process_E_None) + return false; + + /* remove path and extension from filename */ + pProgName = strProgName->buffer; + nLen = strProgName->length ; + + if ((nPos = rtl_ustr_lastIndexOfChar( pProgName, L'/' )) != -1) + nOffset = nPos + 1; + else if ((nPos = rtl_ustr_lastIndexOfChar( pProgName, L':' )) != -1) + nOffset = nPos + 1; + + if ((nPos = rtl_ustr_lastIndexOfChar( pProgName, L'.' )) != -1 ) + nLen -= 4; + + if ((nFileLen = nLen - nOffset) >= aFile.getBufSizeInSymbols()) + return false; + + copy_ustr_n(aFile, pProgName + nOffset, nFileLen); + + if (nFileLen + wcslen(STR_INI_EXTENSION) >= aFile.getBufSizeInSymbols()) + return false; + + /* add default extension */ + copy_ustr_n(aFile + nFileLen, STR_INI_EXTENSION, wcslen(STR_INI_EXTENSION)+1); + nFileLen += wcslen(STR_INI_EXTENSION); + + rtl_uString_release( strProgName ); + } + + if (aFile[0] == 0) + return false; + + /* build directory path */ + if (strPath && strPath->length) + { + sal_Unicode *pPath = rtl_uString_getStr(strPath); + sal_Int32 nLen = rtl_uString_getLength(strPath); + + if ((rtl_ustr_ascii_compare_WithLength(pPath, RTL_CONSTASCII_LENGTH(STR_INI_METAHOME) , STR_INI_METAHOME) == 0) && + ((nLen == RTL_CONSTASCII_LENGTH(STR_INI_METAHOME)) || (pPath[RTL_CONSTASCII_LENGTH(STR_INI_METAHOME)] == '/'))) + { + rtl_uString * strHome = nullptr; + oslSecurity security = osl_getCurrentSecurity(); + + bFailed = ! osl_getHomeDir(security, &strHome); + osl_freeSecurityHandle(security); + + if (bFailed) return false; + + if ( ::sal::static_int_cast< sal_uInt32 >( strHome->length ) >= aPath.getBufSizeInSymbols()) + return false; + + copy_ustr_n( aPath, strHome->buffer, strHome->length+1); + nPathLen = strHome->length; + + if (nLen > RTL_CONSTASCII_LENGTH(STR_INI_METAHOME)) + { + pPath += RTL_CONSTASCII_LENGTH(STR_INI_METAHOME); + nLen -= RTL_CONSTASCII_LENGTH(STR_INI_METAHOME); + + if (nLen + nPathLen >= aPath.getBufSizeInSymbols()) + return false; + + copy_ustr_n(aPath + nPathLen, pPath, nLen+1); + nPathLen += nLen; + } + + rtl_uString_release(strHome); + } + + else if ((rtl_ustr_ascii_compare_WithLength(pPath, RTL_CONSTASCII_LENGTH(STR_INI_METACFG), STR_INI_METACFG) == 0) && + ((nLen == RTL_CONSTASCII_LENGTH(STR_INI_METACFG)) || (pPath[RTL_CONSTASCII_LENGTH(STR_INI_METACFG)] == '/'))) + { + rtl_uString * strConfig = nullptr; + oslSecurity security = osl_getCurrentSecurity(); + + bFailed = ! osl_getConfigDir(security, &strConfig); + osl_freeSecurityHandle(security); + + if (bFailed) return false; + + if ( ::sal::static_int_cast< sal_uInt32 >( strConfig->length ) >= aPath.getBufSizeInSymbols()) + return false; + + copy_ustr_n( aPath, strConfig->buffer, strConfig->length+1 ); + nPathLen = strConfig->length; + + if (nLen > RTL_CONSTASCII_LENGTH(STR_INI_METACFG)) + { + pPath += RTL_CONSTASCII_LENGTH(STR_INI_METACFG); + nLen -= RTL_CONSTASCII_LENGTH(STR_INI_METACFG); + + if (nLen + nPathLen >= aPath.getBufSizeInSymbols()) + return false; + + copy_ustr_n(aPath + nPathLen, pPath, nLen+1); + nPathLen += nLen; + } + + rtl_uString_release(strConfig); + } + + else if ((rtl_ustr_ascii_compare_WithLength(pPath, RTL_CONSTASCII_LENGTH(STR_INI_METASYS), STR_INI_METASYS) == 0) && + ((nLen == RTL_CONSTASCII_LENGTH(STR_INI_METASYS)) || (pPath[RTL_CONSTASCII_LENGTH(STR_INI_METASYS)] == '/'))) + { + if (((nPathLen = GetWindowsDirectoryW(o3tl::toW(aPath), aPath.getBufSizeInSymbols())) == 0) || (nPathLen >= aPath.getBufSizeInSymbols())) + return false; + + if (nLen > RTL_CONSTASCII_LENGTH(STR_INI_METASYS)) + { + pPath += RTL_CONSTASCII_LENGTH(STR_INI_METASYS); + nLen -= RTL_CONSTASCII_LENGTH(STR_INI_METASYS); + + if (nLen + nPathLen >= aPath.getBufSizeInSymbols()) + return false; + + copy_ustr_n(aPath + nPathLen, pPath, nLen+1); + nPathLen += nLen; + } + } + + else if ((rtl_ustr_ascii_compare_WithLength(pPath, RTL_CONSTASCII_LENGTH(STR_INI_METAINS), STR_INI_METAINS) == 0) && + ((nLen == RTL_CONSTASCII_LENGTH(STR_INI_METAINS)) || (pPath[RTL_CONSTASCII_LENGTH(STR_INI_METAINS)] == '/') || + (pPath[RTL_CONSTASCII_LENGTH(STR_INI_METAINS)] == '"') ) ) + { + if (! lookupProfile(pPath + RTL_CONSTASCII_LENGTH(STR_INI_METAINS), aFile, aPath)) + return false; + + nPathLen = rtl_ustr_getLength(aPath); + } + + else if( ::sal::static_int_cast< sal_uInt32 >( nLen ) < aPath.getBufSizeInSymbols()) + { + copy_ustr_n(aPath, pPath, nLen+1); + nPathLen = rtl_ustr_getLength(aPath); + } + else + return false; + } + else + { + rtl_uString * strConfigDir = nullptr; + oslSecurity security = osl_getCurrentSecurity(); + + bFailed = ! osl_getConfigDir(security, &strConfigDir); + osl_freeSecurityHandle(security); + + if (bFailed) return false; + if ( ::sal::static_int_cast< sal_uInt32 >( strConfigDir->length ) >= aPath.getBufSizeInSymbols() ) + return false; + + copy_ustr_n(aPath, strConfigDir->buffer, strConfigDir->length+1); + nPathLen = strConfigDir->length; + } + + if (nPathLen && (aPath[nPathLen - 1] != L'/') && (aPath[nPathLen - 1] != L'\\')) + { + aPath[nPathLen++] = L'\\'; + aPath[nPathLen] = 0; + } + + if (nPathLen + nFileLen >= aPath.getBufSizeInSymbols()) + return false; + + /* append file name */ + copy_ustr_n(aPath + nPathLen, aFile, nFileLen+1); + nPathLen += nFileLen; + + /* copy filename */ + rtl_uString_newFromStr_WithLength(&strTmp, aPath, nPathLen); + nError = osl_getFileURLFromSystemPath(strTmp, strProfileName); + rtl_uString_release(strTmp); + + return nError == osl_File_E_None; +} + +sal_uInt32 SAL_CALL osl_getProfileSections(oslProfile Profile, char* pszBuffer, sal_uInt32 MaxLen) +{ + sal_uInt32 i, n = 0; + osl_TProfileImpl* pProfile = acquireProfile(Profile, false); + + if (pProfile == nullptr) + return 0; + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if (MaxLen != 0) + { + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if ((n + pSec->m_Len + 1) < MaxLen) + { + strncpy(&pszBuffer[n], &pProfile->m_Lines[pSec->m_Line][pSec->m_Offset], + pSec->m_Len); + n += pSec->m_Len; + pszBuffer[n++] = '\0'; + } + else + break; + } + + pszBuffer[n++] = '\0'; + } + else + { + for (i = 0; i < pProfile->m_NoSections; i++) + n += pProfile->m_Sections[i].m_Len + 1; + + n += 1; + } + } + else + { + std::vector<wchar_t> aBuf(MaxLen + 1); + GetPrivateProfileSectionNamesW(aBuf.data(), MaxLen, o3tl::toW(rtl_uString_getStr(pProfile->m_strFileName))); + + n = WideCharToMultiByte(CP_ACP, 0, aBuf.data(), -1, pszBuffer, MaxLen, nullptr, nullptr); + } + + releaseProfile(pProfile); + + return n; +} + +static osl_TStamp getFileStamp(osl_TFile* pFile) +{ + FILETIME FileTime; + + if ((pFile->m_Handle == INVALID_HANDLE_VALUE) || + (! GetFileTime(pFile->m_Handle, nullptr, nullptr, &FileTime))) + memset(&FileTime, 0, sizeof(FileTime)); + + return FileTime; +} + +static bool lockFile(const osl_TFile* pFile, osl_TLockMode eMode) +{ + bool status = false; + OVERLAPPED Overlapped = {}; + + if (pFile->m_Handle == INVALID_HANDLE_VALUE) + return false; + + switch (eMode) + { + case un_lock: + status = UnlockFileEx( + pFile->m_Handle, 0, 0xFFFFFFFF, 0, &Overlapped); + break; + + case read_lock: + status = LockFileEx( + pFile->m_Handle, 0, 0, 0xFFFFFFFF, 0, &Overlapped); + break; + + case write_lock: + status = LockFileEx( + pFile->m_Handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 0xFFFFFFFF, 0, + &Overlapped); + break; + } + + return status; +} + +static osl_TFile* openFileImpl(rtl_uString * strFileName, oslProfileOption ProfileFlags ) +{ + osl_TFile* pFile = static_cast< osl_TFile*>( calloc( 1, sizeof(osl_TFile) ) ); + if (!pFile) + return nullptr; + bool bWriteable = false; + + if ( ProfileFlags & ( osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ) ) + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "setting bWriteable to TRUE"); +#endif + bWriteable=true; + } + + if (! bWriteable) + { + pFile->m_Handle = CreateFileW( o3tl::toW(rtl_uString_getStr( strFileName )), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + /* mfe: argghh!!! do not check if the file could be opened */ + /* default mode expects it that way!!! */ + } + else + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "opening read/write " << pszFilename); +#endif + + if ((pFile->m_Handle = CreateFileW( o3tl::toW(rtl_uString_getStr( strFileName )), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)) + == INVALID_HANDLE_VALUE) + { + free(pFile); + return nullptr; + } + } + + pFile->m_pWriteBuf=nullptr; + pFile->m_nWriteBufFree=0; + pFile->m_nWriteBufLen=0; + + if ( ProfileFlags & (osl_Profile_WRITELOCK | osl_Profile_READLOCK ) ) + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "locking file " << pszFilename); +#endif + + lockFile(pFile, bWriteable ? write_lock : read_lock); + } + + return pFile; +} + +static osl_TStamp closeFileImpl(osl_TFile* pFile) +{ + osl_TStamp stamp = {0, 0}; + + if ( pFile == nullptr ) + { + return stamp; + } + + if (pFile->m_Handle != INVALID_HANDLE_VALUE) + { + stamp = getFileStamp(pFile); + + lockFile(pFile, un_lock); + + CloseHandle(pFile->m_Handle); + pFile->m_Handle = INVALID_HANDLE_VALUE; + } + + if ( pFile->m_pWriteBuf != nullptr ) + { + free(pFile->m_pWriteBuf); + } + + free(pFile); + + return stamp; +} + +static bool rewindFile(osl_TFile* pFile, bool bTruncate) +{ + if (pFile->m_Handle != INVALID_HANDLE_VALUE) + { + pFile->m_pReadPtr = pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf); + + SetFilePointer(pFile->m_Handle, 0, nullptr, FILE_BEGIN); + + if (bTruncate) + SetEndOfFile(pFile->m_Handle); + } + + return true; +} + +static bool getLine(osl_TFile* pFile, char *pszLine, int MaxLen) +{ + DWORD Max; + size_t Free; + char* pChr; + char* pLine = pszLine; + + if (pFile->m_Handle == INVALID_HANDLE_VALUE) + return false; + + MaxLen -= 1; + + do + { + size_t Bytes = sizeof(pFile->m_ReadBuf) - (pFile->m_pReadPtr - pFile->m_ReadBuf); + + if (Bytes <= 1) + { + /* refill buffer */ + memcpy(pFile->m_ReadBuf, pFile->m_pReadPtr, Bytes); + pFile->m_pReadPtr = pFile->m_ReadBuf; + + Free = sizeof(pFile->m_ReadBuf) - Bytes; + + if (! ReadFile(pFile->m_Handle, &pFile->m_ReadBuf[Bytes], Free, &Max, nullptr)) + { + *pLine = '\0'; + return false; + } + + if (Max < Free) + { + if ((Max == 0) && (pLine == pszLine)) + { + *pLine = '\0'; + return false; + } + + pFile->m_ReadBuf[Bytes + Max] = '\0'; + } + } + + for (pChr = pFile->m_pReadPtr; + (*pChr != '\n') && (*pChr != '\r') && (*pChr != '\0') && + (pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf) - 1)); + pChr++); + + Max = min(static_cast<int>(pChr - pFile->m_pReadPtr), MaxLen); + memcpy(pLine, pFile->m_pReadPtr, Max); + MaxLen -= Max; + pLine += Max; + + if (pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf) - 1)) + { + if (*pChr != '\0') + { + if ((pChr[0] == '\r') && (pChr[1] == '\n')) + pChr += 2; + else + pChr += 1; + } + + if ((pChr < (pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf))) && + (*pChr == '\0')) + pChr = pFile->m_ReadBuf + sizeof(pFile->m_ReadBuf); + + *pLine = '\0'; + + /* setting MaxLen to -1 indicates terminating read loop */ + MaxLen = -1; + } + + pFile->m_pReadPtr = pChr; + } + while (MaxLen > 0); + + return true; +} + +static bool putLine(osl_TFile* pFile, const char *pszLine) +{ + unsigned int Len = strlen(pszLine); + + if ( pFile == nullptr || pFile->m_Handle == INVALID_HANDLE_VALUE ) + { + return false; + } + + if ( pFile->m_pWriteBuf == nullptr ) + { + pFile->m_pWriteBuf = static_cast<char*>(malloc(Len+3)); + pFile->m_nWriteBufLen = Len+3; + pFile->m_nWriteBufFree = Len+3; + } + else + { + if ( pFile->m_nWriteBufFree <= Len + 3 ) + { + char* pTmp; + + pTmp=static_cast<char*>(realloc(pFile->m_pWriteBuf,( ( pFile->m_nWriteBufLen + Len ) * 2) )); + if ( pTmp == nullptr ) + { + return false; + } + pFile->m_pWriteBuf = pTmp; + pFile->m_nWriteBufFree = pFile->m_nWriteBufFree + pFile->m_nWriteBufLen + ( 2 * Len ); + pFile->m_nWriteBufLen = ( pFile->m_nWriteBufLen + Len ) * 2; + memset( (pFile->m_pWriteBuf) + ( pFile->m_nWriteBufLen - pFile->m_nWriteBufFree ), 0, pFile->m_nWriteBufFree); + } + } + + memcpy(pFile->m_pWriteBuf + ( pFile->m_nWriteBufLen - pFile->m_nWriteBufFree ),pszLine,Len+1); + + pFile->m_pWriteBuf[pFile->m_nWriteBufLen - pFile->m_nWriteBufFree + Len]='\r'; + pFile->m_pWriteBuf[pFile->m_nWriteBufLen - pFile->m_nWriteBufFree + Len + 1]='\n'; + pFile->m_pWriteBuf[pFile->m_nWriteBufLen - pFile->m_nWriteBufFree + Len + 2]='\0'; + + pFile->m_nWriteBufFree-=Len+2; + + return true; +} + +/* platform specific end */ + +static const char* stripBlanks(const char* String, sal_uInt32* pLen) +{ + if ( (pLen != nullptr) && ( *pLen != 0 ) ) + { + while ((String[*pLen - 1] == ' ') || (String[*pLen - 1] == '\t')) + (*pLen)--; + + while ((*String == ' ') || (*String == '\t')) + { + String++; + (*pLen)--; + } + } + else + while ((*String == ' ') || (*String == '\t')) + String++; + + return String; +} + +static const char* addLine(osl_TProfileImpl* pProfile, const char* Line) +{ + if (pProfile->m_NoLines >= pProfile->m_MaxLines) + { + if (pProfile->m_Lines == nullptr) + { + pProfile->m_MaxLines = LINES_INI; + pProfile->m_Lines = static_cast<char **>(calloc(pProfile->m_MaxLines, sizeof(char *))); + } + else + { + unsigned int index=0; + unsigned int oldmax=pProfile->m_MaxLines; + + pProfile->m_MaxLines += LINES_ADD; + if (auto p = static_cast<char **>(realloc(pProfile->m_Lines, pProfile->m_MaxLines * sizeof(char *)))) + { + pProfile->m_Lines = p; + + for ( index = oldmax ; index < pProfile->m_MaxLines ; ++index ) + { + pProfile->m_Lines[index]=nullptr; + } + } + else + { + free(pProfile->m_Lines); + pProfile->m_Lines = nullptr; + } + } + + if (pProfile->m_Lines == nullptr) + { + pProfile->m_NoLines = 0; + pProfile->m_MaxLines = 0; + return nullptr; + } + + } + + if ( pProfile->m_Lines != nullptr && pProfile->m_Lines[pProfile->m_NoLines] != nullptr ) + { + free(pProfile->m_Lines[pProfile->m_NoLines]); + } + pProfile->m_Lines[pProfile->m_NoLines++] = strdup(Line); + + return pProfile->m_Lines[pProfile->m_NoLines - 1]; +} + +static const char* insertLine(osl_TProfileImpl* pProfile, const char* Line, sal_uInt32 LineNo) +{ + if (pProfile->m_NoLines >= pProfile->m_MaxLines) + { + if (pProfile->m_Lines == nullptr) + { + pProfile->m_MaxLines = LINES_INI; + pProfile->m_Lines = static_cast<char **>(calloc(pProfile->m_MaxLines, sizeof(char *))); + } + else + { + pProfile->m_MaxLines += LINES_ADD; + if (auto p = static_cast<char**>( + realloc(pProfile->m_Lines, pProfile->m_MaxLines * sizeof(char*)))) + { + pProfile->m_Lines = p; + + memset(&pProfile->m_Lines[pProfile->m_NoLines], 0, + (pProfile->m_MaxLines - pProfile->m_NoLines - 1) * sizeof(char*)); + } + else + { + free(pProfile->m_Lines); + pProfile->m_Lines = nullptr; + } + } + + if (pProfile->m_Lines == nullptr) + { + pProfile->m_NoLines = 0; + pProfile->m_MaxLines = 0; + return nullptr; + } + } + + LineNo = std::min(LineNo, pProfile->m_NoLines); + + if (LineNo < pProfile->m_NoLines) + { + sal_uInt32 i, n; + + memmove(&pProfile->m_Lines[LineNo + 1], &pProfile->m_Lines[LineNo], + (pProfile->m_NoLines - LineNo) * sizeof(char *)); + + /* adjust line references */ + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if (pSec->m_Line >= LineNo) + pSec->m_Line++; + + for (n = 0; n < pSec->m_NoEntries; n++) + if (pSec->m_Entries[n].m_Line >= LineNo) + pSec->m_Entries[n].m_Line++; + } + } + + pProfile->m_NoLines++; + + pProfile->m_Lines[LineNo] = strdup(Line); + + return pProfile->m_Lines[LineNo]; +} + +static void removeLine(osl_TProfileImpl* pProfile, sal_uInt32 LineNo) +{ + if (LineNo < pProfile->m_NoLines) + { + free(pProfile->m_Lines[LineNo]); + pProfile->m_Lines[LineNo]=nullptr; + if (pProfile->m_NoLines - LineNo > 1) + { + sal_uInt32 i, n; + + memmove(&pProfile->m_Lines[LineNo], &pProfile->m_Lines[LineNo + 1], + (pProfile->m_NoLines - LineNo - 1) * sizeof(char *)); + + memset(&pProfile->m_Lines[pProfile->m_NoLines - 1], + 0, + (pProfile->m_MaxLines - pProfile->m_NoLines) * sizeof(char*)); + + /* adjust line references */ + for (i = 0; i < pProfile->m_NoSections; i++) + { + osl_TProfileSection* pSec = &pProfile->m_Sections[i]; + + if (pSec->m_Line > LineNo) + pSec->m_Line--; + + for (n = 0; n < pSec->m_NoEntries; n++) + if (pSec->m_Entries[n].m_Line > LineNo) + pSec->m_Entries[n].m_Line--; + } + } + else + { + pProfile->m_Lines[LineNo] = nullptr; + } + + pProfile->m_NoLines--; + } + + return; +} + +static void setEntry(osl_TProfileImpl* pProfile, osl_TProfileSection* pSection, + sal_uInt32 NoEntry, sal_uInt32 Line, + const char* Entry, sal_uInt32 Len) +{ + Entry = stripBlanks(Entry, &Len); + pSection->m_Entries[NoEntry].m_Line = Line; + pSection->m_Entries[NoEntry].m_Offset = Entry - pProfile->m_Lines[Line]; + pSection->m_Entries[NoEntry].m_Len = Len; + + return; +} + +static bool addEntry(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection, + int Line, const char* Entry, sal_uInt32 Len) +{ + if (pSection != nullptr) + { + if (pSection->m_NoEntries >= pSection->m_MaxEntries) + { + if (pSection->m_Entries == nullptr) + { + pSection->m_MaxEntries = ENTRIES_INI; + pSection->m_Entries = static_cast<osl_TProfileEntry *>(malloc( + pSection->m_MaxEntries * sizeof(osl_TProfileEntry))); + } + else + { + pSection->m_MaxEntries += ENTRIES_ADD; + if (auto p = static_cast<osl_TProfileEntry*>(realloc( + pSection->m_Entries, pSection->m_MaxEntries * sizeof(osl_TProfileEntry)))) + pSection->m_Entries = p; + else + { + free(pSection->m_Entries); + pSection->m_Entries = nullptr; + } + } + + if (pSection->m_Entries == nullptr) + { + pSection->m_NoEntries = 0; + pSection->m_MaxEntries = 0; + return false; + } + } + + pSection->m_NoEntries++; + + Entry = stripBlanks(Entry, &Len); + setEntry(pProfile, pSection, pSection->m_NoEntries - 1, Line, + Entry, Len); + + return true; + } + + return false; +} + +static void removeEntry(osl_TProfileSection *pSection, sal_uInt32 NoEntry) +{ + if (NoEntry < pSection->m_NoEntries) + { + if (pSection->m_NoEntries - NoEntry > 1) + { + memmove(&pSection->m_Entries[NoEntry], + &pSection->m_Entries[NoEntry + 1], + (pSection->m_NoEntries - NoEntry - 1) * sizeof(osl_TProfileEntry)); + pSection->m_Entries[pSection->m_NoEntries - 1].m_Line=0; + pSection->m_Entries[pSection->m_NoEntries - 1].m_Offset=0; + pSection->m_Entries[pSection->m_NoEntries - 1].m_Len=0; + } + + pSection->m_NoEntries--; + } + + return; +} + +static bool addSection(osl_TProfileImpl* pProfile, int Line, const char* Section, sal_uInt32 Len) +{ + if (pProfile->m_NoSections >= pProfile->m_MaxSections) + { + if (pProfile->m_Sections == nullptr) + { + pProfile->m_MaxSections = SECTIONS_INI; + pProfile->m_Sections = static_cast<osl_TProfileSection*>(calloc(pProfile->m_MaxSections, sizeof(osl_TProfileSection))); + } + else + { + unsigned int index=0; + unsigned int oldmax=pProfile->m_MaxSections; + + pProfile->m_MaxSections += SECTIONS_ADD; + if (auto p = static_cast<osl_TProfileSection*>(realloc( + pProfile->m_Sections, pProfile->m_MaxSections * sizeof(osl_TProfileSection)))) + { + pProfile->m_Sections = p; + for ( index = oldmax ; index < pProfile->m_MaxSections ; ++index ) + { + pProfile->m_Sections[index].m_Entries=nullptr; + } + } + else + { + free(pProfile->m_Sections); + pProfile->m_Sections = nullptr; + } + } + + if (pProfile->m_Sections == nullptr) + { + pProfile->m_NoSections = 0; + pProfile->m_MaxSections = 0; + return false; + } + } + + pProfile->m_NoSections++; + + if ( pProfile->m_Sections[(pProfile->m_NoSections) - 1].m_Entries != nullptr ) + { + free(pProfile->m_Sections[(pProfile->m_NoSections) - 1].m_Entries); + } + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Entries = nullptr; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_NoEntries = 0; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_MaxEntries = 0; + + Section = stripBlanks(Section, &Len); + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Line = Line; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Offset = Section - pProfile->m_Lines[Line]; + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Len = Len; + + return true; +} + +static void removeSection(osl_TProfileImpl* pProfile, osl_TProfileSection *pSection) +{ + sal_uInt32 Section; + + if ((Section = pSection - pProfile->m_Sections) < pProfile->m_NoSections) + { + free (pSection->m_Entries); + pSection->m_Entries=nullptr; + if (pProfile->m_NoSections - Section > 1) + { + memmove(&pProfile->m_Sections[Section], &pProfile->m_Sections[Section + 1], + (pProfile->m_NoSections - Section - 1) * sizeof(osl_TProfileSection)); + + memset(&pProfile->m_Sections[pProfile->m_NoSections - 1], + 0, + (pProfile->m_MaxSections - pProfile->m_NoSections) * sizeof(osl_TProfileSection)); + pProfile->m_Sections[pProfile->m_NoSections - 1].m_Entries = nullptr; + } + else + { + pSection->m_Entries = nullptr; + } + + pProfile->m_NoSections--; + } + + return; +} + +static osl_TProfileSection* findEntry(osl_TProfileImpl* pProfile, const char* Section, + const char* Entry, sal_uInt32 *pNoEntry) +{ + static sal_uInt32 Sect = 0; + sal_uInt32 i, n; + sal_uInt32 Len; + osl_TProfileSection* pSec = nullptr; + + Len = strlen(Section); + Section = stripBlanks(Section, &Len); + + n = Sect; + + for (i = 0; i < pProfile->m_NoSections; i++) + { + n %= pProfile->m_NoSections; + pSec = &pProfile->m_Sections[n]; + if ((Len == pSec->m_Len) && + (strnicmp(Section, &pProfile->m_Lines[pSec->m_Line][pSec->m_Offset], pSec->m_Len) + == 0)) + break; + n++; + } + + Sect = n; + + if (i < pProfile->m_NoSections) + { + Len = strlen(Entry); + Entry = stripBlanks(Entry, &Len); + + *pNoEntry = pSec->m_NoEntries; + + for (i = 0; i < pSec->m_NoEntries; i++) + { + const char* pStr = &pProfile->m_Lines[pSec->m_Entries[i].m_Line] + [pSec->m_Entries[i].m_Offset]; + if ((Len == pSec->m_Entries[i].m_Len) && + (strnicmp(Entry, pStr, pSec->m_Entries[i].m_Len) + == 0)) + { + *pNoEntry = i; + break; + } + } + } + else + pSec = nullptr; + + return pSec; +} + +static bool loadProfile(osl_TFile* pFile, osl_TProfileImpl* pProfile) +{ + sal_uInt32 i; + char const * pStr; + char const * pChar; + char Line[4096]; + + pProfile->m_NoLines = 0; + pProfile->m_NoSections = 0; + + OSL_VERIFY(rewindFile(pFile, false)); + + while (getLine(pFile, Line, sizeof(Line))) + { + if (! addLine(pProfile, Line)) + return false; + } + + for (i = 0; i < pProfile->m_NoLines; i++) + { + pStr = stripBlanks(pProfile->m_Lines[i], nullptr); + + if ((*pStr == '\0') || (*pStr == ';')) + continue; + + if ((*pStr != '[') || ((pChar = strrchr(pStr, ']')) == nullptr) || + ((pChar - pStr) <= 2)) + { + /* insert entry */ + + if (pProfile->m_NoSections < 1) + continue; + + if ((pChar = strchr(pStr, '=')) == nullptr) + pChar = pStr + strlen(pStr); + + if (! addEntry(pProfile, &pProfile->m_Sections[pProfile->m_NoSections - 1], + i, pStr, pChar - pStr)) + return false; + } + else + { + /* new section */ + if (! addSection(pProfile, i, pStr + 1, pChar - pStr - 1)) + return false; + } + } + + return true; +} + +static bool storeProfile(osl_TProfileImpl* pProfile, bool bCleanup) +{ + if (pProfile->m_Lines != nullptr) + { + if (pProfile->m_Flags & FLG_MODIFIED) + { + sal_uInt32 i; + + osl_TFile* pTmpFile = osl_openTmpProfileImpl(pProfile); + + if ( pTmpFile == nullptr ) + { + return false; + } + + OSL_VERIFY(rewindFile(pTmpFile, true)); + + for (i = 0; i < pProfile->m_NoLines; i++) + { + OSL_VERIFY(putLine(pTmpFile, pProfile->m_Lines[i])); + } + + if ( ! writeProfileImpl(pTmpFile) ) + { + if ( pTmpFile->m_pWriteBuf != nullptr ) + { + free(pTmpFile->m_pWriteBuf); + } + + pTmpFile->m_pWriteBuf=nullptr; + pTmpFile->m_nWriteBufLen=0; + pTmpFile->m_nWriteBufFree=0; + + closeFileImpl(pTmpFile); + + return false; + } + + pProfile->m_Flags &= ~FLG_MODIFIED; + + closeFileImpl(pProfile->m_pFile); + closeFileImpl(pTmpFile); + + osl_ProfileSwapProfileNames(pProfile); + + pProfile->m_pFile = openFileImpl(pProfile->m_strFileName,pProfile->m_Flags); + + } + + if (bCleanup) + { + while (pProfile->m_NoLines > 0) + removeLine(pProfile, pProfile->m_NoLines - 1); + + free(pProfile->m_Lines); + pProfile->m_Lines = nullptr; + pProfile->m_MaxLines = 0; + + while (pProfile->m_NoSections > 0) + removeSection(pProfile, &pProfile->m_Sections[pProfile->m_NoSections - 1]); + + free(pProfile->m_Sections); + pProfile->m_Sections = nullptr; + pProfile->m_MaxSections = 0; + } + } + + return true; +} + +static osl_TFile* osl_openTmpProfileImpl(osl_TProfileImpl* pProfile) +{ + osl_TFile* pFile=nullptr; + rtl_uString* ustrExtension=nullptr; + rtl_uString* ustrTmpName=nullptr; + oslProfileOption PFlags=0; + + rtl_uString_newFromAscii(&ustrExtension,"tmp"); + + /* generate tmp profilename */ + ustrTmpName=osl_ProfileGenerateExtension(pProfile->m_strFileName,ustrExtension); + rtl_uString_release(ustrExtension); + + if (ustrTmpName == nullptr) + return nullptr; + + if (!(pProfile->m_Flags & osl_Profile_READLOCK)) + PFlags |= osl_Profile_WRITELOCK; + + /* open this file */ + pFile = openFileImpl(ustrTmpName,pProfile->m_Flags | PFlags); + + /* return new pFile */ + return pFile; +} + +static bool osl_ProfileSwapProfileNames(osl_TProfileImpl* pProfile) +{ + rtl_uString* ustrBakFile=nullptr; + rtl_uString* ustrTmpFile=nullptr; + rtl_uString* ustrIniFile=nullptr; + rtl_uString* ustrExtension=nullptr; + + rtl_uString_newFromAscii(&ustrExtension,"bak"); + + ustrBakFile=osl_ProfileGenerateExtension(pProfile->m_strFileName,ustrExtension); + rtl_uString_release(ustrExtension); + ustrExtension=nullptr; + + rtl_uString_newFromAscii(&ustrExtension,"ini"); + + ustrIniFile=osl_ProfileGenerateExtension(pProfile->m_strFileName,ustrExtension); + rtl_uString_release(ustrExtension); + ustrExtension=nullptr; + + rtl_uString_newFromAscii(&ustrExtension,"tmp"); + + ustrTmpFile=osl_ProfileGenerateExtension(pProfile->m_strFileName,ustrExtension); + rtl_uString_release(ustrExtension); + ustrExtension=nullptr; + + /* unlink bak */ + DeleteFileW( o3tl::toW(rtl_uString_getStr( ustrBakFile )) ); + + /* rename ini bak */ + MoveFileExW( o3tl::toW(rtl_uString_getStr( ustrIniFile )), o3tl::toW(rtl_uString_getStr( ustrBakFile )), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH ); + + /* rename tmp ini */ + MoveFileExW( o3tl::toW(rtl_uString_getStr( ustrTmpFile )), o3tl::toW(rtl_uString_getStr( ustrIniFile )), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH ); + + return false; +} + +static rtl_uString* osl_ProfileGenerateExtension(rtl_uString* ustrFileName, rtl_uString* ustrExtension) +{ + rtl_uString* ustrNewFileName = nullptr; + rtl_uString* ustrOldExtension = nullptr; + + sal_Unicode* pFileNameBuf = rtl_uString_getStr(ustrFileName); + + rtl_uString_newFromAscii(&ustrOldExtension, "."); + + sal_Unicode* pExtensionBuf = rtl_uString_getStr(ustrOldExtension); + + sal_Int32 nIndex = rtl_ustr_lastIndexOfChar(pFileNameBuf, *pExtensionBuf); + + rtl_uString_newReplaceStrAt(&ustrNewFileName, + ustrFileName, + nIndex+1, + 3, + ustrExtension); + + return ustrNewFileName; +} + +static osl_TProfileImpl* acquireProfile(oslProfile Profile, bool bWriteable) +{ + osl_TProfileImpl* pProfile = static_cast<osl_TProfileImpl*>(Profile); + oslProfileOption PFlags=0; + + if ( bWriteable ) + { + PFlags = osl_Profile_DEFAULT | osl_Profile_WRITELOCK; + } + else + { + PFlags = osl_Profile_DEFAULT; + } + + if (pProfile == nullptr) + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "AUTOOPEN MODE"); +#endif + + if ( ( pProfile = static_cast<osl_TProfileImpl*>(osl_openProfile( nullptr, PFlags )) ) != nullptr ) + { + pProfile->m_Flags |= FLG_AUTOOPEN; + } + } + else + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "try to acquire"); +#endif + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if (! (pProfile->m_Flags & (osl_Profile_READLOCK | + osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE))) + { + osl_TStamp Stamp; +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "DEFAULT MODE"); +#endif + pProfile->m_pFile = openFileImpl( + pProfile->m_strFileName, pProfile->m_Flags | PFlags); + if (!pProfile->m_pFile) + return nullptr; + + Stamp = getFileStamp(pProfile->m_pFile); + + if (memcmp(&Stamp, &(pProfile->m_Stamp), sizeof(osl_TStamp))) + { + pProfile->m_Stamp = Stamp; + + loadProfile(pProfile->m_pFile, pProfile); + } + } + else + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "READ/WRITELOCK MODE"); +#endif + + /* A readlock file could not be written */ + if ((pProfile->m_Flags & osl_Profile_READLOCK) && bWriteable) + { + return nullptr; + } + } + } + } + + return pProfile; +} + +static bool releaseProfile(osl_TProfileImpl* pProfile) +{ + if ( pProfile == nullptr ) + { + return false; + } + + if (! (pProfile->m_Flags & osl_Profile_SYSTEM)) + { + if (pProfile->m_Flags & FLG_AUTOOPEN) + { + return osl_closeProfile(static_cast<oslProfile>(pProfile)); + } + else + { +#ifdef DEBUG_OSL_PROFILE + SAL_INFO("sal.osl", "DEFAULT MODE"); +#endif + if (! (pProfile->m_Flags & (osl_Profile_READLOCK | + osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE))) + { + if (pProfile->m_Flags & FLG_MODIFIED) + storeProfile(pProfile, false); + + closeFileImpl(pProfile->m_pFile); + pProfile->m_pFile = nullptr; + } + } + } + + return true; +} + +static bool lookupProfile(const sal_Unicode *strPath, const sal_Unicode *strFile, sal_Unicode *strProfile) +{ + char *pChr; + char Buffer[4096] = ""; + char Product[132] = ""; + + ::osl::LongPathBuffer< sal_Unicode > aPath( MAX_LONG_PATH ); + aPath[0] = 0; + + if (*strPath == L'"') + { + int i = 0; + + strPath++; + + while ((strPath[i] != L'"') && (strPath[i] != L'\0')) + i++; + + WideCharToMultiByte(CP_ACP,0, o3tl::toW(strPath), i, Product, sizeof(Product), nullptr, nullptr); + Product[i] = '\0'; + strPath += i; + + if (*strPath == L'"') + strPath++; + + if ( (*strPath == L'/') || (*strPath == L'\\') ) + { + strPath++; + } + } + + else + { + /* if we have not product identification, do a special handling for soffice.ini */ + if (rtl_ustr_ascii_compare(strFile, SVERSION_PROFILE) == 0) + { + rtl_uString * strSVProfile = nullptr; + rtl_uString * strSVFallback = nullptr; + rtl_uString * strSVLocation = nullptr; + rtl_uString * strSVName = nullptr; + ::osl::LongPathBuffer< char > aDir( MAX_LONG_PATH ); + oslProfile hProfile; + + rtl_uString_newFromAscii(&strSVFallback, SVERSION_FALLBACK); + rtl_uString_newFromAscii(&strSVLocation, SVERSION_LOCATION); + rtl_uString_newFromAscii(&strSVName, SVERSION_NAME); + + /* open sversion.ini in the system directory, and try to locate the entry + with the highest version for StarOffice */ + if (osl_getProfileName( strSVFallback, strSVName, &strSVProfile)) + { + hProfile = osl_openProfile(strSVProfile, osl_Profile_READLOCK); + if (hProfile) + { + osl_getProfileSectionEntries( + hProfile, SVERSION_SECTION, Buffer, sizeof(Buffer)); + + for (pChr = Buffer; *pChr != '\0'; pChr += strlen(pChr) + 1) + { + if ((strnicmp( + pChr, SVERSION_SOFFICE, + sizeof(SVERSION_SOFFICE) - 1) + == 0) + && (stricmp(Product, pChr) < 0)) + { + osl_readProfileString( + hProfile, SVERSION_SECTION, pChr, aDir, + aDir.getBufSizeInSymbols(), ""); + + /* check for existence of path */ + if (access(aDir, 0) >= 0) + strcpy(Product, pChr); + } + } + + osl_closeProfile(hProfile); + } + rtl_uString_release(strSVProfile); + strSVProfile = nullptr; + } + + /* open sversion.ini in the users directory, and try to locate the entry + with the highest version for StarOffice */ + if ( osl_getProfileName(strSVLocation, strSVName, &strSVProfile) ) + { + hProfile = osl_openProfile(strSVProfile, osl_Profile_READLOCK); + if (hProfile) + { + osl_getProfileSectionEntries( + hProfile, SVERSION_SECTION, Buffer, sizeof(Buffer)); + + for (pChr = Buffer; *pChr != '\0'; pChr += strlen(pChr) + 1) + { + if ((strnicmp( + pChr, SVERSION_SOFFICE, + sizeof(SVERSION_SOFFICE) - 1) + == 0) + && (stricmp(Product, pChr) < 0)) + { + osl_readProfileString( + hProfile, SVERSION_SECTION, pChr, aDir, + aDir.getBufSizeInSymbols(), ""); + + /* check for existence of path */ + if (access(aDir, 0) >= 0) + strcpy(Product, pChr); + } + } + + osl_closeProfile(hProfile); + } + rtl_uString_release(strSVProfile); + } + + rtl_uString_release(strSVFallback); + rtl_uString_release(strSVLocation); + rtl_uString_release(strSVName); + + /* remove any trailing build number */ + if ((pChr = strrchr(Product, '/')) != nullptr) + *pChr = '\0'; + } + } + + rtl_uString * strExecutable = nullptr; + rtl_uString * strTmp = nullptr; + sal_Int32 nPos; + + /* try to find the file in the directory of the executable */ + if (osl_getExecutableFile(&strTmp) != osl_Process_E_None) + return false; + + /* convert to native path */ + if (osl_getSystemPathFromFileURL(strTmp, &strExecutable) != osl_File_E_None) + { + rtl_uString_release(strTmp); + return false; + } + + rtl_uString_release(strTmp); + + DWORD dwPathLen = 0; + + /* separate path from filename */ + if ((nPos = rtl_ustr_lastIndexOfChar(strExecutable->buffer, L'\\')) == -1) + { + if ((nPos = rtl_ustr_lastIndexOfChar(strExecutable->buffer, L':')) == -1) + { + rtl_uString_release(strExecutable); + return false; + } + else + { + copy_ustr_n(aPath, strExecutable->buffer, nPos); + aPath[nPos] = 0; + dwPathLen = nPos; + } + } + else + { + copy_ustr_n(aPath, strExecutable->buffer, nPos); + dwPathLen = nPos; + aPath[dwPathLen] = 0; + } + + /* if we have no product identification use the executable file name */ + if (*Product == 0) + { + WideCharToMultiByte(CP_ACP,0, o3tl::toW(strExecutable->buffer + nPos + 1), -1, Product, sizeof(Product), nullptr, nullptr); + + /* remove extension */ + if ((pChr = strrchr(Product, '.')) != nullptr) + *pChr = '\0'; + } + + rtl_uString_release(strExecutable); + + /* remember last subdir */ + nPos = rtl_ustr_lastIndexOfChar(aPath, L'\\'); + + copy_ustr_n(aPath + dwPathLen++, L"\\", 2); + + if (*strPath) + { + copy_ustr_n(aPath + dwPathLen, strPath, rtl_ustr_getLength(strPath)+1); + dwPathLen += rtl_ustr_getLength(strPath); + } + + { + ::osl::LongPathBuffer< char > aTmpPath( MAX_LONG_PATH ); + + WideCharToMultiByte(CP_ACP,0, o3tl::toW(aPath), -1, aTmpPath, aTmpPath.getBufSizeInSymbols(), nullptr, nullptr); + + /* if file not exists, remove any specified subdirectories + like "bin" or "program" */ + + if (((access(aTmpPath, 0) < 0) && (nPos != -1)) || (*strPath == 0)) + { + static const char *SubDirs[] = SVERSION_DIRS; + + unsigned i = 0; + char *pStr = aTmpPath + nPos; + + for (i = 0; i < SAL_N_ELEMENTS(SubDirs); i++) + if (strnicmp(pStr + 1, SubDirs[i], strlen(SubDirs[i])) == 0) + { + if ( *strPath == 0) + { + strcpy(pStr + 1,SVERSION_USER); + if ( access(aTmpPath, 0) < 0 ) + { + *(pStr+1)='\0'; + } + else + { + dwPathLen = nPos + MultiByteToWideChar( CP_ACP, 0, SVERSION_USER, -1, o3tl::toW(aPath + nPos + 1), aPath.getBufSizeInSymbols() - (nPos + 1) ); + } + } + else + { + copy_ustr_n(aPath + nPos + 1, strPath, rtl_ustr_getLength(strPath)+1); + dwPathLen = nPos + 1 + rtl_ustr_getLength(strPath); + } + + break; + } + } + } + + if ((aPath[dwPathLen - 1] != L'/') && (aPath[dwPathLen - 1] != L'\\')) + { + aPath[dwPathLen++] = L'\\'; + aPath[dwPathLen] = 0; + } + + copy_ustr_n(aPath + dwPathLen, strFile, rtl_ustr_getLength(strFile)+1); + + { + ::osl::LongPathBuffer< char > aTmpPath( MAX_LONG_PATH ); + + WideCharToMultiByte(CP_ACP,0, o3tl::toW(aPath), -1, aTmpPath, aTmpPath.getBufSizeInSymbols(), nullptr, nullptr); + + if ((access(aTmpPath, 0) < 0) && (Product[0] != '\0')) + { + rtl_uString * strSVFallback = nullptr; + rtl_uString * strSVProfile = nullptr; + rtl_uString * strSVLocation = nullptr; + rtl_uString * strSVName = nullptr; + oslProfile hProfile; + + rtl_uString_newFromAscii(&strSVFallback, SVERSION_FALLBACK); + rtl_uString_newFromAscii(&strSVLocation, SVERSION_LOCATION); + rtl_uString_newFromAscii(&strSVName, SVERSION_NAME); + + /* open sversion.ini in the system directory, and try to locate the entry + with the highest version for StarOffice */ + if (osl_getProfileName(strSVLocation, strSVName, &strSVProfile)) + { + hProfile = osl_openProfile( + strSVProfile, osl_Profile_READLOCK); + if (hProfile) + { + osl_readProfileString( + hProfile, SVERSION_SECTION, Product, Buffer, + sizeof(Buffer), ""); + osl_closeProfile(hProfile); + + /* if not found, try the fallback */ + if (Buffer[0] == '\0') + { + if (osl_getProfileName( + strSVFallback, strSVName, &strSVProfile)) + { + hProfile = osl_openProfile( + strSVProfile, osl_Profile_READLOCK); + if (hProfile) + { + osl_readProfileString( + hProfile, SVERSION_SECTION, Product, + Buffer, sizeof(Buffer), ""); + } + } + + osl_closeProfile(hProfile); + } + + if (Buffer[0] != '\0') + { + dwPathLen = MultiByteToWideChar( + CP_ACP, 0, Buffer, -1, o3tl::toW(aPath), aPath.getBufSizeInSymbols() ); + dwPathLen -=1; + + /* build full path */ + if ((aPath[dwPathLen - 1] != L'/') + && (aPath[dwPathLen - 1] != L'\\')) + { + copy_ustr_n(aPath + dwPathLen++, L"\\", 2); + } + + if (*strPath) + { + copy_ustr_n(aPath + dwPathLen, strPath, rtl_ustr_getLength(strPath)+1); + dwPathLen += rtl_ustr_getLength(strPath); + } + else + { + ::osl::LongPathBuffer< char > aTmpPath2( MAX_LONG_PATH ); + int n; + + if ((n = WideCharToMultiByte( + CP_ACP,0, o3tl::toW(aPath), -1, aTmpPath2, + aTmpPath2.getBufSizeInSymbols(), nullptr, nullptr)) + > 0) + { + strcpy(aTmpPath2 + n, SVERSION_USER); + if (access(aTmpPath2, 0) >= 0) + { + dwPathLen += MultiByteToWideChar( + CP_ACP, 0, SVERSION_USER, -1, + o3tl::toW(aPath + dwPathLen), + aPath.getBufSizeInSymbols() - dwPathLen ); + } + } + } + } + } + + rtl_uString_release(strSVProfile); + } + + rtl_uString_release(strSVFallback); + rtl_uString_release(strSVLocation); + rtl_uString_release(strSVName); + } + } + + aPath[dwPathLen] = 0; + + /* copy filename */ + copy_ustr_n(strProfile, aPath, dwPathLen+1); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/random.cxx b/sal/osl/w32/random.cxx new file mode 100644 index 000000000..a2c364da2 --- /dev/null +++ b/sal/osl/w32/random.cxx @@ -0,0 +1,63 @@ +/* -*- 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/. + */ + +#define _CRT_RAND_S + +#include <stdlib.h> +#include <memory.h> + +#include <oslrandom.h> + +int osl_get_system_random_data(char* buffer, size_t desired_len) +{ + unsigned int val; + + /* if unaligned fill to alignment */ + if (reinterpret_cast<uintptr_t>(buffer) & 3) + { + size_t len = 4 - (reinterpret_cast<size_t>(buffer) & 3); + + if (len > desired_len) + { + len = desired_len; + } + if (rand_s(&val)) + { + return 0; + } + memcpy(buffer, &val, len); + buffer += len; + desired_len -= len; + } + /* fill directly into the buffer as long as we can */ + while (desired_len >= 4) + { + if (rand_s(reinterpret_cast<unsigned int*>(buffer))) + { + return 0; + } + else + { + buffer += 4; + desired_len -= 4; + } + } + /* deal with the partial int reminder to fill */ + if (desired_len) + { + if (rand_s(&val)) + { + return 0; + } + memcpy(buffer, &val, desired_len); + } + return 1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/salinit.cxx b/sal/osl/w32/salinit.cxx new file mode 100644 index 000000000..c0b917127 --- /dev/null +++ b/sal/osl/w32/salinit.cxx @@ -0,0 +1,83 @@ +/* -*- 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 "system.h" +#include "time.hxx" + +#include <osl/process.h> +#include <sal/main.h> +#include <sal/types.h> + +extern "C" { + +// Prototypes for initialization and deinitialization of SAL library + +void sal_detail_initialize(int argc, char ** argv) +{ + if (argc == sal::detail::InitializeSoffice) + { + return; + } + sal_initGlobalTimer(); +#ifndef _WIN64 + SetProcessDEPPolicy(PROCESS_DEP_ENABLE); +#endif + SetDllDirectoryW(L""); // remove the current directory from the default DLL search order + SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT); + + WSADATA wsaData; + int error; + WORD wVersionRequested; + + wVersionRequested = MAKEWORD(1, 1); + + error = WSAStartup(wVersionRequested, &wsaData); + if ( 0 == error ) + { + WORD const wMajorVersionRequired = 1; + WORD const wMinorVersionRequired = 1; + + if ((LOBYTE(wsaData.wVersion) < wMajorVersionRequired) || + ((LOBYTE(wsaData.wVersion) == wMajorVersionRequired) && + (HIBYTE(wsaData.wVersion) < wMinorVersionRequired))) + { + // How to handle a very unlikely error ??? + } + } + else + { + // How to handle a very unlikely error ??? + } + + osl_setCommandArgs(argc, argv); +} + +void sal_detail_deinitialize() +{ + if ( SOCKET_ERROR == WSACleanup() ) + { + // We should never reach this point or we did wrong elsewhere + } +} + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/secimpl.hxx b/sal/osl/w32/secimpl.hxx new file mode 100644 index 000000000..7c952bb64 --- /dev/null +++ b/sal/osl/w32/secimpl.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SAL_OSL_W32_SECIMPL_HXX +#define INCLUDED_SAL_OSL_W32_SECIMPL_HXX + +#include <winnetwk.h> + +#include <osl/security.h> + +#define USER_BUFFER_SIZE 256 + +typedef struct +{ + HANDLE m_hProfile; + HANDLE m_hToken; + sal_Unicode m_User[USER_BUFFER_SIZE]; + /* extension by fileserver login */ + NETRESOURCEW* m_pNetResource; +} oslSecurityImpl; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/security.cxx b/sal/osl/w32/security.cxx new file mode 100644 index 000000000..21ed64a78 --- /dev/null +++ b/sal/osl/w32/security.cxx @@ -0,0 +1,662 @@ +/* -*- 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 "system.h" +#include <userenv.h> + +#include <cassert> +#include <osl/security.h> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/file.h> +#include <systools/win32/uwinapi.h> +#include <sddl.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include "secimpl.hxx" + +/* To get an impersonation token we need to create an impersonation + duplicate so every access token has to be created with duplicate + access rights */ + +#define TOKEN_DUP_QUERY (TOKEN_QUERY|TOKEN_DUPLICATE) + +static bool GetSpecialFolder(rtl_uString **strPath, REFKNOWNFOLDERID rFolder); +// We use LPCTSTR here, because we use it with SE_foo_NAME constants +// which are defined in winnt.h as UNICODE-dependent TEXT("PrivilegeName") +static bool Privilege(LPCTSTR pszPrivilege, bool bEnable); +static bool getUserNameImpl(oslSecurity Security, rtl_uString **strName, bool bIncludeDomain); + +oslSecurity SAL_CALL osl_getCurrentSecurity(void) +{ + oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl))); + if (pSecImpl) + { + pSecImpl->m_pNetResource = nullptr; + pSecImpl->m_User[0] = '\0'; + pSecImpl->m_hToken = nullptr; + pSecImpl->m_hProfile = nullptr; + } + return pSecImpl; +} + +oslSecurityError SAL_CALL osl_loginUser( rtl_uString *strUserName, rtl_uString *strPasswd, oslSecurity *pSecurity ) +{ + oslSecurityError ret; + + sal_Unicode* strUser; + sal_Unicode* strDomain = o3tl::toU(_wcsdup(o3tl::toW(rtl_uString_getStr(strUserName)))); + HANDLE hUserToken; + LUID luid; + + if (nullptr != (strUser = o3tl::toU(wcschr(o3tl::toW(strDomain), L'/')))) + *strUser++ = L'\0'; + else + { + strUser = strDomain; + strDomain = nullptr; + } + + // this process must have the right: 'act as a part of operatingsystem' + OSL_ASSERT(LookupPrivilegeValue(nullptr, SE_TCB_NAME, &luid)); + (void) luid; + + if (LogonUserW(o3tl::toW(strUser), strDomain ? o3tl::toW(strDomain) : L"", o3tl::toW(rtl_uString_getStr(strPasswd)), + LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, + &hUserToken)) + { + oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl))); + if (pSecImpl) + { + pSecImpl->m_pNetResource = nullptr; + pSecImpl->m_hToken = hUserToken; + pSecImpl->m_hProfile = nullptr; + wcscpy(o3tl::toW(pSecImpl->m_User), o3tl::toW(strUser)); + } + *pSecurity = pSecImpl; + ret = pSecImpl ? osl_Security_E_None : osl_Security_E_Unknown; + } + else + { + ret = osl_Security_E_UserUnknown; + } + + if (strDomain) + free(strDomain); + else + free(strUser); + + return ret; +} + +oslSecurityError SAL_CALL osl_loginUserOnFileServer(rtl_uString *strUserName, + rtl_uString *strPasswd, + rtl_uString *strFileServer, + oslSecurity *pSecurity) +{ + oslSecurityError ret; + DWORD err; + NETRESOURCEW netResource; + sal_Unicode* remoteName; + sal_Unicode* userName; + + remoteName = static_cast<sal_Unicode *>(malloc((rtl_uString_getLength(strFileServer) + rtl_uString_getLength(strUserName) + 4) * sizeof(sal_Unicode))); + userName = static_cast<sal_Unicode *>(malloc((rtl_uString_getLength(strFileServer) + rtl_uString_getLength(strUserName) + 2) * sizeof(sal_Unicode))); + + wcscpy(o3tl::toW(remoteName), L"\\\\"); + wcscat(o3tl::toW(remoteName), o3tl::toW(rtl_uString_getStr(strFileServer))); + wcscat(o3tl::toW(remoteName), L"\\"); + wcscat(o3tl::toW(remoteName), o3tl::toW(rtl_uString_getStr(strUserName))); + + wcscpy(o3tl::toW(userName), o3tl::toW(rtl_uString_getStr(strFileServer))); + wcscat(o3tl::toW(userName), L"\\"); + wcscat(o3tl::toW(userName), o3tl::toW(rtl_uString_getStr(strUserName))); + + netResource.dwScope = RESOURCE_GLOBALNET; + netResource.dwType = RESOURCETYPE_DISK; + netResource.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE; + netResource.dwUsage = RESOURCEUSAGE_CONNECTABLE; + netResource.lpLocalName = nullptr; + netResource.lpRemoteName = o3tl::toW(remoteName); + netResource.lpComment = nullptr; + netResource.lpProvider = nullptr; + + err = WNetAddConnection2W(&netResource, o3tl::toW(rtl_uString_getStr(strPasswd)), o3tl::toW(userName), 0); + + if ((err == NO_ERROR) || (err == ERROR_ALREADY_ASSIGNED)) + { + oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl))); + if (pSecImpl) + { + pSecImpl->m_pNetResource = static_cast<NETRESOURCEW *>(malloc(sizeof(NETRESOURCE))); + if (pSecImpl->m_pNetResource) + { + *pSecImpl->m_pNetResource = netResource; + pSecImpl->m_hToken = nullptr; + pSecImpl->m_hProfile = nullptr; + wcscpy(o3tl::toW(pSecImpl->m_User), o3tl::toW(rtl_uString_getStr(strUserName))); + } + else + { + free(pSecImpl); + pSecImpl = nullptr; + } + } + *pSecurity = pSecImpl; + + ret = pSecImpl ? osl_Security_E_None : osl_Security_E_Unknown; + } + else + { + ret = osl_Security_E_UserUnknown; + } + + free(remoteName); + free(userName); + + return ret; +} + +sal_Bool SAL_CALL osl_isAdministrator(oslSecurity Security) +{ + if (!Security) + return false; + + HANDLE hImpersonationToken = nullptr; + PSID psidAdministrators; + SID_IDENTIFIER_AUTHORITY siaNtAuthority = { SECURITY_NT_AUTHORITY }; + bool bSuccess = false; + + /* If Security contains an access token we need to duplicate it to an impersonation + access token. NULL works with CheckTokenMembership() as the current effective + impersonation token + */ + + if ( static_cast<oslSecurityImpl*>(Security)->m_hToken ) + { + if ( !DuplicateToken (static_cast<oslSecurityImpl*>(Security)->m_hToken, SecurityImpersonation, &hImpersonationToken) ) + return false; + } + + /* CheckTokenMembership() can be used on W2K and higher (NT4 no longer supported by OOo) + and also works on Vista to retrieve the effective user rights. Just checking for + membership of Administrators group is not enough on Vista this would require additional + complicated checks as described in KB article Q118626: http://support.microsoft.com/kb/118626/en-us + */ + + if (AllocateAndInitializeSid(&siaNtAuthority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &psidAdministrators)) + { + BOOL fSuccess = FALSE; + + if (CheckTokenMembership(hImpersonationToken, psidAdministrators, &fSuccess) && fSuccess) + bSuccess = true; + + FreeSid(psidAdministrators); + } + + if (hImpersonationToken) + CloseHandle(hImpersonationToken); + + return bSuccess; +} + +void SAL_CALL osl_freeSecurityHandle(oslSecurity Security) +{ + if (!Security) + return; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security); + + if (pSecImpl->m_pNetResource != nullptr) + { + WNetCancelConnection2W(pSecImpl->m_pNetResource->lpRemoteName, 0, true); + + free(pSecImpl->m_pNetResource->lpRemoteName); + free(pSecImpl->m_pNetResource); + } + + if (pSecImpl->m_hToken) + CloseHandle(pSecImpl->m_hToken); + + if ( pSecImpl->m_hProfile ) + CloseHandle(pSecImpl->m_hProfile); + + free (pSecImpl); +} + +sal_Bool SAL_CALL osl_getUserIdent(oslSecurity Security, rtl_uString **strIdent) +{ + if (!Security) + return false; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security); + + HANDLE hAccessToken = pSecImpl->m_hToken; + + if (hAccessToken == nullptr) + OpenProcessToken(GetCurrentProcess(), TOKEN_DUP_QUERY, &hAccessToken); + + if (hAccessToken) + { + DWORD nInfoBuffer = 512; + UCHAR* pInfoBuffer = static_cast<UCHAR *>(malloc(nInfoBuffer)); + + while (!GetTokenInformation(hAccessToken, TokenUser, + pInfoBuffer, nInfoBuffer, &nInfoBuffer)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + if (auto p = static_cast<UCHAR *>(realloc(pInfoBuffer, nInfoBuffer))) + pInfoBuffer = p; + else + { + free(pInfoBuffer); + pInfoBuffer = nullptr; + break; + } + } + else + { + free(pInfoBuffer); + pInfoBuffer = nullptr; + break; + } + } + + if (pSecImpl->m_hToken == nullptr) + CloseHandle(hAccessToken); + + if (pInfoBuffer) + { + PSID pSid = reinterpret_cast<PTOKEN_USER>(pInfoBuffer)->User.Sid; + + LPWSTR pSidStr = nullptr; + bool bResult = ConvertSidToStringSidW(pSid, &pSidStr); + if (bResult) + { + rtl_uString_newFromStr(strIdent, o3tl::toU(pSidStr)); + LocalFree(pSidStr); + } + else + { + const DWORD dwError = GetLastError(); + SAL_WARN( + "sal.osl", + "ConvertSidToStringSidW failed. GetLastError returned: " << dwError); + } + + free(pInfoBuffer); + + return bResult; + } + } + else + { + DWORD needed = 0; + + WNetGetUserW(nullptr, nullptr, &needed); + if (needed < 16) + needed = 16; + + if (auto Ident = static_cast<sal_Unicode *>(malloc(needed*sizeof(sal_Unicode)))) + { + if (WNetGetUserW(nullptr, o3tl::toW(Ident), &needed) != NO_ERROR) + { + wcscpy(o3tl::toW(Ident), L"unknown"); + Ident[7] = L'\0'; + } + + rtl_uString_newFromStr( strIdent, Ident); + free(Ident); + return true; + } + } + return false; +} + +sal_Bool SAL_CALL osl_getUserName(oslSecurity Security, rtl_uString **strName) +{ + return getUserNameImpl(Security, strName, true); +} + +sal_Bool SAL_CALL osl_getShortUserName(oslSecurity Security, rtl_uString **strName) +{ + return getUserNameImpl(Security, strName, false); +} + +sal_Bool SAL_CALL osl_getHomeDir(oslSecurity Security, rtl_uString **pustrDirectory) +{ + if (!Security) + return false; + + rtl_uString *ustrSysDir = nullptr; + bool bSuccess = false; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security); + + if (pSecImpl->m_pNetResource != nullptr) + { + rtl_uString_newFromStr( &ustrSysDir, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName)); + + bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath( ustrSysDir, pustrDirectory ); + } + else + { + bSuccess = GetSpecialFolder(&ustrSysDir, FOLDERID_Documents) && + (osl_File_E_None == osl_getFileURLFromSystemPath(ustrSysDir, pustrDirectory)); + } + + if ( ustrSysDir ) + rtl_uString_release( ustrSysDir ); + + return bSuccess; +} + +sal_Bool SAL_CALL osl_getConfigDir(oslSecurity Security, rtl_uString **pustrDirectory) +{ + if (!Security) + return false; + + bool bSuccess = false; + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security); + + if (pSecImpl->m_pNetResource != nullptr) + { + rtl_uString *ustrSysDir = nullptr; + + rtl_uString_newFromStr( &ustrSysDir, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName)); + bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath( ustrSysDir, pustrDirectory); + + if ( ustrSysDir ) + rtl_uString_release( ustrSysDir ); + } + else + { + if (pSecImpl->m_hToken) + { + /* not implemented */ + OSL_ASSERT(false); + } + else + { + rtl_uString *ustrFile = nullptr; + sal_Unicode sFile[_MAX_PATH]; + + if ( !GetSpecialFolder( &ustrFile, FOLDERID_RoamingAppData) ) + { + OSL_VERIFY(GetWindowsDirectoryW(o3tl::toW(sFile), _MAX_DIR) > 0); + + rtl_uString_newFromStr( &ustrFile, sFile); + } + + bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath(ustrFile, pustrDirectory); + + if ( ustrFile ) + rtl_uString_release( ustrFile ); + } + } + + return bSuccess; +} + +sal_Bool SAL_CALL osl_loadUserProfile(oslSecurity Security) +{ + /* CreateProcessAsUser does not load the specified user's profile + into the HKEY_USERS registry key. This means that access to information + in the HKEY_CURRENT_USER registry key may not produce results consistent + with a normal interactive logon. + It is your responsibility to load the user's registry hive into HKEY_USERS + with the LoadUserProfile function before calling CreateProcessAsUser. + */ + + RegCloseKey(HKEY_CURRENT_USER); + + if (!Privilege(SE_RESTORE_NAME, true)) + return false; + + bool bOk = false; + HANDLE hAccessToken = static_cast<oslSecurityImpl*>(Security)->m_hToken; + + /* try to create user profile */ + if ( !hAccessToken ) + { + /* retrieve security handle if not done before e.g. osl_getCurrentSecurity() + */ + HANDLE hProcess = GetCurrentProcess(); + + if (hProcess != nullptr) + { + OpenProcessToken(hProcess, TOKEN_IMPERSONATE, &hAccessToken); + CloseHandle(hProcess); + } + } + + rtl_uString *buffer = nullptr; + PROFILEINFOW pi; + + getUserNameImpl(Security, &buffer, false); + + ZeroMemory(&pi, sizeof(pi)); + pi.dwSize = sizeof(pi); + pi.lpUserName = o3tl::toW(rtl_uString_getStr(buffer)); + pi.dwFlags = PI_NOUI; + + if (LoadUserProfileW(hAccessToken, &pi)) + { + UnloadUserProfile(hAccessToken, pi.hProfile); + + bOk = true; + } + + rtl_uString_release(buffer); + + if (hAccessToken && (hAccessToken != static_cast<oslSecurityImpl*>(Security)->m_hToken)) + CloseHandle(hAccessToken); + + return bOk; +} + +void SAL_CALL osl_unloadUserProfile(oslSecurity Security) +{ + if ( static_cast<oslSecurityImpl*>(Security)->m_hProfile == nullptr ) + return; + + HANDLE hAccessToken = static_cast<oslSecurityImpl*>(Security)->m_hToken; + + if ( !hAccessToken ) + { + /* retrieve security handle if not done before e.g. osl_getCurrentSecurity() + */ + HANDLE hProcess = GetCurrentProcess(); + + if (hProcess != nullptr) + { + OpenProcessToken(hProcess, TOKEN_IMPERSONATE, &hAccessToken); + CloseHandle(hProcess); + } + } + + /* unloading the user profile */ + UnloadUserProfile(hAccessToken, static_cast<oslSecurityImpl*>(Security)->m_hProfile); + + static_cast<oslSecurityImpl*>(Security)->m_hProfile = nullptr; + + if (hAccessToken && (hAccessToken != static_cast<oslSecurityImpl*>(Security)->m_hToken)) + CloseHandle(hAccessToken); +} + +static bool GetSpecialFolder(rtl_uString **strPath, REFKNOWNFOLDERID rFolder) +{ + bool bRet = false; + PWSTR PathW; + if (SUCCEEDED(SHGetKnownFolderPath(rFolder, KF_FLAG_CREATE, nullptr, &PathW))) + { + rtl_uString_newFromStr(strPath, o3tl::toU(PathW)); + CoTaskMemFree(PathW); + bRet = true; + } + + return bRet; +} + +// We use LPCTSTR here, because we use it with SE_foo_NAME constants +// which are defined in winnt.h as UNICODE-dependent TEXT("PrivilegeName") +static bool Privilege(LPCTSTR strPrivilege, bool bEnable) +{ + HANDLE hToken; + TOKEN_PRIVILEGES tp; + + // obtain the processes token + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_DUP_QUERY, &hToken)) + return false; + + // get the luid + if (!LookupPrivilegeValue(nullptr, strPrivilege, &tp.Privileges[0].Luid)) + return false; + + tp.PrivilegeCount = 1; + + if (bEnable) + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + else + tp.Privileges[0].Attributes = 0; + + // enable or disable the privilege + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, nullptr, nullptr)) + return false; + + if (!CloseHandle(hToken)) + return false; + + return true; +} + +static bool getUserNameImpl(oslSecurity Security, rtl_uString **strName, bool bIncludeDomain) +{ + if (!Security) + return false; + + oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security); + + HANDLE hAccessToken = pSecImpl->m_hToken; + + if (hAccessToken == nullptr) + OpenProcessToken(GetCurrentProcess(), TOKEN_DUP_QUERY, &hAccessToken); + + if (hAccessToken) + { + DWORD nInfoBuffer = 512; + UCHAR* pInfoBuffer = static_cast<UCHAR *>(malloc(nInfoBuffer)); + + while (!GetTokenInformation(hAccessToken, TokenUser, + pInfoBuffer, nInfoBuffer, &nInfoBuffer)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + if (auto p = static_cast<UCHAR *>(realloc(pInfoBuffer, nInfoBuffer))) + pInfoBuffer = p; + else + { + free(pInfoBuffer); + pInfoBuffer = nullptr; + break; + } + } + else + { + free(pInfoBuffer); + pInfoBuffer = nullptr; + break; + } + } + + if (pSecImpl->m_hToken == nullptr) + CloseHandle(hAccessToken); + + if (pInfoBuffer) + { + sal_Unicode UserName[128]; + sal_Unicode DomainName[128]; + sal_Unicode Name[257]; + DWORD nUserName = SAL_N_ELEMENTS(UserName); + DWORD nDomainName = SAL_N_ELEMENTS(DomainName); + SID_NAME_USE sUse; + + if (LookupAccountSidW(nullptr, reinterpret_cast<PTOKEN_USER>(pInfoBuffer)->User.Sid, + o3tl::toW(UserName), &nUserName, + o3tl::toW(DomainName), &nDomainName, &sUse)) + { + if (bIncludeDomain) + { + wcscpy(o3tl::toW(Name), o3tl::toW(DomainName)); + wcscat(o3tl::toW(Name), L"/"); + wcscat(o3tl::toW(Name), o3tl::toW(UserName)); + } + else + { + wcscpy(o3tl::toW(Name), o3tl::toW(UserName)); + } + + rtl_uString_newFromStr(strName, Name); + free(pInfoBuffer); + return true; + } + } + } + else + { + DWORD needed=0; + sal_Unicode *pNameW=nullptr; + + WNetGetUserW(nullptr, nullptr, &needed); + pNameW = static_cast<sal_Unicode *>(malloc (needed*sizeof(sal_Unicode))); + assert(pNameW); // Don't handle OOM conditions + + if (WNetGetUserW(nullptr, o3tl::toW(pNameW), &needed) == NO_ERROR) + { + rtl_uString_newFromStr( strName, pNameW); + + if (pNameW) + free(pNameW); + return true; + } + else if (pSecImpl->m_User[0] != '\0') + { + rtl_uString_newFromStr(strName, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName)); + + if (pNameW) + free(pNameW); + + return true; + } + + if (pNameW) + free(pNameW); + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/signal.cxx b/sal/osl/w32/signal.cxx new file mode 100644 index 000000000..dcb05beae --- /dev/null +++ b/sal/osl/w32/signal.cxx @@ -0,0 +1,133 @@ +/* -*- 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 <stdlib.h> + +#include <config_features.h> + +#include <signalshared.hxx> + +#include <systools/win32/uwinapi.h> +#include <errorrep.h> +#include <werapi.h> + +namespace +{ +long WINAPI signalHandlerFunction(LPEXCEPTION_POINTERS lpEP); + +LPTOP_LEVEL_EXCEPTION_FILTER pPreviousHandler = nullptr; +} + +bool onInitSignal() +{ + pPreviousHandler = SetUnhandledExceptionFilter(signalHandlerFunction); + + WerAddExcludedApplication(L"SOFFICE.EXE", FALSE); + + return true; +} + +bool onDeInitSignal() +{ + SetUnhandledExceptionFilter(pPreviousHandler); + + return false; +} + +namespace +{ +/* magic Microsoft C++ compiler exception constant */ +#define EXCEPTION_MSC_CPP_EXCEPTION 0xe06d7363 + +long WINAPI signalHandlerFunction(LPEXCEPTION_POINTERS lpEP) +{ +#if HAVE_FEATURE_BREAKPAD + // we should make sure to call the breakpad handler as + // first step when we hit a problem + if (pPreviousHandler) + pPreviousHandler(lpEP); +#endif + + static bool bNested = false; + + oslSignalInfo info; + + info.UserSignal = lpEP->ExceptionRecord->ExceptionCode; + info.UserData = nullptr; + + switch (lpEP->ExceptionRecord->ExceptionCode) + { + /* Transform unhandled exceptions into access violations. + Microsoft C++ compiler (add more for other compilers if necessary). + */ + case EXCEPTION_MSC_CPP_EXCEPTION: + case EXCEPTION_ACCESS_VIOLATION: + info.Signal = osl_Signal_AccessViolation; + break; + + case EXCEPTION_INT_DIVIDE_BY_ZERO: + info.Signal = osl_Signal_IntegerDivideByZero; + break; + + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + info.Signal = osl_Signal_FloatDivideByZero; + break; + + case EXCEPTION_BREAKPOINT: + info.Signal = osl_Signal_DebugBreak; + break; + + default: + info.Signal = osl_Signal_System; + break; + } + + oslSignalAction action; + + if (!bNested) + { + bNested = true; + action = callSignalHandler(&info); + } + else + action = osl_Signal_ActKillApp; + + switch (action) + { + case osl_Signal_ActCallNextHdl: + return EXCEPTION_CONTINUE_SEARCH; + + case osl_Signal_ActAbortApp: + return EXCEPTION_EXECUTE_HANDLER; + + case osl_Signal_ActKillApp: + SetErrorMode(SEM_NOGPFAULTERRORBOX); + exit(255); + break; + default: + break; + } + + return EXCEPTION_CONTINUE_EXECUTION; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/socket.cxx b/sal/osl/w32/socket.cxx new file mode 100644 index 000000000..2548be0d5 --- /dev/null +++ b/sal/osl/w32/socket.cxx @@ -0,0 +1,1611 @@ +/* -*- 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 <utility> + +#include "system.h" + +#include <osl/socket.h> +#include <osl/thread.h> +#include <osl/diagnose.h> +#include <rtl/alloc.h> +#include <rtl/byteseq.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/safeint.hxx> +#include <comphelper/windowserrorstring.hxx> + +#include "sockimpl.hxx" + +/* + oslSocketAddr is a pointer to a Berkeley struct sockaddr. + I refrained from using sockaddr_in because of possible further + extensions of this socket-interface (IP-NG?). + The intention was to hide all Berkeley data-structures from + direct access past the osl-interface. + + The current implementation is internet (IP) centered. All + the constructor-functions (osl_create...) take parameters + that will probably make sense only in the IP-environment + (e.g. because of using the dotted-Addr-format). + + If the interface will be extended to host other protocol- + families, I expect no externally visible changes in the + existing functions. You'll probably need only new + constructor-functions who take the different Addr + formats into consideration (maybe a long dotted Addr + or whatever). +*/ + +/* + _Note_ that I rely on the fact that oslSocketAddr and struct sockaddr + are the same! I don't like it very much but see no other easy way to + conceal the struct sockaddr from the eyes of the user. +*/ + +#define OSL_INVALID_SOCKET INVALID_SOCKET /* WIN32 */ +#define OSL_SOCKET_ERROR SOCKET_ERROR /* WIN32 */ + +static DWORD FamilyMap[]= { + AF_INET, /* osl_Socket_FamilyInet */ + AF_IPX, /* osl_Socket_FamilyIpx */ + 0 /* osl_Socket_FamilyInvalid */ +}; + +static oslAddrFamily osl_AddrFamilyFromNative(DWORD nativeType) +{ + oslAddrFamily i= oslAddrFamily(0); + while(i != osl_Socket_FamilyInvalid) + { + if(FamilyMap[i] == nativeType) + return i; + i = static_cast<oslAddrFamily>( static_cast<int>(i) + 1); + } + return i; +} + +#define FAMILY_FROM_NATIVE(y) osl_AddrFamilyFromNative(y) +#define FAMILY_TO_NATIVE(x) static_cast<short>(FamilyMap[x]) + +static DWORD ProtocolMap[]= { + 0, /* osl_Socket_FamilyInet */ + NSPROTO_IPX, /* osl_Socket_FamilyIpx */ + NSPROTO_SPX, /* osl_Socket_ProtocolSpx */ + NSPROTO_SPXII, /* osl_Socket_ProtocolSpx_ii */ + 0 /* osl_Socket_ProtocolInvalid */ +}; + +#define PROTOCOL_TO_NATIVE(x) ProtocolMap[x] + +static DWORD TypeMap[]= { + SOCK_STREAM, /* osl_Socket_TypeStream */ + SOCK_DGRAM, /* osl_Socket_TypeDgram */ + SOCK_RAW, /* osl_Socket_TypeRaw */ + SOCK_RDM, /* osl_Socket_TypeRdm */ + SOCK_SEQPACKET, /* osl_Socket_TypeSeqPacket */ + 0 /* osl_Socket_TypeInvalid */ +}; + +static oslSocketType osl_SocketTypeFromNative(DWORD nativeType) +{ + oslSocketType i= oslSocketType(0); + while(i != osl_Socket_TypeInvalid) + { + if(TypeMap[i] == nativeType) + return i; + i = static_cast<oslSocketType>(static_cast<int>(i)+1); + } + return i; +} + +#define TYPE_TO_NATIVE(x) TypeMap[x] +#define TYPE_FROM_NATIVE(y) osl_SocketTypeFromNative(y) + +static DWORD OptionMap[]= { + SO_DEBUG, /* osl_Socket_OptionDebug */ + SO_ACCEPTCONN, /* osl_Socket_OptionAcceptConn */ + SO_REUSEADDR, /* osl_Socket_OptionReuseAddr */ + SO_KEEPALIVE, /* osl_Socket_OptionKeepAlive */ + SO_DONTROUTE, /* osl_Socket_OptionDontRoute */ + SO_BROADCAST, /* osl_Socket_OptionBroadcast */ + SO_USELOOPBACK, /* osl_Socket_OptionUseLoopback */ + SO_LINGER, /* osl_Socket_OptionLinger */ + SO_OOBINLINE, /* osl_Socket_OptionOOBinLine */ + SO_SNDBUF, /* osl_Socket_OptionSndBuf */ + SO_RCVBUF, /* osl_Socket_OptionRcvBuf */ + SO_SNDLOWAT, /* osl_Socket_OptionSndLowat */ + SO_RCVLOWAT, /* osl_Socket_OptionRcvLowat */ + SO_SNDTIMEO, /* osl_Socket_OptionSndTimeo */ + SO_RCVTIMEO, /* osl_Socket_OptionRcvTimeo */ + SO_ERROR, /* osl_Socket_OptionError */ + SO_TYPE, /* osl_Socket_OptionType */ + TCP_NODELAY, /* osl_Socket_OptionTcpNoDelay */ + 0 /* osl_Socket_OptionInvalid */ +}; + +#define OPTION_TO_NATIVE(x) OptionMap[x] + +static DWORD OptionLevelMap[]= { + SOL_SOCKET, /* osl_Socket_LevelSocket */ + IPPROTO_TCP, /* osl_Socket_LevelTcp */ + 0 /* osl_invalid_SocketLevel */ +}; + +#define OPTION_LEVEL_TO_NATIVE(x) OptionLevelMap[x] + +static DWORD SocketMsgFlagMap[]= { + 0, /* osl_Socket_MsgNormal */ + MSG_OOB, /* osl_Socket_MsgOOB */ + MSG_PEEK, /* osl_Socket_MsgPeek */ + MSG_DONTROUTE, /* osl_Socket_MsgDontRoute */ + MSG_MAXIOVLEN /* osl_Socket_MsgMaxIOVLen */ +}; + +#define MSG_FLAG_TO_NATIVE(x) SocketMsgFlagMap[x] + +static DWORD SocketDirection[]= { + SD_RECEIVE, /* osl_Socket_DirRead */ + SD_SEND, /* osl_Socket_DirWrite */ + SD_BOTH /* osl_Socket_DirReadwrite */ +}; + +#define DIRECTION_TO_NATIVE(x) SocketDirection[x] + +static int SocketError[]= { + 0, /* no error */ + WSAENOTSOCK, /* Socket operation on non-socket */ + WSAEDESTADDRREQ, /* Destination address required */ + WSAEMSGSIZE, /* Message too long */ + WSAEPROTOTYPE, /* Protocol wrong type for socket */ + WSAENOPROTOOPT, /* Protocol not available */ + WSAEPROTONOSUPPORT, /* Protocol not supported */ + WSAESOCKTNOSUPPORT, /* Socket type not supported */ + WSAEOPNOTSUPP, /* Operation not supported on socket */ + WSAEPFNOSUPPORT, /* Protocol family not supported */ + WSAEAFNOSUPPORT, /* Address family not supported by protocol family */ + WSAEADDRINUSE, /* Address already in use */ + WSAEADDRNOTAVAIL, /* Can't assign requested address */ + WSAENETDOWN, /* Network is down */ + WSAENETUNREACH, /* Network is unreachable */ + WSAENETRESET, /* Network dropped connection because of reset */ + WSAECONNABORTED, /* Software caused connection abort */ + WSAECONNRESET, /* Connection reset by peer */ + WSAENOBUFS, /* No buffer space available */ + WSAEISCONN, /* Socket is already connected */ + WSAENOTCONN, /* Socket is not connected */ + WSAESHUTDOWN, /* Can't send after socket shutdown */ + WSAETOOMANYREFS, /* Too many references: can't splice */ + WSAETIMEDOUT, /* Connection timed out */ + WSAECONNREFUSED, /* Connection refused */ + WSAEHOSTDOWN, /* Host is down */ + WSAEHOSTUNREACH, /* No route to host */ + WSAEWOULDBLOCK, /* call would block on non-blocking socket */ + WSAEALREADY, /* operation already in progress */ + WSAEINPROGRESS /* operation now in progress */ +}; + +static oslSocketError osl_SocketErrorFromNative(int nativeType) +{ + oslSocketError i= oslSocketError(0); + + while(i != osl_Socket_E_InvalidError) + { + if(SocketError[i] == nativeType) + return i; + + i = static_cast<oslSocketError>( static_cast<int>(i) + 1); + } + return i; +} + +#define ERROR_FROM_NATIVE(y) osl_SocketErrorFromNative(y) + +#if OSL_DEBUG_LEVEL > 0 +static sal_uInt32 g_nSocketAddr = 0; + +namespace { + +struct LeakWarning +{ + ~LeakWarning() + { + SAL_WARN_IF( g_nSocketAddr, "sal.osl", "sal_socket: " << g_nSocketAddr << " socket address instances leak" ); + } +}; + +} + +static LeakWarning socketWarning; +#endif + +static oslSocket createSocketImpl(SOCKET Socket) +{ + oslSocket pSockImpl = static_cast<oslSocket>(rtl_allocateZeroMemory( sizeof(struct oslSocketImpl))); + pSockImpl->m_Socket = Socket; + pSockImpl->m_nRefCount = 1; + return pSockImpl; +} + +static void destroySocketImpl(oslSocketImpl *pImpl) +{ + if (pImpl) + { + free (pImpl); + } +} + +static oslSocketAddr createSocketAddr( ) +{ + oslSocketAddr pAddr = static_cast<oslSocketAddr>(rtl_allocateZeroMemory( sizeof( struct oslSocketAddrImpl ))); + pAddr->m_nRefCount = 1; +#if OSL_DEBUG_LEVEL > 0 + g_nSocketAddr ++; +#endif + return pAddr; +} + +static oslSocketAddr createSocketAddrWithFamily( + oslAddrFamily family, sal_Int32 port, sal_uInt32 nAddr ) +{ + OSL_ASSERT( family == osl_Socket_FamilyInet ); + + oslSocketAddr pAddr = createSocketAddr(); + switch( family ) + { + case osl_Socket_FamilyInet: + { + struct sockaddr_in* pInetAddr= reinterpret_cast<struct sockaddr_in*>(&pAddr->m_sockaddr); + + pInetAddr->sin_family = FAMILY_TO_NATIVE(osl_Socket_FamilyInet); + pInetAddr->sin_addr.s_addr = nAddr; + pInetAddr->sin_port = static_cast<sal_uInt16>(port&0xffff); + break; + } + default: + pAddr->m_sockaddr.sa_family = FAMILY_TO_NATIVE(family); + } + return pAddr; +} + +static oslSocketAddr createSocketAddFromSystem( struct sockaddr *pSystemSockAddr ) +{ + oslSocketAddr pAddr = createSocketAddr(); + memcpy( &(pAddr->m_sockaddr), pSystemSockAddr, sizeof( sockaddr ) ); + return pAddr; +} + +static void destroySocketAddr( oslSocketAddr addr ) +{ +#if OSL_DEBUG_LEVEL > 0 + g_nSocketAddr --; +#endif + free( addr ); +} + +oslSocketAddr SAL_CALL osl_createEmptySocketAddr(oslAddrFamily Family) +{ + oslSocketAddr pAddr = nullptr; + + /* is it an internet-Addr? */ + if (Family == osl_Socket_FamilyInet) + pAddr = createSocketAddrWithFamily(Family, 0 , htonl(INADDR_ANY) ); + else + pAddr = createSocketAddrWithFamily( Family , 0 , 0 ); + + return pAddr; +} + +/** @deprecated, to be removed */ +oslSocketAddr SAL_CALL osl_copySocketAddr(oslSocketAddr Addr) +{ + oslSocketAddr pCopy = nullptr; + if (Addr) + { + pCopy = createSocketAddr(); + + if (pCopy) + memcpy(&(pCopy->m_sockaddr),&(Addr->m_sockaddr), sizeof(struct sockaddr)); + } + return pCopy; +} + +sal_Bool SAL_CALL osl_isEqualSocketAddr(oslSocketAddr Addr1, oslSocketAddr Addr2) +{ + OSL_ASSERT(Addr1); + OSL_ASSERT(Addr2); + struct sockaddr* pAddr1= &(Addr1->m_sockaddr); + struct sockaddr* pAddr2= &(Addr2->m_sockaddr); + + OSL_ASSERT(pAddr1); + OSL_ASSERT(pAddr2); + + if (pAddr1->sa_family == pAddr2->sa_family) + { + switch (pAddr1->sa_family) + { + case AF_INET: + { + struct sockaddr_in* pInetAddr1= reinterpret_cast<struct sockaddr_in*>(pAddr1); + struct sockaddr_in* pInetAddr2= reinterpret_cast<struct sockaddr_in*>(pAddr2); + + if ((pInetAddr1->sin_family == pInetAddr2->sin_family) && + (pInetAddr1->sin_addr.s_addr == pInetAddr2->sin_addr.s_addr) && + (pInetAddr1->sin_port == pInetAddr2->sin_port)) + return true; + [[fallthrough]]; + } + + default: + { + return (memcmp(pAddr1, pAddr2, sizeof(struct sockaddr)) == 0); + } + } + } + + return false; +} + +oslSocketAddr SAL_CALL osl_createInetBroadcastAddr ( + rtl_uString *strDottedAddr, + sal_Int32 Port) +{ + sal_uInt32 nAddr = OSL_INADDR_NONE; + + if (strDottedAddr && strDottedAddr->length) + { + IN_ADDR addr; + INT ret = InetPtonW(AF_INET, o3tl::toW(strDottedAddr->buffer), & addr); + if (1 == ret) + { + nAddr = addr.S_un.S_addr; + } + } + + if (nAddr != OSL_INADDR_NONE) + { + /* Limited broadcast */ + nAddr = ntohl(nAddr); + if (IN_CLASSA(nAddr)) + { + nAddr &= IN_CLASSA_NET; + nAddr |= IN_CLASSA_HOST; + } + else if (IN_CLASSB(nAddr)) + { + nAddr &= IN_CLASSB_NET; + nAddr |= IN_CLASSB_HOST; + } + else if (IN_CLASSC(nAddr)) + { + nAddr &= IN_CLASSC_NET; + nAddr |= IN_CLASSC_HOST; + } + else + { + /* No broadcast in class D */ + return nullptr; + } + nAddr = htonl(nAddr); + } + + oslSocketAddr pAddr = + createSocketAddrWithFamily( osl_Socket_FamilyInet, htons( static_cast<sal_uInt16>(Port)), nAddr ); + return pAddr; +} + +oslSocketAddr SAL_CALL osl_createInetSocketAddr ( + rtl_uString *strDottedAddr, + sal_Int32 Port) +{ + sal_uInt32 Addr; + + IN_ADDR addr; + INT ret = InetPtonW(AF_INET, o3tl::toW(strDottedAddr->buffer), & addr); + Addr = ret == 1 ? addr.S_un.S_addr : OSL_INADDR_NONE; + + oslSocketAddr pAddr = nullptr; + if(Addr != OSL_INADDR_NONE) + { + pAddr = createSocketAddrWithFamily( osl_Socket_FamilyInet, htons( static_cast<sal_uInt16>(Port)), Addr ); + } + return pAddr; +} + +oslSocketResult SAL_CALL osl_setAddrOfSocketAddr( oslSocketAddr pAddr, sal_Sequence *pByteSeq ) +{ + OSL_ASSERT( pAddr ); + OSL_ASSERT( pByteSeq ); + + oslSocketResult res = osl_Socket_Error; + if( pAddr && pByteSeq ) + { + OSL_ASSERT( pAddr->m_sockaddr.sa_family == FAMILY_TO_NATIVE( osl_Socket_FamilyInet ) ); + OSL_ASSERT( pByteSeq->nElements == 4 ); + struct sockaddr_in * pSystemInetAddr = reinterpret_cast<struct sockaddr_in *>(&pAddr->m_sockaddr); + memcpy( &(pSystemInetAddr->sin_addr) , pByteSeq->elements , 4 ); + res = osl_Socket_Ok; + } + return res; +} + +/** Returns the addr field in the struct sockaddr. ppByteSeq is in network byteorder. *ppByteSeq may + either be 0 or contain a constructed sal_Sequence. + */ +oslSocketResult SAL_CALL osl_getAddrOfSocketAddr( oslSocketAddr pAddr, sal_Sequence **ppByteSeq ) +{ + OSL_ASSERT( pAddr ); + OSL_ASSERT( ppByteSeq ); + + oslSocketResult res = osl_Socket_Error; + if( pAddr && ppByteSeq ) + { + struct sockaddr_in * pSystemInetAddr = reinterpret_cast<struct sockaddr_in *>(&pAddr->m_sockaddr); + rtl_byte_sequence_constructFromArray( ppByteSeq , reinterpret_cast<sal_Int8 *>(&pSystemInetAddr->sin_addr),4); + res = osl_Socket_Ok; + } + return res; +} + +struct oslHostAddrImpl { + rtl_uString *pHostName; + oslSocketAddr pSockAddr; +} ; + +oslHostAddr SAL_CALL osl_createHostAddr ( + rtl_uString *strHostname, + const oslSocketAddr pSocketAddr) +{ + oslHostAddr pAddr; + rtl_uString *cn= nullptr; + + if ((strHostname == nullptr) || (strHostname->length == 0) || (pSocketAddr == nullptr)) + return nullptr; + + rtl_uString_newFromString( &cn, strHostname); + + pAddr= static_cast<oslHostAddr>(malloc (sizeof (struct oslHostAddrImpl))); + + if (pAddr == nullptr) + { + rtl_uString_release(cn); + return nullptr; + } + + pAddr->pHostName= cn; + pAddr->pSockAddr= osl_copySocketAddr( pSocketAddr ); + + return pAddr; +} + +oslHostAddr SAL_CALL osl_createHostAddrByName(rtl_uString *strHostname) +{ + if ((strHostname == nullptr) || (strHostname->length == 0)) + return nullptr; + + PADDRINFOW pAddrInfo = nullptr; + int ret = GetAddrInfoW( + o3tl::toW(strHostname->buffer), nullptr, nullptr, & pAddrInfo); + if (0 != ret) + { + SAL_INFO("sal.osl", "GetAddrInfoW failed: " << WSAGetLastError()); + return nullptr; + } + + oslHostAddr pRet = nullptr; + for (PADDRINFOW pIter = pAddrInfo; pIter; pIter = pIter->ai_next) + { + if (AF_INET == pIter->ai_family) + { + pRet = static_cast<oslHostAddr>( + rtl_allocateZeroMemory(sizeof(struct oslHostAddrImpl))); + if (pIter->ai_canonname == nullptr) { + rtl_uString_new(&pRet->pHostName); + } else { + rtl_uString_newFromStr(&pRet->pHostName, o3tl::toU(pIter->ai_canonname)); + } + pRet->pSockAddr = createSocketAddr(); + memcpy(& pRet->pSockAddr->m_sockaddr, + pIter->ai_addr, pIter->ai_addrlen); + break; // ignore other results + } + } + FreeAddrInfoW(pAddrInfo); + return pRet; +} + +oslHostAddr SAL_CALL osl_createHostAddrByAddr(const oslSocketAddr pAddr) +{ + if (pAddr == nullptr) + return nullptr; + + if (pAddr->m_sockaddr.sa_family != FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + return nullptr; + + const struct sockaddr_in *sin= reinterpret_cast<const struct sockaddr_in *>(&pAddr->m_sockaddr); + + if (sin->sin_addr.s_addr == htonl(INADDR_ANY)) + return nullptr; + + WCHAR buf[NI_MAXHOST]; + int ret = GetNameInfoW( + & pAddr->m_sockaddr, sizeof(struct sockaddr), + buf, NI_MAXHOST, + nullptr, 0, 0); + if (0 != ret) + { + SAL_INFO("sal.osl", "GetNameInfoW failed: " << WSAGetLastError()); + return nullptr; + } + + oslHostAddr pRet = static_cast<oslHostAddr>( + rtl_allocateZeroMemory(sizeof(struct oslHostAddrImpl))); + rtl_uString_newFromStr(&pRet->pHostName, o3tl::toU(buf)); + pRet->pSockAddr = createSocketAddr(); + memcpy(& pRet->pSockAddr->m_sockaddr, + & pAddr->m_sockaddr, sizeof(struct sockaddr)); + return pRet; +} + +oslHostAddr SAL_CALL osl_copyHostAddr(const oslHostAddr Addr) +{ + oslHostAddr pAddr = Addr; + + if (pAddr) + return osl_createHostAddr (pAddr->pHostName, pAddr->pSockAddr); + else + return nullptr; +} + +void SAL_CALL osl_getHostnameOfHostAddr( + const oslHostAddr pAddr, rtl_uString **strHostname) +{ + if (pAddr) + rtl_uString_assign (strHostname, pAddr->pHostName); + else + rtl_uString_new (strHostname); +} + +oslSocketAddr SAL_CALL osl_getSocketAddrOfHostAddr(const oslHostAddr pAddr) +{ + if (pAddr) + return pAddr->pSockAddr; + else + return nullptr; +} + +void SAL_CALL osl_destroyHostAddr(oslHostAddr pAddr) +{ + if (pAddr) + { + if (pAddr->pHostName) + rtl_uString_release (pAddr->pHostName); + if (pAddr->pSockAddr) + osl_destroySocketAddr( pAddr->pSockAddr ); + + free (pAddr); + } +} + +oslSocketResult SAL_CALL osl_getLocalHostname (rtl_uString **strLocalHostname) +{ + static auto const init = []() -> std::pair<oslSocketResult, OUString> { + sal_Unicode LocalHostname[256] = {0}; + + char Host[256]= ""; + if (gethostname(Host, sizeof(Host)) == 0) + { + OUString u; + if (rtl_convertStringToUString( + &u.pData, Host, strlen(Host), osl_getThreadTextEncoding(), + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)) + && o3tl::make_unsigned(u.getLength()) < SAL_N_ELEMENTS(LocalHostname)) + { + memcpy(LocalHostname, u.getStr(), (u.getLength() + 1) * sizeof (sal_Unicode)); + } + } + + if (rtl_ustr_getLength(LocalHostname) > 0) + { + return {osl_Socket_Ok, OUString(LocalHostname)}; + } + + return {osl_Socket_Error, OUString()}; + }(); + + rtl_uString_assign (strLocalHostname, init.second.pData); + + return init.first; +} + +oslSocketAddr SAL_CALL osl_resolveHostname(rtl_uString* strHostname) +{ + oslHostAddr pAddr = osl_createHostAddrByName (strHostname); + if (pAddr) + { + oslSocketAddr SockAddr = osl_copySocketAddr( pAddr->pSockAddr ); + osl_destroyHostAddr(pAddr); + return SockAddr; + } + return nullptr; +} + +sal_Int32 SAL_CALL osl_getServicePort ( + rtl_uString* strServicename, + rtl_uString* strProtocol) +{ + struct servent* ps; + + rtl_String *str_Servicename=nullptr; + rtl_String *str_Protocol=nullptr; + + rtl_uString2String( + &str_Servicename, + rtl_uString_getStr(strServicename), + rtl_uString_getLength(strServicename), + RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS); + rtl_uString2String( + &str_Protocol, + rtl_uString_getStr(strProtocol), + rtl_uString_getLength(strProtocol), + RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS); + + ps= getservbyname( + rtl_string_getStr(str_Servicename), + rtl_string_getStr(str_Protocol)); + + rtl_string_release( str_Servicename ); + rtl_string_release( str_Protocol ); + + if (ps != nullptr) + return ntohs(ps->s_port); + + return OSL_INVALID_PORT; +} + +void SAL_CALL osl_destroySocketAddr(oslSocketAddr pAddr) +{ + destroySocketAddr( pAddr ); +} + +oslAddrFamily SAL_CALL osl_getFamilyOfSocketAddr(oslSocketAddr pAddr) +{ + if (pAddr) + return FAMILY_FROM_NATIVE(pAddr->m_sockaddr.sa_family); + else + return osl_Socket_FamilyInvalid; +} + +sal_Int32 SAL_CALL osl_getInetPortOfSocketAddr(oslSocketAddr pAddr) +{ + if( pAddr ) + { + struct sockaddr_in* pSystemInetAddr= reinterpret_cast<struct sockaddr_in*>(&pAddr->m_sockaddr); + + if (pSystemInetAddr->sin_family == FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + return ntohs(pSystemInetAddr->sin_port); + } + return OSL_INVALID_PORT; +} + +sal_Bool SAL_CALL osl_setInetPortOfSocketAddr ( + oslSocketAddr pAddr, + sal_Int32 Port) +{ + if (pAddr == nullptr) + return false; + + struct sockaddr_in* pSystemInetAddr= reinterpret_cast<struct sockaddr_in*>(&pAddr->m_sockaddr); + + if (pSystemInetAddr->sin_family != FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + return false; + + pSystemInetAddr->sin_port= htons(static_cast<short>(Port)); + return true; +} + +oslSocketResult SAL_CALL osl_getHostnameOfSocketAddr ( + oslSocketAddr Addr, + rtl_uString **strHostName) +{ + oslHostAddr pAddr= osl_createHostAddrByAddr (Addr); + + if (pAddr) + { + rtl_uString_newFromString(strHostName, pAddr->pHostName); + + osl_destroyHostAddr(pAddr); + + return osl_Socket_Ok; + } + + return osl_Socket_Error; +} + +oslSocketResult SAL_CALL osl_getDottedInetAddrOfSocketAddr ( + oslSocketAddr pAddr, + rtl_uString **strDottedInetAddr) +{ + if (pAddr == nullptr) + return osl_Socket_Error; + + struct sockaddr_in *pSystemInetAddr = reinterpret_cast<struct sockaddr_in*>(&pAddr->m_sockaddr); + if (pSystemInetAddr->sin_family != FAMILY_TO_NATIVE(osl_Socket_FamilyInet)) + return osl_Socket_Error; + + *strDottedInetAddr = nullptr; + WCHAR buf[16]; // 16 for IPV4, 46 for IPV6 + PCWSTR ret = InetNtopW( + AF_INET, & pSystemInetAddr->sin_addr, + buf, SAL_N_ELEMENTS(buf)); + if (nullptr == ret) + { + SAL_INFO("sal.osl", "InetNtopW failed: " << WSAGetLastError()); + return osl_Socket_Error; + } + rtl_uString_newFromStr(strDottedInetAddr, o3tl::toU(ret)); + OSL_ASSERT(*strDottedInetAddr != nullptr); + + return osl_Socket_Ok; +} + +oslSocket SAL_CALL osl_createSocket( + oslAddrFamily Family, + oslSocketType Type, + oslProtocol Protocol) +{ + /* alloc memory */ + oslSocket pSocket = createSocketImpl(0); + + if (pSocket == nullptr) + return nullptr; + + /* create socket */ + pSocket->m_Socket = socket(FAMILY_TO_NATIVE(Family), + TYPE_TO_NATIVE(Type), + PROTOCOL_TO_NATIVE(Protocol)); + + /* creation failed => free memory */ + if(pSocket->m_Socket == OSL_INVALID_SOCKET) + { + int nErrno = WSAGetLastError(); + SAL_WARN("sal.osl", "socket creation failed: (" << nErrno << "): " << WindowsErrorString(nErrno)); + + destroySocketImpl(pSocket); + pSocket = nullptr; + } + else + { + pSocket->m_Flags = 0; + } + + return pSocket; +} + +void SAL_CALL osl_acquireSocket(oslSocket pSocket) +{ + osl_atomic_increment(&(pSocket->m_nRefCount)); +} + +void SAL_CALL osl_releaseSocket(oslSocket pSocket) +{ + if (pSocket && osl_atomic_decrement(&(pSocket->m_nRefCount)) == 0) + { + osl_closeSocket(pSocket); + destroySocketImpl(pSocket); + } +} + +void SAL_CALL osl_closeSocket(oslSocket pSocket) +{ + /* socket already invalid */ + if (!pSocket) + return; + + /* close */ + closesocket(pSocket->m_Socket); + + pSocket->m_Socket = OSL_INVALID_SOCKET; +} + +/** + Note that I rely on the fact that oslSocketAddr and struct sockaddr + are the same! I don't like it very much but see no other easy way + to conceal the struct sockaddr from the eyes of the user. +*/ +oslSocketAddr SAL_CALL osl_getLocalAddrOfSocket(oslSocket pSocket) +{ + struct sockaddr Addr; + int AddrLen; + + if (pSocket == nullptr) /* ENOTSOCK */ + return nullptr; + + AddrLen= sizeof(struct sockaddr); + + if (getsockname(pSocket->m_Socket, &Addr, &AddrLen) == OSL_SOCKET_ERROR) + return nullptr; + + oslSocketAddr pAddr = createSocketAddFromSystem( &Addr ); + return pAddr; +} + +oslSocketAddr SAL_CALL osl_getPeerAddrOfSocket(oslSocket pSocket) +{ + struct sockaddr Addr; + int AddrLen; + + if (pSocket == nullptr) /* ENOTSOCK */ + return nullptr; + + AddrLen= sizeof(struct sockaddr); + + if (getpeername(pSocket->m_Socket, &Addr, &AddrLen) == OSL_SOCKET_ERROR) + return nullptr; + + oslSocketAddr pAddr = createSocketAddFromSystem( &Addr ); + return pAddr; +} + +sal_Bool SAL_CALL osl_bindAddrToSocket ( oslSocket pSocket, oslSocketAddr pAddr) +{ + OSL_ASSERT( pAddr ); + + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + return (bind(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) != OSL_SOCKET_ERROR); +} + +oslSocketResult SAL_CALL osl_connectSocketTo ( + oslSocket pSocket, + oslSocketAddr pAddr, + const TimeValue* pTimeout) +{ + + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + if (pAddr == nullptr) /* EDESTADDRREQ */ + return osl_Socket_Error; + + if (pTimeout == nullptr) + { + if(connect(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) == OSL_SOCKET_ERROR) + return osl_Socket_Error; + else + return osl_Socket_Ok; + } + else + { + fd_set fds; + int error; + struct timeval tv; + unsigned long Param; + oslSocketResult Result= osl_Socket_Ok; + + if (pSocket->m_Flags & OSL_SOCKET_FLAGS_NONBLOCKING) + { + if (connect(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) == OSL_SOCKET_ERROR) + { + switch (WSAGetLastError()) + { + case WSAEWOULDBLOCK: + case WSAEINPROGRESS: + return osl_Socket_InProgress; + + default: + return osl_Socket_Error; + } + } + else + return osl_Socket_Ok; + } + + /* set socket temporarily to non-blocking */ + Param= 1; + OSL_VERIFY(ioctlsocket( + pSocket->m_Socket, FIONBIO, &Param) != OSL_SOCKET_ERROR); + + /* initiate connect */ + if (connect(pSocket->m_Socket, + &(pAddr->m_sockaddr), + sizeof(struct sockaddr)) != OSL_SOCKET_ERROR) + { + /* immediate connection */ + + Param= 0; + ioctlsocket(pSocket->m_Socket, FIONBIO, &Param); + + return osl_Socket_Ok; + } + else + { + error = WSAGetLastError(); + + /* really an error or just delayed? */ + if (error != WSAEWOULDBLOCK && error != WSAEINPROGRESS) + { + Param= 0; + ioctlsocket(pSocket->m_Socket, FIONBIO, &Param); + + return osl_Socket_Error; + } + } + + /* prepare select set for socket */ + FD_ZERO(&fds); + FD_SET(pSocket->m_Socket, &fds); + + /* divide milliseconds into seconds and microseconds */ + tv.tv_sec= pTimeout->Seconds; + tv.tv_usec= pTimeout->Nanosec / 1000L; + + /* select */ + error= select(pSocket->m_Socket+1, + nullptr, + &fds, + nullptr, + &tv); + + if (error > 0) /* connected */ + { + SAL_WARN_IF( + !FD_ISSET(pSocket->m_Socket, &fds), + "sal.osl", + "osl_connectSocketTo(): select returned but socket not set"); + + Result= osl_Socket_Ok; + + } + else if(error < 0) /* error */ + { + /* errno == EBADF: most probably interrupted by close() */ + if(WSAGetLastError() == WSAEBADF) + { + /* do not access pSockImpl because it is about to be or */ + /* already destroyed */ + return osl_Socket_Interrupted; + } + else + Result= osl_Socket_Error; + + } + else /* timeout */ + Result= osl_Socket_TimedOut; + + /* clean up */ + Param= 0; + ioctlsocket(pSocket->m_Socket, FIONBIO, &Param); + + return Result; + } +} + +sal_Bool SAL_CALL osl_listenOnSocket ( + oslSocket pSocket, + sal_Int32 MaxPendingConnections) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + return (listen(pSocket->m_Socket, + MaxPendingConnections == -1 ? + SOMAXCONN : + MaxPendingConnections) != OSL_SOCKET_ERROR); +} + +oslSocket SAL_CALL osl_acceptConnectionOnSocket ( + oslSocket pSocket, + oslSocketAddr* ppAddr) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return nullptr; + + SOCKET Connection; + if(ppAddr) + { + if( *ppAddr ) + { + osl_destroySocketAddr( *ppAddr ); + *ppAddr = nullptr; + } + int AddrLen= sizeof(struct sockaddr); + + /* user wants to know peer Addr */ + struct sockaddr Addr; + + Connection= accept(pSocket->m_Socket, &Addr, &AddrLen); + OSL_ASSERT(AddrLen == sizeof(struct sockaddr)); + + if(Connection != static_cast<SOCKET>(OSL_SOCKET_ERROR)) + *ppAddr= createSocketAddFromSystem(&Addr); + else + *ppAddr = nullptr; + } + else + { + /* user is not interested in peer-addr */ + Connection= accept(pSocket->m_Socket, nullptr, nullptr); + } + + /* accept failed? */ + if(Connection == static_cast<SOCKET>(OSL_SOCKET_ERROR)) + return nullptr; + + /* alloc memory */ + oslSocket pConnectionSocket; + pConnectionSocket= createSocketImpl(Connection); + + pConnectionSocket->m_Flags = 0; + + return pConnectionSocket; +} + +sal_Int32 SAL_CALL osl_receiveSocket ( + oslSocket pSocket, + void* pBuffer, + sal_uInt32 BytesToRead, + oslSocketMsgFlag Flag) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + return recv(pSocket->m_Socket, + static_cast<char*>(pBuffer), + BytesToRead, + MSG_FLAG_TO_NATIVE(Flag)); +} + +sal_Int32 SAL_CALL osl_receiveFromSocket ( + oslSocket pSocket, + oslSocketAddr SenderAddr, + void* pBuffer, + sal_uInt32 BufferSize, + oslSocketMsgFlag Flag) +{ + struct sockaddr *pSystemSockAddr = nullptr; + int AddrLen = 0; + if( SenderAddr ) + { + AddrLen = sizeof( struct sockaddr ); + pSystemSockAddr = &(SenderAddr->m_sockaddr); + } + + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + return recvfrom(pSocket->m_Socket, + static_cast<char*>(pBuffer), + BufferSize, + MSG_FLAG_TO_NATIVE(Flag), + pSystemSockAddr, + &AddrLen); +} + +sal_Int32 SAL_CALL osl_sendSocket ( + oslSocket pSocket, + const void* pBuffer, + sal_uInt32 BytesToSend, + oslSocketMsgFlag Flag) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + return send(pSocket->m_Socket, + static_cast<char const *>(pBuffer), + BytesToSend, + MSG_FLAG_TO_NATIVE(Flag)); +} + +sal_Int32 SAL_CALL osl_sendToSocket ( + oslSocket pSocket, + oslSocketAddr ReceiverAddr, + const void* pBuffer, + sal_uInt32 BytesToSend, + oslSocketMsgFlag Flag) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + /* ReceiverAddr might be 0 when used on a connected socket. */ + /* Then sendto should behave like send. */ + + struct sockaddr *pSystemSockAddr = nullptr; + if( ReceiverAddr ) + pSystemSockAddr = &(ReceiverAddr->m_sockaddr); + + return sendto(pSocket->m_Socket, + static_cast<char const *>(pBuffer), + BytesToSend, + MSG_FLAG_TO_NATIVE(Flag), + pSystemSockAddr, + pSystemSockAddr == nullptr ? 0 : sizeof(struct sockaddr)); +} + +sal_Int32 SAL_CALL osl_readSocket( oslSocket pSocket, void *pBuffer, sal_Int32 n ) +{ + sal_uInt8 * Ptr = static_cast<sal_uInt8 *>(pBuffer); + + OSL_ASSERT( pSocket); + + /* loop until all desired bytes were read or an error occurred */ + sal_uInt32 BytesRead= 0; + sal_uInt32 BytesToRead= n; + while (BytesToRead > 0) + { + sal_Int32 RetVal; + RetVal= osl_receiveSocket(pSocket, + Ptr, + BytesToRead, + osl_Socket_MsgNormal); + + /* error occurred? */ + if(RetVal <= 0) + { + break; + } + + BytesToRead -= RetVal; + BytesRead += RetVal; + Ptr += RetVal; + } + + return BytesRead; +} + +sal_Int32 SAL_CALL osl_writeSocket( oslSocket pSocket, const void *pBuffer, sal_Int32 n ) +{ + OSL_ASSERT( pSocket ); + + /* loop until all desired bytes were send or an error occurred */ + sal_uInt32 BytesSend= 0; + sal_uInt32 BytesToSend= n; + sal_uInt8 const *Ptr = static_cast<sal_uInt8 const *>(pBuffer); + while (BytesToSend > 0) + { + sal_Int32 RetVal; + + RetVal= osl_sendSocket( pSocket,Ptr,BytesToSend,osl_Socket_MsgNormal); + + /* error occurred? */ + if(RetVal <= 0) + { + break; + } + + BytesToSend -= RetVal; + BytesSend += RetVal; + Ptr += RetVal; + + } + return BytesSend; +} + +sal_Bool SAL_CALL osl_isReceiveReady ( + oslSocket pSocket, + const TimeValue* pTimeout) +{ + fd_set fds; + struct timeval tv; + + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + FD_ZERO(&fds); + FD_SET(pSocket->m_Socket, &fds); + + if (pTimeout) + { + tv.tv_sec = pTimeout->Seconds; + tv.tv_usec = pTimeout->Nanosec / 1000L; + } + + return (select(pSocket->m_Socket + 1, /* no of sockets to monitor */ + &fds, /* check read operations */ + nullptr, /* check write ops */ + nullptr, /* ckeck for OOB */ + pTimeout ? &tv : nullptr)==1); /* use timeout? */ +} + +/*****************************************************************************/ +/* osl_isSendReady */ +/*****************************************************************************/ +sal_Bool SAL_CALL osl_isSendReady ( + oslSocket pSocket, + const TimeValue* pTimeout) +{ + fd_set fds; + struct timeval tv; + + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + FD_ZERO(&fds); + FD_SET(pSocket->m_Socket, &fds); + + if (pTimeout) + { + tv.tv_sec = pTimeout->Seconds; + tv.tv_usec = pTimeout->Nanosec / 1000L; + } + + return (select(pSocket->m_Socket + 1, /* no of sockets to monitor */ + nullptr, /* check read operations */ + &fds, /* check write ops */ + nullptr, /* ckeck for OOB */ + pTimeout ? &tv : nullptr)==1); /* use timeout? */ +} + +sal_Bool SAL_CALL osl_isExceptionPending ( + oslSocket pSocket, + const TimeValue* pTimeout) +{ + fd_set fds; + struct timeval tv; + + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + FD_ZERO(&fds); + FD_SET(pSocket->m_Socket, &fds); + + if (pTimeout) + { + tv.tv_sec = pTimeout->Seconds; + tv.tv_usec = pTimeout->Nanosec / 1000L; + } + + return (select(pSocket->m_Socket + 1, /* no of sockets to monitor */ + nullptr, /* check read operations */ + nullptr, /* check write ops */ + &fds, /* ckeck for OOB */ + pTimeout ? &tv : nullptr)==1); /* use timeout? */ +} + +sal_Bool SAL_CALL osl_shutdownSocket ( + oslSocket pSocket, + oslSocketDirection Direction) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + return (shutdown(pSocket->m_Socket, DIRECTION_TO_NATIVE(Direction))==0); +} + +sal_Int32 SAL_CALL osl_getSocketOption ( + oslSocket pSocket, + oslSocketOptionLevel Level, + oslSocketOption Option, + void* pBuffer, + sal_uInt32 BufferLen) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_Error; + + int len = BufferLen; + if (getsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(Level), + OPTION_TO_NATIVE(Option), + static_cast<char *>(pBuffer), + &len) == -1) + { + return -1; + } + + return len; +} + +sal_Bool SAL_CALL osl_setSocketOption ( + oslSocket pSocket, + oslSocketOptionLevel Level, + oslSocketOption Option, + void* pBuffer, + sal_uInt32 BufferLen) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + return(setsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(Level), + OPTION_TO_NATIVE(Option), + static_cast<char*>(pBuffer), + BufferLen) == 0); +} + +sal_Bool SAL_CALL osl_enableNonBlockingMode ( oslSocket pSocket, sal_Bool On) +{ + unsigned long Param= On ? 1 : 0; + + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + pSocket->m_Flags = Param ? + (pSocket->m_Flags | OSL_SOCKET_FLAGS_NONBLOCKING) : + (pSocket->m_Flags & ~OSL_SOCKET_FLAGS_NONBLOCKING) ; + + return ( + ioctlsocket(pSocket->m_Socket, FIONBIO, &Param) != OSL_SOCKET_ERROR); +} + +sal_Bool SAL_CALL osl_isNonBlockingMode(oslSocket pSocket) +{ + if (pSocket == nullptr) /* ENOTSOCK */ + return false; + + return (pSocket->m_Flags & OSL_SOCKET_FLAGS_NONBLOCKING) != 0; +} + +oslSocketType SAL_CALL osl_getSocketType(oslSocket pSocket) +{ + int Type=0; + int TypeSize= sizeof(Type); + + if (pSocket == nullptr) /* ENOTSOCK */ + return osl_Socket_TypeInvalid; + + if(getsockopt(pSocket->m_Socket, + OPTION_LEVEL_TO_NATIVE(osl_Socket_LevelSocket), + OPTION_TO_NATIVE(osl_Socket_OptionType), + reinterpret_cast<char *>(&Type), + &TypeSize) == -1) + { + /* error */ + return osl_Socket_TypeInvalid; + } + + return TYPE_FROM_NATIVE(Type); +} + +void SAL_CALL osl_getLastSocketErrorDescription ( + oslSocket /*Socket*/, + rtl_uString **strError) +{ + int error; + + switch(error = WSAGetLastError()) + { + case WSAENOTSOCK: + rtl_uString_newFromAscii (strError, "WSAENOTSOCK, Socket operation on non-socket. A socket created in one process is used by another process."); + break; + + case WSAEDESTADDRREQ: + rtl_uString_newFromAscii (strError, "WSAEDESTADDRREQ, Destination Addr required"); + break; + + case WSAEMSGSIZE: + rtl_uString_newFromAscii (strError, "WSAEMSGSIZE, Message too long"); + break; + + case WSAEPROTOTYPE: + rtl_uString_newFromAscii (strError, "WSAEPROTOTYPE, Protocol wrong type for socket"); + break; + + case WSAENOPROTOOPT: + rtl_uString_newFromAscii (strError, "WSAENOPROTOOPT, Protocol not available"); + break; + + case WSAEPROTONOSUPPORT: + rtl_uString_newFromAscii (strError, "WSAEPROTONOSUPPORT, Protocol not supported"); + break; + + case WSAESOCKTNOSUPPORT: + rtl_uString_newFromAscii (strError, "WSAESOCKTNOSUPPORT, Socket type not supported"); + break; + + case WSAEOPNOTSUPP: + rtl_uString_newFromAscii (strError, "WSAEOPNOTSUPP, Operation not supported on socket"); + break; + + case WSAEPFNOSUPPORT: + rtl_uString_newFromAscii (strError, "WSAEPFNOSUPPORT, Protocol family not supported"); + break; + + case WSAEAFNOSUPPORT: + rtl_uString_newFromAscii (strError, "WSEAFNOSUPPORT, Addr family not supported by protocol family"); + break; + + case WSAEADDRINUSE: + rtl_uString_newFromAscii (strError, "WSAEADDRINUSE, Triggered by bind() because a process went down without closing a socket."); + break; + + case WSAEADDRNOTAVAIL: + rtl_uString_newFromAscii (strError, "WSAEADDRNOTAVAIL, Can't assign requested Addr"); + break; + + case WSAENETDOWN: + rtl_uString_newFromAscii (strError, "WSAENETDOWN, Network is down"); + break; + + case WSAENETUNREACH: + rtl_uString_newFromAscii (strError, "WSAENETUNREACH, Network is unreachable"); + break; + + case WSAENETRESET: + rtl_uString_newFromAscii (strError, "WSAENETRESET, Network dropped connection or reset"); + break; + + case WSAECONNABORTED: + rtl_uString_newFromAscii (strError, "WSAECONNABORTED, Software caused connection abort"); + break; + + case WSAECONNRESET: + rtl_uString_newFromAscii (strError, "WSAECONNRESET, Connection reset by peer"); + break; + + case WSAENOBUFS: + rtl_uString_newFromAscii (strError, "WSAENOBUFS, No buffer space available."); + break; + + case WSAEISCONN: + rtl_uString_newFromAscii (strError, "WSAEISCONN, Socket is already connected"); + break; + + case WSAENOTCONN: + rtl_uString_newFromAscii (strError, "WSAENOTCONN, Socket is not connected"); + break; + + case WSAESHUTDOWN: + rtl_uString_newFromAscii (strError, "WSAESHUTDOWN, Can't send after socket shutdown"); + break; + + case WSAETIMEDOUT: + rtl_uString_newFromAscii (strError, "WSAETIMEDOUT, Connection timed out"); + break; + + case WSAECONNREFUSED: + rtl_uString_newFromAscii (strError, "WSAECONNREFUSED, Connection refused"); + break; + + case WSAEHOSTDOWN: + rtl_uString_newFromAscii (strError, "WSAEHOSTDOWN, Networking subsystem not started"); + break; + + case WSAEHOSTUNREACH: + rtl_uString_newFromAscii (strError, "WSAEHOSTUNREACH, No route to host"); + break; + + case WSAEWOULDBLOCK: + rtl_uString_newFromAscii (strError, "WSAEWOULDBLOCK, Operation would block"); + break; + + case WSAEINPROGRESS: + rtl_uString_newFromAscii (strError, "WSAEINPROGRESS, Operation now in progress"); + break; + + case WSAEALREADY: + rtl_uString_newFromAscii (strError, "WSAEALREADY, Operation already in progress"); + break; + + case WSAEINTR: + rtl_uString_newFromAscii (strError, "WSAEALREADY, Operation was interrupted"); + break; + + case WSAEBADF: + rtl_uString_newFromAscii (strError, "WSAEBADF, Bad file number"); + break; + + case WSAEACCES: + rtl_uString_newFromAscii (strError, "WSAEACCES, Access is denied"); + break; + + case WSAEFAULT: + rtl_uString_newFromAscii (strError, "WSAEFAULT, Bad memory Addr"); + break; + + case WSAEINVAL: + rtl_uString_newFromAscii (strError, "WSAEINVAL, The socket has not been bound with bind() or is already connected"); + break; + + case WSAEMFILE: + rtl_uString_newFromAscii (strError, "WSAEMFILE, No more file descriptors are available"); + break; + + case WSAETOOMANYREFS: + rtl_uString_newFromAscii (strError, "WSAETOOMANYREFS, Undocumented WinSock error"); + break; + + case WSAENAMETOOLONG: + rtl_uString_newFromAscii (strError, "WSAENAMETOOLONG, Undocumented WinSock error"); + break; + + case WSAENOTEMPTY: + rtl_uString_newFromAscii (strError, "WSAENOTEMPTY, Undocumented WinSock error"); + break; + + case WSAEPROCLIM: + rtl_uString_newFromAscii (strError, "WSAEPROCLIM, Undocumented WinSock error"); + break; + + case WSAEUSERS: + rtl_uString_newFromAscii (strError, "WSAEUSERS, Undocumented WinSock error"); + break; + + case WSAEDQUOT: + rtl_uString_newFromAscii (strError, "WSAEDQUOT, Undocumented WinSock error"); + break; + + case WSAESTALE: + rtl_uString_newFromAscii (strError, "WSAESTALE, Undocumented WinSock error"); + break; + + case WSAEREMOTE: + rtl_uString_newFromAscii (strError, "WSAEREMOTE, Undocumented WinSock error"); + break; + + case WSAEDISCON: + rtl_uString_newFromAscii (strError, "WSAEDISCON, Circuit was gracefully terminated"); + break; + + case WSASYSNOTREADY: + rtl_uString_newFromAscii (strError, "WSASYSNOTREADY, The underlying network subsystem is not ready for network communication"); + break; + + case WSAVERNOTSUPPORTED: + rtl_uString_newFromAscii (strError, "WSAVERNOTSUPPORTED, The version of Windows Sockets API support requested is not provided by this particular Windows Sockets implementation"); + break; + + case WSANOTINITIALISED: + rtl_uString_newFromAscii (strError, "WSANOTINITIALISED, WSAStartup() has not been called"); + break; + + case WSAHOST_NOT_FOUND: + rtl_uString_newFromAscii (strError, "WSAHOST_NOT_FOUND, Authoritative answer host not found"); + break; + + case WSATRY_AGAIN: + rtl_uString_newFromAscii (strError, "WSATRY_AGAIN, Non-authoritative answer host not found or SERVERFAIL"); + break; + + case WSANO_RECOVERY: + rtl_uString_newFromAscii (strError, "WSANO_RECOVERY, Non recoverable errors, FORMERR, REFUSED, NOTIMP"); + break; + + case WSANO_DATA: + rtl_uString_newFromAscii (strError, "WSANO_DATA or WSANO_ADDRESS, Valid name, no data record of requested type"); + break; + + default: + { + sal_Unicode message[128]; + + wsprintfW(o3tl::toW(message), L"Unknown WinSock Error Number %d", error); + rtl_uString_newFromStr (strError, message); + } + + return; + + } +} + +oslSocketError SAL_CALL osl_getLastSocketError(oslSocket /*Socket*/) +{ + return ERROR_FROM_NATIVE(WSAGetLastError()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/sockimpl.hxx b/sal/osl/w32/sockimpl.hxx new file mode 100644 index 000000000..72b204a2e --- /dev/null +++ b/sal/osl/w32/sockimpl.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/socket.h> +#include <osl/interlck.h> + +#define OSL_SOCKET_FLAGS_NONBLOCKING 0x0001 + +struct oslSocketImpl { + oslInterlockedCount m_nRefCount; + SOCKET m_Socket; + int m_Flags; +}; + +struct oslSocketAddrImpl +{ + struct sockaddr m_sockaddr; + oslInterlockedCount m_nRefCount; +}; + +oslSocket osl_createSocketImpl_(SOCKET Socket); +void osl_destroySocketImpl_(oslSocket pImpl); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/system.h b/sal/osl/w32/system.h new file mode 100644 index 000000000..921d746fd --- /dev/null +++ b/sal/osl/w32/system.h @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#if OSL_DEBUG_LEVEL <= 3 +#define NO_DEBUG_CRT +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <malloc.h> +#include <limits.h> +#include <process.h> +#include <time.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <io.h> +#include <share.h> +#include <direct.h> + +/* Must define this else build breaks because Winsock2.h + includes Windows.h and without WIN32_LEAN_AND_MEAN + also includes mswsock.h which needs a forward typedef + of SOCKET ... +*/ +#define WIN32_LEAN_AND_MEAN + +// winsock2.h includes windows.h +#include <winsock2.h> +#include <wsipx.h> +#include <ws2tcpip.h> +#include <shlobj.h> +#ifndef NO_DEBUG_CRT +#include <crtdbg.h> +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/tempfile.cxx b/sal/osl/w32/tempfile.cxx new file mode 100644 index 000000000..f0065bf2d --- /dev/null +++ b/sal/osl/w32/tempfile.cxx @@ -0,0 +1,238 @@ +/* -*- 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 <systools/win32/uwinapi.h> + +#include <osl/file.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <rtl/ustring.hxx> + +#include "file-impl.hxx" +#include "file_error.hxx" +#include "file_url.hxx" +#include "path_helper.hxx" + +#include <malloc.h> +#include <cassert> + +// Allocate n number of t's on the stack return a pointer to it in p +#define STACK_ALLOC(p, t, n) __try {(p) = static_cast<t*>(_alloca((n)*sizeof(t)));} \ + __except(EXCEPTION_EXECUTE_HANDLER) {(p) = nullptr;} + +// Temp file functions + +static oslFileError osl_setup_base_directory_impl_( + rtl_uString* pustrDirectoryURL, + rtl_uString** ppustr_base_dir) +{ + OUString dir_url; + OUString dir; + oslFileError error = osl_File_E_None; + + if (pustrDirectoryURL) + dir_url = pustrDirectoryURL; + else + error = osl_getTempDirURL(&dir_url.pData); + + if (error == osl_File_E_None) + error = osl_getSystemPathFromFileURL_(dir_url, &dir.pData, false); + + if (error == osl_File_E_None) + rtl_uString_assign(ppustr_base_dir, dir.pData); + + return error; +} + +static oslFileError osl_setup_createTempFile_impl_( + rtl_uString* pustrDirectoryURL, + oslFileHandle* pHandle, + rtl_uString** ppustrTempFileURL, + rtl_uString** ppustr_base_dir, + sal_Bool* b_delete_on_close) +{ + oslFileError osl_error; + + OSL_PRECOND(((pHandle != nullptr) || (ppustrTempFileURL != nullptr)), "Invalid parameter!"); + + if ((pHandle == nullptr) && (ppustrTempFileURL == nullptr)) + { + osl_error = osl_File_E_INVAL; + } + else + { + osl_error = osl_setup_base_directory_impl_( + pustrDirectoryURL, ppustr_base_dir); + + *b_delete_on_close = (ppustrTempFileURL == nullptr); + } + + return osl_error; +} + +static oslFileError osl_win32_GetTempFileName_impl_( + rtl_uString* base_directory, LPWSTR temp_file_name) +{ + oslFileError osl_error = osl_File_E_None; + + if (GetTempFileNameW( + o3tl::toW(rtl_uString_getStr(base_directory)), + L"", + 0, + temp_file_name) == 0) + { + osl_error = oslTranslateFileError(GetLastError()); + } + + return osl_error; +} + +static bool osl_win32_CreateFile_impl_( + LPCWSTR file_name, bool b_delete_on_close, oslFileHandle* p_handle) +{ + DWORD flags = FILE_ATTRIBUTE_NORMAL; + HANDLE hFile; + + assert(p_handle); + + if (b_delete_on_close) + flags |= FILE_FLAG_DELETE_ON_CLOSE; + + hFile = CreateFileW( + file_name, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + TRUNCATE_EXISTING, + flags, + nullptr); + + // @@@ ERROR HANDLING @@@ + if (IsValidHandle(hFile)) + *p_handle = osl_createFileHandleFromOSHandle(hFile, osl_File_OpenFlag_Read | osl_File_OpenFlag_Write); + + return IsValidHandle(hFile); +} + +static oslFileError osl_createTempFile_impl_( + rtl_uString* base_directory, + LPWSTR tmp_name, + bool b_delete_on_close, + oslFileHandle* pHandle, + rtl_uString** ppustrTempFileURL) +{ + oslFileError osl_error; + + do + { + osl_error = osl_win32_GetTempFileName_impl_(base_directory, tmp_name); + + /* if file could not be opened try again */ + + if ((osl_File_E_None != osl_error) || (nullptr == pHandle) || + osl_win32_CreateFile_impl_(tmp_name, b_delete_on_close, pHandle)) + break; + + } while(true); // try until success + + if ((osl_error == osl_File_E_None) && !b_delete_on_close) + { + rtl_uString* pustr = nullptr; + rtl_uString_newFromStr(&pustr, o3tl::toU(tmp_name)); + osl_getFileURLFromSystemPath(pustr, ppustrTempFileURL); + rtl_uString_release(pustr); + } + + return osl_error; +} + +oslFileError SAL_CALL osl_createTempFile( + rtl_uString* pustrDirectoryURL, + oslFileHandle* pHandle, + rtl_uString** ppustrTempFileURL) +{ + rtl_uString* base_directory = nullptr; + LPWSTR tmp_name; + sal_Bool b_delete_on_close; + oslFileError osl_error; + + osl_error = osl_setup_createTempFile_impl_( + pustrDirectoryURL, + pHandle, + ppustrTempFileURL, + &base_directory, + &b_delete_on_close); + + if (osl_error != osl_File_E_None) + return osl_error; + + /* allocate enough space on the stack, the file name can not be longer than MAX_PATH */ + STACK_ALLOC(tmp_name, WCHAR, (rtl_uString_getLength(base_directory) + MAX_PATH)); + + if (tmp_name) + { + osl_error = osl_createTempFile_impl_( + base_directory, + tmp_name, + b_delete_on_close, + pHandle, + ppustrTempFileURL); + } + else // stack alloc failed + { + osl_error = osl_File_E_NOMEM; + } + + if (base_directory) + rtl_uString_release(base_directory); + + return osl_error; +} + +oslFileError SAL_CALL osl_getTempDirURL(rtl_uString** pustrTempDir) +{ + ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH ); + LPWSTR lpBuffer = o3tl::toW(aBuffer); + DWORD nBufferLength = aBuffer.getBufSizeInSymbols() - 1; + + DWORD nLength; + oslFileError error; + + nLength = GetTempPathW( aBuffer.getBufSizeInSymbols(), lpBuffer ); + + if ( nLength > nBufferLength ) + { + // the provided path has invalid length + error = osl_File_E_NOENT; + } + else if ( nLength ) + { + if ( '\\' == lpBuffer[nLength-1] ) + --nLength; + + const OUString ustrTempPath(o3tl::toU(lpBuffer), static_cast<sal_Int32>(nLength)); + + error = osl_getFileURLFromSystemPath(ustrTempPath.pData, pustrTempDir); + } + else + error = oslTranslateFileError( GetLastError() ); + + return error; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/thread.cxx b/sal/osl/w32/thread.cxx new file mode 100644 index 000000000..fe5ac30b0 --- /dev/null +++ b/sal/osl/w32/thread.cxx @@ -0,0 +1,540 @@ +/* -*- 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 "system.h" +#include "thread.hxx" + +#include <comphelper/windowserrorstring.hxx> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <osl/thread.h> +#include <rtl/alloc.h> +#include <osl/time.h> +#include <osl/interlck.h> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <systools/win32/comtools.hxx> + +#include <errno.h> + +namespace { + +/** + Thread-data structure hidden behind oslThread: + */ +typedef struct +{ + HANDLE m_hThread; /* OS-handle used for all thread-functions */ + DWORD m_ThreadId; /* identifier for this thread */ + sal_Int32 m_nTerminationRequested; + oslWorkerFunction m_WorkerFunction; + void* m_pData; + +} osl_TThreadImpl; + +} + +static oslThread oslCreateThread(oslWorkerFunction pWorker, void* pThreadData, sal_uInt32 nFlags); + +static DWORD WINAPI oslWorkerWrapperFunction(_In_ LPVOID pData) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(pData); + + /* Initialize COM - Multi Threaded Apartment (MTA) for all threads */ + sal::systools::CoInitializeGuard aGuard(COINIT_MULTITHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow); + + /* call worker-function with data */ + + pThreadImpl->m_WorkerFunction(pThreadImpl->m_pData); + + return 0; +} + +static oslThread oslCreateThread(oslWorkerFunction pWorker, + void* pThreadData, + sal_uInt32 nFlags) +{ + osl_TThreadImpl* pThreadImpl; + + /* alloc mem. for our internal data structure */ + pThreadImpl= static_cast<osl_TThreadImpl *>(malloc(sizeof(osl_TThreadImpl))); + + OSL_ASSERT(pThreadImpl); + + if ( pThreadImpl == nullptr ) + { + return nullptr; + } + + pThreadImpl->m_WorkerFunction= pWorker; + pThreadImpl->m_pData= pThreadData; + pThreadImpl->m_nTerminationRequested= 0; + + pThreadImpl->m_hThread= CreateThread( + nullptr, /* no security */ + 0, /* default stack-size */ + oslWorkerWrapperFunction, /* worker-function */ + pThreadImpl, /* provide worker-function with data */ + nFlags, /* start thread immediately or suspended */ + &pThreadImpl->m_ThreadId); + + if(pThreadImpl->m_hThread == nullptr) + { + SAL_WARN("sal.osl", "CreateThread failed:" << WindowsErrorString(GetLastError())); + + /* create failed */ + free(pThreadImpl); + return nullptr; + } + + return pThreadImpl; +} + +oslThread SAL_CALL osl_createThread(oslWorkerFunction pWorker, + void* pThreadData) +{ + return oslCreateThread(pWorker, pThreadData, 0); +} + +oslThread SAL_CALL osl_createSuspendedThread(oslWorkerFunction pWorker, + void* pThreadData) +{ + return oslCreateThread(pWorker, pThreadData, CREATE_SUSPENDED); +} + +oslThreadIdentifier SAL_CALL osl_getThreadIdentifier(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + if (pThreadImpl != nullptr) + return static_cast<oslThreadIdentifier>(pThreadImpl->m_ThreadId); + else + return static_cast<oslThreadIdentifier>(GetCurrentThreadId()); +} + +void SAL_CALL osl_destroyThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + if (Thread == nullptr) /* valid ptr? */ + { + /* thread already destroyed or not created */ + return; + } + + /* !!!! _exitthreadex does _not_ call CloseHandle !!! */ + CloseHandle( pThreadImpl->m_hThread ); + + /* free memory */ + free(Thread); +} + +void SAL_CALL osl_resumeThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + OSL_ASSERT(pThreadImpl); /* valid ptr? */ + + ResumeThread(pThreadImpl->m_hThread); +} + +void SAL_CALL osl_suspendThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + OSL_ASSERT(pThreadImpl); /* valid ptr? */ + + SuspendThread(pThreadImpl->m_hThread); +} + +void SAL_CALL osl_setThreadPriority(oslThread Thread, + oslThreadPriority Priority) +{ + int winPriority; + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + OSL_ASSERT(pThreadImpl); /* valid ptr? */ + + /* map enum to WIN32 levels + it would be faster and more elegant to preset + the enums, but that would require an #ifdef in + the exported header, which is not desired. + */ + switch(Priority) { + + case osl_Thread_PriorityHighest: + winPriority= THREAD_PRIORITY_HIGHEST; + break; + + case osl_Thread_PriorityAboveNormal: + winPriority= THREAD_PRIORITY_ABOVE_NORMAL; + break; + + case osl_Thread_PriorityNormal: + winPriority= THREAD_PRIORITY_NORMAL; + break; + + case osl_Thread_PriorityBelowNormal: + winPriority= THREAD_PRIORITY_BELOW_NORMAL; + break; + + case osl_Thread_PriorityLowest: + winPriority= THREAD_PRIORITY_LOWEST; + break; + + case osl_Thread_PriorityUnknown: + OSL_ASSERT(FALSE); /* only fools try this...*/ + + /* let release-version behave friendly */ + return; + + default: + OSL_ASSERT(FALSE); /* enum expanded, but forgotten here...*/ + + /* let release-version behave friendly */ + return; + } + + SetThreadPriority(pThreadImpl->m_hThread, winPriority); +} + +oslThreadPriority SAL_CALL osl_getThreadPriority(const oslThread Thread) +{ + int winPriority; + oslThreadPriority Priority; + + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + /* invalid arguments ?*/ + if(pThreadImpl==nullptr || pThreadImpl->m_hThread==nullptr) + { + return osl_Thread_PriorityUnknown; + } + + winPriority= + GetThreadPriority(pThreadImpl->m_hThread); + + if(winPriority == THREAD_PRIORITY_ERROR_RETURN) + { + return osl_Thread_PriorityUnknown; + } + + /* map WIN32 priority to enum */ + switch(winPriority) + { + case THREAD_PRIORITY_TIME_CRITICAL: + case THREAD_PRIORITY_HIGHEST: + Priority= osl_Thread_PriorityHighest; + break; + + case THREAD_PRIORITY_ABOVE_NORMAL: + Priority= osl_Thread_PriorityAboveNormal; + break; + + case THREAD_PRIORITY_NORMAL: + Priority= osl_Thread_PriorityNormal; + break; + + case THREAD_PRIORITY_BELOW_NORMAL: + Priority= osl_Thread_PriorityBelowNormal; + break; + + case THREAD_PRIORITY_IDLE: + case THREAD_PRIORITY_LOWEST: + Priority= osl_Thread_PriorityLowest; + break; + + default: + OSL_ASSERT(FALSE); /* WIN32 API changed, incorporate new prio-level! */ + + /* release-version behaves friendly */ + Priority= osl_Thread_PriorityUnknown; + } + + return Priority; +} + +sal_Bool SAL_CALL osl_isThreadRunning(const oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + /* invalid arguments ?*/ + if(pThreadImpl==nullptr || pThreadImpl->m_hThread==nullptr) + { + return false; + } + + return WaitForSingleObject(pThreadImpl->m_hThread, 0) != WAIT_OBJECT_0; +} + +void SAL_CALL osl_joinWithThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + /* invalid arguments?*/ + if(pThreadImpl==nullptr || pThreadImpl->m_hThread==nullptr) + { + /* assume thread is not running */ + return; + } + + WaitForSingleObject(pThreadImpl->m_hThread, INFINITE); +} + +void SAL_CALL osl_waitThread(const TimeValue* pDelay) +{ + if (pDelay) + { + DWORD millisecs = pDelay->Seconds * 1000L + pDelay->Nanosec / 1000000L; + + Sleep(millisecs); + } +} + +void SAL_CALL osl_terminateThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + /* invalid arguments?*/ + if (pThreadImpl==nullptr || pThreadImpl->m_hThread==nullptr) + { + /* assume thread is not running */ + return; + } + + osl_atomic_increment(&(pThreadImpl->m_nTerminationRequested)); +} + +sal_Bool SAL_CALL osl_scheduleThread(oslThread Thread) +{ + osl_TThreadImpl* pThreadImpl= static_cast<osl_TThreadImpl*>(Thread); + + osl_yieldThread(); + + /* invalid arguments?*/ + if (pThreadImpl==nullptr || pThreadImpl->m_hThread==nullptr) + { + /* assume thread is not running */ + return false; + } + + return 0 == pThreadImpl->m_nTerminationRequested; +} + +void SAL_CALL osl_yieldThread(void) +{ + Sleep(0); +} + +static void impSetThreadDescription(char const * name) { + // SetThreadDescription is only available since Windows 10 version 1607 + typedef HRESULT(WINAPI * TSetThreadDescription)(HANDLE, PCWSTR); + static const auto pSetThreadDescription = reinterpret_cast<TSetThreadDescription>( + GetProcAddress(GetModuleHandleA("KernelBase.dll"), "SetThreadDescription")); + if (pSetThreadDescription) + { + if (const int nReqCCh = MultiByteToWideChar(CP_ACP, 0, name, -1, nullptr, 0)) + { + if (PWSTR wStr = static_cast<PWSTR>(malloc(nReqCCh * sizeof(WCHAR)))) + { + if (MultiByteToWideChar(CP_ACP, 0, name, -1, wStr, nReqCCh)) + pSetThreadDescription(GetCurrentThread(), wStr); + free(wStr); + } + } + } +} + +void SAL_CALL osl_setThreadName(char const * name) { + /* See < https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code >: */ +#pragma pack(push, 8) + struct { + DWORD dwType = 0x1000; + LPCSTR szName; + DWORD dwThreadID = DWORD(-1); + DWORD dwFlags = 0; + } info; +#pragma pack(pop) + info.szName = name; + __try { + RaiseException( + 0x406D1388, 0, sizeof info / sizeof (ULONG_PTR), + reinterpret_cast<ULONG_PTR *>(&info)); + } __except (EXCEPTION_EXECUTE_HANDLER) {} + + impSetThreadDescription(name); +} + +namespace { + +typedef struct TLS_ +{ + DWORD dwIndex; + oslThreadKeyCallbackFunction pfnCallback; + struct TLS_ *pNext, *pPrev; +} TLS, *PTLS; + +PTLS g_pThreadKeyList = nullptr; +osl::Mutex& getThreadKeyListMutex() +{ + static osl::Mutex g_ThreadKeyListMutex; + return g_ThreadKeyListMutex; +} + +} + +static void AddKeyToList( PTLS pTls ) +{ + if ( pTls ) + { + osl::MutexGuard aGuard(getThreadKeyListMutex()); + + pTls->pNext = g_pThreadKeyList; + pTls->pPrev = nullptr; + + if ( g_pThreadKeyList ) + g_pThreadKeyList->pPrev = pTls; + + g_pThreadKeyList = pTls; + } +} + +static void RemoveKeyFromList( PTLS pTls ) +{ + if ( pTls ) + { + osl::MutexGuard aGuard(getThreadKeyListMutex()); + if ( pTls->pPrev ) + pTls->pPrev->pNext = pTls->pNext; + else + { + OSL_ASSERT( pTls == g_pThreadKeyList ); + g_pThreadKeyList = pTls->pNext; + } + + if ( pTls->pNext ) + pTls->pNext->pPrev = pTls->pPrev; + } +} + +void osl_callThreadKeyCallbackOnThreadDetach(void) +{ + PTLS pTls; + + osl::MutexGuard aGuard(getThreadKeyListMutex()); + pTls = g_pThreadKeyList; + while ( pTls ) + { + if ( pTls->pfnCallback ) + { + void *pValue = TlsGetValue( pTls->dwIndex ); + + if ( pValue ) + pTls->pfnCallback( pValue ); + } + + pTls = pTls->pNext; + } +} + +oslThreadKey SAL_CALL osl_createThreadKey(oslThreadKeyCallbackFunction pCallback) +{ + PTLS pTls = static_cast<PTLS>(malloc( sizeof(TLS) )); + + if ( pTls ) + { + pTls->pfnCallback = pCallback; + if ( DWORD(-1) == (pTls->dwIndex = TlsAlloc()) ) + { + free( pTls ); + pTls = nullptr; + } + else + AddKeyToList( pTls ); + } + + return pTls; +} + +void SAL_CALL osl_destroyThreadKey(oslThreadKey Key) +{ + if (Key != nullptr) + { + PTLS pTls = static_cast<PTLS>(Key); + + RemoveKeyFromList( pTls ); + TlsFree( pTls->dwIndex ); + free( pTls ); + } +} + +void* SAL_CALL osl_getThreadKeyData(oslThreadKey Key) +{ + if (Key != nullptr) + { + PTLS pTls = static_cast<PTLS>(Key); + + return TlsGetValue( pTls->dwIndex ); + } + + return nullptr; +} + +sal_Bool SAL_CALL osl_setThreadKeyData(oslThreadKey Key, void *pData) +{ + if (Key != nullptr) + { + PTLS pTls = static_cast<PTLS>(Key); + void* pOldData = nullptr; + bool fSuccess; + + if ( pTls->pfnCallback ) + pOldData = TlsGetValue( pTls->dwIndex ); + + fSuccess = TlsSetValue( pTls->dwIndex, pData ); + + if ( fSuccess && pTls->pfnCallback && pOldData ) + pTls->pfnCallback( pOldData ); + + return fSuccess; + } + + return false; +} + +namespace +{ +rtl_TextEncoding& getThreadTextEncodingImpl() +{ + static thread_local rtl_TextEncoding s_enc = rtl_getTextEncodingFromWindowsCodePage(GetACP()); + return s_enc; +} +} + +rtl_TextEncoding SAL_CALL osl_getThreadTextEncoding(void) { return getThreadTextEncodingImpl(); } + +rtl_TextEncoding SAL_CALL osl_setThreadTextEncoding( rtl_TextEncoding Encoding ) +{ + rtl_TextEncoding oldEncoding = getThreadTextEncodingImpl(); + getThreadTextEncodingImpl() = Encoding; + return oldEncoding; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/thread.hxx b/sal/osl/w32/thread.hxx new file mode 100644 index 000000000..8002d8b7f --- /dev/null +++ b/sal/osl/w32/thread.hxx @@ -0,0 +1,18 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <sal/types.h> + +void osl_callThreadKeyCallbackOnThreadDetach(void); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/w32/time.cxx b/sal/osl/w32/time.cxx new file mode 100644 index 000000000..3f11746fe --- /dev/null +++ b/sal/osl/w32/time.cxx @@ -0,0 +1,200 @@ +/* -*- 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 "system.h" + +#include "filetime.hxx" +#include "time.hxx" + +#include <osl/diagnose.h> +#include <osl/time.h> +#include <sys/timeb.h> + +sal_Bool SAL_CALL osl_getSystemTime(TimeValue* pTimeVal) +{ + unsigned __int64 CurTime; + + typedef VOID (WINAPI *GetSystemTimePreciseAsFileTime_PROC)(LPFILETIME); + + OSL_ASSERT(pTimeVal != nullptr); + + static GetSystemTimePreciseAsFileTime_PROC pGetSystemTimePreciseAsFileTime = []() + { + HMODULE hModule = GetModuleHandleW( L"Kernel32.dll" ); + return reinterpret_cast<GetSystemTimePreciseAsFileTime_PROC>( + GetProcAddress(hModule, "GetSystemTimePreciseAsFileTime")); + }(); + + // use ~1 microsecond resolution if available + if (pGetSystemTimePreciseAsFileTime) + pGetSystemTimePreciseAsFileTime(reinterpret_cast<LPFILETIME>(&CurTime)); + else + { + SYSTEMTIME SystemTime; + GetSystemTime(&SystemTime); + SystemTimeToFileTime(&SystemTime, reinterpret_cast<LPFILETIME>(&CurTime)); + } + + static const unsigned __int64 OffTime = [] { + SYSTEMTIME SystemTime; + SystemTime.wYear = 1970; + SystemTime.wMonth = 1; + SystemTime.wDayOfWeek = 0; + SystemTime.wDay = 1; + SystemTime.wHour = 0; + SystemTime.wMinute = 0; + SystemTime.wSecond = 0; + SystemTime.wMilliseconds = 0; + + unsigned __int64 ft; + SystemTimeToFileTime(&SystemTime, reinterpret_cast<LPFILETIME>(&ft)); + return ft; + }(); + + const unsigned __int64 Value = CurTime - OffTime; + + pTimeVal->Seconds = static_cast<unsigned long>(Value / 10000000L); + pTimeVal->Nanosec = static_cast<unsigned long>((Value % 10000000L) * 100); + + return true; +} + +sal_Bool SAL_CALL osl_getDateTimeFromTimeValue( const TimeValue* pTimeVal, oslDateTime* pDateTime ) +{ + FILETIME aFileTime; + SYSTEMTIME aSystemTime; + + if ( TimeValueToFileTime(pTimeVal, &aFileTime) ) + { + if ( FileTimeToSystemTime( &aFileTime, &aSystemTime ) ) + { + pDateTime->NanoSeconds = pTimeVal->Nanosec; + + pDateTime->Seconds = aSystemTime.wSecond; + pDateTime->Minutes = aSystemTime.wMinute; + pDateTime->Hours = aSystemTime.wHour; + pDateTime->Day = aSystemTime.wDay; + pDateTime->DayOfWeek = aSystemTime.wDayOfWeek; + pDateTime->Month = aSystemTime.wMonth; + pDateTime->Year = aSystemTime.wYear; + + return true; + } + } + + return false; +} + +sal_Bool SAL_CALL osl_getTimeValueFromDateTime( const oslDateTime* pDateTime, TimeValue* pTimeVal ) +{ + FILETIME aFileTime; + SYSTEMTIME aSystemTime; + + aSystemTime.wMilliseconds = 0; + aSystemTime.wSecond = pDateTime->Seconds; + aSystemTime.wMinute = pDateTime->Minutes; + aSystemTime.wHour = pDateTime->Hours; + aSystemTime.wDay = pDateTime->Day; + aSystemTime.wDayOfWeek = pDateTime->DayOfWeek; + aSystemTime.wMonth = pDateTime->Month; + aSystemTime.wYear = pDateTime->Year; + + if ( SystemTimeToFileTime( &aSystemTime, &aFileTime ) ) + { + if (FileTimeToTimeValue( &aFileTime, pTimeVal ) ) + { + pTimeVal->Nanosec = pDateTime->NanoSeconds; + return true; + } + } + + return false; +} + +sal_Bool SAL_CALL osl_getLocalTimeFromSystemTime( const TimeValue* pSystemTimeVal, TimeValue* pLocalTimeVal ) +{ + TIME_ZONE_INFORMATION aTimeZoneInformation; + + // get timezone information + DWORD Success = GetTimeZoneInformation( &aTimeZoneInformation ); + if (Success == TIME_ZONE_ID_INVALID) + return false; + + sal_Int64 bias = aTimeZoneInformation.Bias; + + // add bias for daylight saving time + if ( Success == TIME_ZONE_ID_DAYLIGHT ) + bias+=aTimeZoneInformation.DaylightBias; + + if ( static_cast<sal_Int64>(pSystemTimeVal->Seconds) > ( bias * 60 ) ) + { + pLocalTimeVal->Seconds = static_cast<sal_uInt32>(pSystemTimeVal->Seconds - ( bias * 60) ); + pLocalTimeVal->Nanosec = pSystemTimeVal->Nanosec; + + return true; + } + return false; +} + +sal_Bool SAL_CALL osl_getSystemTimeFromLocalTime( const TimeValue* pLocalTimeVal, TimeValue* pSystemTimeVal ) +{ + TIME_ZONE_INFORMATION aTimeZoneInformation; + + // get timezone information + DWORD Success = GetTimeZoneInformation( &aTimeZoneInformation ); + if ( Success == TIME_ZONE_ID_INVALID ) + return false; + + sal_Int64 bias = aTimeZoneInformation.Bias; + + // add bias for daylight saving time + if ( Success == TIME_ZONE_ID_DAYLIGHT ) + bias+=aTimeZoneInformation.DaylightBias; + + if ( static_cast<sal_Int64>(pLocalTimeVal->Seconds) + ( bias * 60 ) > 0 ) + { + pSystemTimeVal->Seconds = static_cast<sal_uInt32>( pLocalTimeVal->Seconds + ( bias * 60) ); + pSystemTimeVal->Nanosec = pLocalTimeVal->Nanosec; + + return true; + } + + return false; +} + +static struct _timeb startTime; +void sal_initGlobalTimer() +{ + _ftime( &startTime ); +} + +sal_uInt32 SAL_CALL osl_getGlobalTimer(void) +{ + struct _timeb currentTime; + sal_uInt32 nSeconds; + + _ftime( ¤tTime ); + + nSeconds = static_cast<sal_uInt32>( currentTime.time - startTime.time ); + + return ( nSeconds * 1000 ) + static_cast<long>( currentTime.millitm - startTime.millitm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/w32/time.hxx b/sal/osl/w32/time.hxx new file mode 100644 index 000000000..c0ca9d777 --- /dev/null +++ b/sal/osl/w32/time.hxx @@ -0,0 +1,19 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_SAL_OSL_W32_TIME_HXX +#define INCLUDED_SAL_OSL_W32_TIME_HXX + +#include <sal/config.h> + +void sal_initGlobalTimer(void); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |