diff options
Diffstat (limited to 'sal/osl/unx')
53 files changed, 18492 insertions, 0 deletions
diff --git a/sal/osl/unx/asm/interlck_sparc.s b/sal/osl/unx/asm/interlck_sparc.s new file mode 100644 index 0000000000..7b971a50a4 --- /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 0000000000..f757c85066 --- /dev/null +++ b/sal/osl/unx/backtrace.c @@ -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 . + */ + +#include "backtrace.h" + +#if ! HAVE_FEATURE_BACKTRACE /* no GNU backtrace implementation available */ + +#include <sal/types.h> + +#ifdef __sun /* Solaris */ + +#include "backtrace_solaris.c" + +#elif defined FREEBSD || defined NETBSD || defined OPENBSD || defined(DRAGONFLY) + +#include "backtrace_bsd.c" + +#else /* not GNU/BSD/Solaris */ + +#include "backtrace_other.c" + +#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 0000000000..11a9da52ef --- /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/backtrace_bsd.c b/sal/osl/unx/backtrace_bsd.c new file mode 100644 index 0000000000..f20b739cc2 --- /dev/null +++ b/sal/osl/unx/backtrace_bsd.c @@ -0,0 +1,105 @@ +/* -*- 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 . + */ + +// This file is #include from backtrace.c + +#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 ); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/backtrace_other.c b/sal/osl/unx/backtrace_other.c new file mode 100644 index 0000000000..d5ad0a8e53 --- /dev/null +++ b/sal/osl/unx/backtrace_other.c @@ -0,0 +1,39 @@ +/* -*- 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 . + */ + +// This file is #include from backtrace.c + +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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/backtrace_solaris.c b/sal/osl/unx/backtrace_solaris.c new file mode 100644 index 0000000000..76f4475bfb --- /dev/null +++ b/sal/osl/unx/backtrace_solaris.c @@ -0,0 +1,134 @@ +/* -*- 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 . + */ + +// This file is #include from backtrace.c + +#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 ); + } +} + + +/* 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 0000000000..4c1f25b886 --- /dev/null +++ b/sal/osl/unx/backtraceapi.cxx @@ -0,0 +1,278 @@ +/* -*- 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 <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 0000000000..16c4ad11b1 --- /dev/null +++ b/sal/osl/unx/conditn.cxx @@ -0,0 +1,151 @@ +/* -*- 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 <condition_variable> +#include <mutex> + +#include <sal/log.hxx> +#include <sal/types.h> + +#include <osl/conditn.h> +#include <osl/time.h> + +namespace { + +struct oslConditionImpl +{ + std::condition_variable m_Condition; + std::mutex m_Lock; + bool m_State = false; +}; + +} + +oslCondition SAL_CALL osl_createCondition() +{ + oslConditionImpl* pCond = new oslConditionImpl; + + 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 ) + delete pCond; +} + +sal_Bool SAL_CALL osl_setCondition(oslCondition Condition) +{ + oslConditionImpl* pCond; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + { + std::unique_lock g(pCond->m_Lock); + + pCond->m_State = true; + pCond->m_Condition.notify_all(); + } + SAL_INFO( "sal.osl.condition", "osl_setCondition(" << pCond << ")" ); + + return true; + +} + +sal_Bool SAL_CALL osl_resetCondition(oslCondition Condition) +{ + oslConditionImpl* pCond; + + assert(Condition); + + pCond = static_cast<oslConditionImpl*>(Condition); + + { + std::unique_lock g(pCond->m_Lock); + + pCond->m_State = false; + } + SAL_INFO( "sal.osl.condition", "osl_resetCondition(" << pCond << ")" ); + + return true; +} + +oslConditionResult SAL_CALL osl_waitCondition(oslCondition Condition, const TimeValue* pTimeout) +{ + oslConditionImpl* pCond; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + SAL_INFO( "sal.osl.condition", "osl_waitCondition(" << pCond << ")" ); + + { + std::unique_lock g(pCond->m_Lock); + + if ( pTimeout ) + { + if ( ! pCond->m_State ) + { + auto duration = std::chrono::seconds(pTimeout->Seconds) + + std::chrono::nanoseconds(pTimeout->Nanosec); + if (!pCond->m_Condition.wait_for(g, duration, [&pCond](){return pCond->m_State;})) + return osl_cond_result_timeout; + } + } + else + { + pCond->m_Condition.wait(g, [&pCond](){return pCond->m_State;}); + } + } + 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; + + assert(Condition); + pCond = static_cast<oslConditionImpl*>(Condition); + + { + std::unique_lock g(pCond->m_Lock); + + State = pCond->m_State; + } + 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 0000000000..11f60ef129 --- /dev/null +++ b/sal/osl/unx/createfilehandlefromfd.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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <osl/file.h> + +namespace osl::detail +{ +oslFileHandle createFileHandleFromFD(int fd); // defined in file.cxx +} + +/* 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 0000000000..eeee7c803f --- /dev/null +++ b/sal/osl/unx/file.cxx @@ -0,0 +1,1605 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> +#include <o3tl/safeint.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <sal/log.hxx> +#include <osl/detail/file.h> +#include <rtl/byteseq.h> +#include <rtl/string.hxx> + +#include "system.hxx" +#include "createfilehandlefromfd.hxx" +#include "file_error_transl.hxx" +#include "file_impl.hxx" +#include "file_url.hxx" +#include "uunxapi.hxx" +#include "unixerrnostring.hxx" + +#include <algorithm> +#include <atomic> +#include <cassert> +#include <fcntl.h> +#include <limits> +#include <limits.h> + +#include <string.h> +#include <pthread.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#if defined(MACOSX) + +#include <sys/param.h> +#include <sys/mount.h> +#define HAVE_O_EXLOCK + +#include <CoreFoundation/CoreFoundation.h> + +#endif /* MACOSX */ + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#include <android/log.h> +#include <android/asset_manager.h> +#include <o3tl/string_view.hxx> +#include <vector> +#endif + +namespace { + +enum class State +{ + Seekable = 1, /*< default */ + Readable = 2, /*< default */ + Writeable = 4, /*< open() sets, write() requires, else osl_File_E_BADF */ + Modified = 8 /*< write() sets, flush() resets */ +}; + +} + +template<> struct o3tl::typed_flags<State>: o3tl::is_typed_flags<State, 15> {}; + +namespace { + +struct FileHandle_Impl +{ + pthread_mutex_t m_mutex; + OString m_strFilePath; /*< holds native file path */ + int m_fd; + + enum Kind + { + KIND_FD = 1, + KIND_MEM = 2 + }; + int m_kind; + /** State + */ + State m_state; + + sal_uInt64 m_size; /*< file size */ + off_t m_offset; /*< physical offset from begin of file */ + // m_fileptr is hit hard in some situations, where the overhead of a mutex starts to show up, so use an atomic + std::atomic<off_t> m_fileptr; /*< logical offset from begin of file */ + + off_t m_bufptr; /*< buffer offset from begin of file */ + size_t m_buflen; /*< buffer filled [0, m_bufsiz - 1] */ + + size_t m_bufsiz; + sal_uInt8 * m_buffer; +#ifdef ANDROID + rtl_String* m_memstreambuf; /*< used for in-memory streams */ +#endif + + explicit FileHandle_Impl(int fd, Kind kind = KIND_FD, OString path = "<anon>"_ostr); + ~FileHandle_Impl(); + + static size_t getpagesize(); + + sal_uInt64 getPos() const; + void setPos(sal_uInt64 uPos); + + sal_uInt64 getSize() const; + oslFileError setSize(sal_uInt64 uSize); + + oslFileError readAt( + off_t nOffset, + void* pBuffer, + size_t nBytesRequested, + sal_uInt64* pBytesRead); + + oslFileError writeAt( + off_t nOffset, + void const* pBuffer, + size_t nBytesToWrite, + sal_uInt64* pBytesWritten); + + oslFileError readFileAt( + off_t nOffset, + void* pBuffer, + size_t nBytesRequested, + sal_uInt64* pBytesRead); + + oslFileError writeFileAt( + off_t nOffset, + void const* pBuffer, + size_t nBytesToWrite, + sal_uInt64* pBytesWritten); + + oslFileError readLineAt( + off_t nOffset, + sal_Sequence** ppSequence, + sal_uInt64* pBytesRead); + + static oslFileError writeSequence_Impl( + sal_Sequence** ppSequence, + size_t* pnOffset, + const void* pBuffer, + size_t nBytes); + + oslFileError syncFile(); + + class Guard + { + pthread_mutex_t *m_mutex; + + public: + explicit Guard(pthread_mutex_t *pMutex); + ~Guard(); + }; +}; + +} + +FileHandle_Impl::Guard::Guard(pthread_mutex_t * pMutex) + : m_mutex(pMutex) +{ + assert(m_mutex); + (void) pthread_mutex_lock(m_mutex); // ignoring EINVAL if a null mutex is passed ... +} + +FileHandle_Impl::Guard::~Guard() +{ + assert(m_mutex); + (void) pthread_mutex_unlock(m_mutex); +} + +FileHandle_Impl::FileHandle_Impl(int fd, enum Kind kind, OString path) + : m_strFilePath(std::move(path)), + m_fd (fd), + m_kind (kind), + m_state (State::Seekable | State::Readable), + m_size (0), + m_offset (0), + m_fileptr (0), + m_bufptr (-1), + m_buflen (0), + m_bufsiz (0), + m_buffer (nullptr) +{ + (void) pthread_mutex_init(&m_mutex, nullptr); + if (m_kind == KIND_FD) + { + size_t const pagesize = getpagesize(); + if (pagesize != size_t(-1)) + { + m_bufsiz = pagesize; + m_buffer = static_cast<sal_uInt8 *>(calloc(1, m_bufsiz)); + } + } +} + +FileHandle_Impl::~FileHandle_Impl() +{ + if (m_kind == KIND_FD) + { + free(m_buffer); + m_buffer = nullptr; + } + + (void) pthread_mutex_destroy(&m_mutex); // ignoring EBUSY ... +} + +size_t FileHandle_Impl::getpagesize() +{ + return sal::static_int_cast< size_t >(::sysconf(_SC_PAGESIZE)); +} + +sal_uInt64 FileHandle_Impl::getPos() const +{ + return sal::static_int_cast< sal_uInt64 >(m_fileptr.load()); +} + +void FileHandle_Impl::setPos(sal_uInt64 uPos) +{ + m_fileptr = sal::static_int_cast< off_t >(uPos); +} + +sal_uInt64 FileHandle_Impl::getSize() const +{ + off_t const bufend = std::max(off_t(0), m_bufptr) + m_buflen; + return std::max(m_size, sal::static_int_cast< sal_uInt64 >(bufend)); +} + +oslFileError FileHandle_Impl::setSize(sal_uInt64 uSize) +{ + off_t const nSize = sal::static_int_cast< off_t >(uSize); + if (ftruncate_with_name(m_fd, nSize, m_strFilePath) == -1) + { + /* Failure. Save original result. Try fallback algorithm */ + oslFileError result = oslTranslateFileError(errno); + + /* Check against current size. Fail upon 'shrink' */ + if (uSize <= getSize()) + { + /* Failure upon 'shrink'. Return original result */ + return result; + } + + /* Save current position */ + off_t const nCurPos = lseek(m_fd, off_t(0), SEEK_CUR); + if (nCurPos == off_t(-1)) + { + int e = errno; + SAL_INFO("sal.file", "lseek(" << m_fd << ",0,SEEK_CUR): " << UnixErrnoString(e)); + return result; + } + else + SAL_INFO("sal.file", "lseek(" << m_fd << ",0,SEEK_CUR): OK"); + + /* Try 'expand' via 'lseek()' and 'write()' */ + if (lseek(m_fd, static_cast<off_t>(nSize - 1), SEEK_SET) == -1) + { + int e = errno; + SAL_INFO("sal.file", "lseek(" << m_fd << "," << nSize - 1 << ",SEEK_SET): " << UnixErrnoString(e)); + return result; + } + else + SAL_INFO("sal.file", "lseek(" << m_fd << "," << nSize - 1 << ",SEEK_SET): OK"); + + if (write(m_fd, "", size_t(1)) == -1) + { + /* Failure. Restore saved position */ + int e = errno; + SAL_INFO("sal.file", "write(" << m_fd << ",\"\",1): " << UnixErrnoString(e)); + (void) lseek(m_fd, nCurPos, SEEK_SET); + return result; + } + else + SAL_INFO("sal.file", "write(" << m_fd << ",\"\",1): OK"); + + /* Success. Restore saved position */ + if (lseek(m_fd, nCurPos, SEEK_SET) == -1) + return result; + } + + m_size = sal::static_int_cast< sal_uInt64 >(nSize); + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::readAt( + off_t nOffset, + void * pBuffer, + size_t nBytesRequested, + sal_uInt64 * pBytesRead) +{ + SAL_WARN_IF(!(m_state & State::Seekable), "sal.osl", "FileHandle_Impl::readAt(): not seekable"); + if (!(m_state & State::Seekable)) + return osl_File_E_SPIPE; + + SAL_WARN_IF(!(m_state & State::Readable), "sal.osl", "FileHandle_Impl::readAt(): not readable"); + if (!(m_state & State::Readable)) + return osl_File_E_BADF; + + if (m_kind == KIND_MEM) + { + ssize_t nBytes; + + m_offset = nOffset; + + if (o3tl::make_unsigned(m_offset) >= m_size) + { + nBytes = 0; + } + else + { + nBytes = std::min(nBytesRequested, static_cast<size_t>(m_size - m_offset)); + memmove(pBuffer, m_buffer + m_offset, nBytes); + m_offset += nBytes; + } + *pBytesRead = nBytes; + return osl_File_E_None; + } + + ssize_t nBytes = ::pread(m_fd, pBuffer, nBytesRequested, nOffset); + if ((nBytes == -1) && (errno == EOVERFLOW)) + { + /* Some 'pread()'s fail with EOVERFLOW when reading at (or past) + * end-of-file, different from 'lseek() + read()' behaviour. + * Returning '0 bytes read' and 'osl_File_E_None' instead. + */ + nBytes = 0; + } + + if (nBytes == -1) + return oslTranslateFileError(errno); + + *pBytesRead = nBytes; + + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::writeAt( + off_t nOffset, + void const * pBuffer, + size_t nBytesToWrite, + sal_uInt64 * pBytesWritten) +{ + SAL_WARN_IF(!(m_state & State::Seekable), "sal.osl", "FileHandle_Impl::writeAt(): not seekable"); + if (!(m_state & State::Seekable)) + return osl_File_E_SPIPE; + + SAL_WARN_IF(!(m_state & State::Writeable), "sal.osl", "FileHandle_Impl::writeAt(): not writeable"); + if (!(m_state & State::Writeable)) + return osl_File_E_BADF; + + ssize_t nBytes = ::pwrite(m_fd, pBuffer, nBytesToWrite, nOffset); + if (nBytes == -1) + return oslTranslateFileError(errno); + + m_size = std::max(m_size, sal::static_int_cast< sal_uInt64 >(nOffset + nBytes)); + + *pBytesWritten = nBytes; + + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::readFileAt( + off_t nOffset, + void* pBuffer, + size_t nBytesRequested, + sal_uInt64* pBytesRead) +{ + if (!(m_state & State::Seekable)) + { + // not seekable (pipe) + ssize_t nBytes = ::read(m_fd, pBuffer, nBytesRequested); + if (nBytes == -1) + return oslTranslateFileError(errno); + + *pBytesRead = nBytes; + + return osl_File_E_None; + } + + if (m_kind == KIND_MEM || !m_buffer) + { + // not buffered + return readAt(nOffset, pBuffer, nBytesRequested, pBytesRead); + } + + sal_uInt8 *buffer = static_cast<sal_uInt8*>(pBuffer); + for (*pBytesRead = 0; nBytesRequested > 0; ) + { + off_t const bufptr = (nOffset / m_bufsiz) * m_bufsiz; + size_t const bufpos = nOffset % m_bufsiz; + + if (bufptr != m_bufptr) + { + // flush current buffer + oslFileError result = syncFile(); + if (result != osl_File_E_None) + return result; + + m_bufptr = -1; + m_buflen = 0; + + if (nBytesRequested >= m_bufsiz) + { + // buffer too small, read through from file + sal_uInt64 uDone = 0; + result = readAt(nOffset, &(buffer[*pBytesRead]), nBytesRequested, &uDone); + if (result != osl_File_E_None) + return result; + + *pBytesRead += uDone; + + return osl_File_E_None; + } + + // update buffer (pointer) + sal_uInt64 uDone = 0; + result = readAt(bufptr, m_buffer, m_bufsiz, &uDone); + if (result != osl_File_E_None) + return result; + + m_bufptr = bufptr; + m_buflen = uDone; + } + + if (bufpos >= m_buflen) + { + // end of file + return osl_File_E_None; + } + + size_t const bytes = std::min(m_buflen - bufpos, nBytesRequested); + SAL_INFO("sal.fileio", "FileHandle_Impl::readFileAt(" << m_fd << ", " << nOffset << ", " << bytes << ")"); + + memcpy(&(buffer[*pBytesRead]), &(m_buffer[bufpos]), bytes); + nBytesRequested -= bytes; + *pBytesRead += bytes; + nOffset += bytes; + } + + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::writeFileAt( + off_t nOffset, + void const * pBuffer, + size_t nBytesToWrite, + sal_uInt64 * pBytesWritten) +{ + if (!(m_state & State::Seekable)) + { + // not seekable (pipe) + ssize_t nBytes = ::write(m_fd, pBuffer, nBytesToWrite); + if (nBytes == -1) + return oslTranslateFileError(errno); + + *pBytesWritten = nBytes; + + return osl_File_E_None; + } + if (!m_buffer) + { + // not buffered + return writeAt(nOffset, pBuffer, nBytesToWrite, pBytesWritten); + } + + sal_uInt8 const * buffer = static_cast<sal_uInt8 const *>(pBuffer); + for (*pBytesWritten = 0; nBytesToWrite > 0;) + { + off_t const bufptr = (nOffset / m_bufsiz) * m_bufsiz; + size_t const bufpos = nOffset % m_bufsiz; + if (bufptr != m_bufptr) + { + // flush current buffer + oslFileError result = syncFile(); + if (result != osl_File_E_None) + return result; + m_bufptr = -1; + m_buflen = 0; + + if (nBytesToWrite >= m_bufsiz) + { + // buffer too small, write through to file + sal_uInt64 uDone = 0; + result = writeAt(nOffset, &(buffer[*pBytesWritten]), nBytesToWrite, &uDone); + if (result != osl_File_E_None) + return result; + + if (uDone != nBytesToWrite) + return osl_File_E_IO; + + *pBytesWritten += uDone; + + return osl_File_E_None; + } + + // update buffer (pointer) + sal_uInt64 uDone = 0; + result = readAt(bufptr, m_buffer, m_bufsiz, &uDone); + if (result != osl_File_E_None) + return result; + + m_bufptr = bufptr; + m_buflen = uDone; + } + + size_t const bytes = std::min(m_bufsiz - bufpos, nBytesToWrite); + SAL_INFO("sal.fileio", "FileHandle_Impl::writeFileAt(" << m_fd << ", " << nOffset << ", " << bytes << ")"); + + memcpy(&(m_buffer[bufpos]), &(buffer[*pBytesWritten]), bytes); + nBytesToWrite -= bytes; + *pBytesWritten += bytes; + nOffset += bytes; + + m_buflen = std::max(m_buflen, bufpos + bytes); + m_state |= State::Modified; + } + + return osl_File_E_None; +} + +oslFileError FileHandle_Impl::readLineAt( + off_t nOffset, + sal_Sequence ** ppSequence, + sal_uInt64 * pBytesRead) +{ + oslFileError result = osl_File_E_None; + + off_t bufptr = nOffset / m_bufsiz * m_bufsiz; + if (bufptr != m_bufptr) + { + /* flush current buffer */ + result = syncFile(); + if (result != osl_File_E_None) + return result; + + /* update buffer (pointer) */ + sal_uInt64 uDone = 0; + result = readAt(bufptr, m_buffer, m_bufsiz, &uDone); + if (result != osl_File_E_None) + return result; + + m_bufptr = bufptr; + m_buflen = uDone; + } + + static int const LINE_STATE_BEGIN = 0; + static int const LINE_STATE_CR = 1; + static int const LINE_STATE_LF = 2; + + size_t bufpos = nOffset - m_bufptr, curpos = bufpos, dstpos = 0; + int state = (bufpos >= m_buflen) ? LINE_STATE_LF : LINE_STATE_BEGIN; + + while (state != LINE_STATE_LF) + { + if (curpos >= m_buflen) + { + /* buffer examined */ + if ((curpos - bufpos) > 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 0000000000..539d4ccfc9 --- /dev/null +++ b/sal/osl/unx/file_error_transl.cxx @@ -0,0 +1,179 @@ +/* -*- 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; + + case ENOTEMPTY: + return osl_File_E_NOTEMPTY; + + 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 0000000000..67e6b54590 --- /dev/null +++ b/sal/osl/unx/file_error_transl.hxx @@ -0,0 +1,36 @@ +/* -*- 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> + +/** 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 0000000000..4a9a90d416 --- /dev/null +++ b/sal/osl/unx/file_impl.hxx @@ -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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_FILE_IMPL_HXX +#define INCLUDED_SAL_OSL_UNX_FILE_IMPL_HXX + +#include <osl/file.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 0000000000..752c85393b --- /dev/null +++ b/sal/osl/unx/file_misc.cxx @@ -0,0 +1,1036 @@ +/* -*- 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/detail/file.h> + +#include <osl/diagnose.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 <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <utime.h> +#include <sys/stat.h> + +#include <algorithm> +#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(); +#ifdef _DIRENT_HAVE_D_TYPE + pImpl = new DirectoryItem_Impl(strFilePath, pEntry->d_type); +#else + pImpl = new DirectoryItem_Impl(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"); + } + } + + if ( S_ISREG(nMode) ) + { + /* copy SourceFile to DestFile */ + nRet = oslDoCopyFile(pszSourceFileName,pszDestFileName,nSourceSize, nMode); + } + else if ( S_ISLNK(nMode) ) + { + nRet = oslDoCopyLink(pszSourceFileName,pszDestFileName); + } + else + { + nRet = EINVAL; + } + + 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 0000000000..472ab880af --- /dev/null +++ b/sal/osl/unx/file_path_helper.cxx @@ -0,0 +1,263 @@ +/* -*- 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 <unistd.h> + +#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 0000000000..cbafb7482c --- /dev/null +++ b/sal/osl/unx/file_path_helper.hxx @@ -0,0 +1,250 @@ +/* -*- 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 <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 0000000000..5c165132e9 --- /dev/null +++ b/sal/osl/unx/file_stat.cxx @@ -0,0 +1,454 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/file.h> + +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> +#include <utime.h> + +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include "system.hxx" +#include "file_impl.hxx" +#include "file_error_transl.hxx" +#include "file_path_helper.hxx" +#include "file_url.hxx" +#include "uunxapi.hxx" + +namespace +{ + void set_file_type(const struct stat& file_stat, oslFileStatus* pStat) + { + /* links to directories state also to be a directory */ + if (S_ISLNK(file_stat.st_mode)) + pStat->eType = osl_File_Type_Link; + else if (S_ISDIR(file_stat.st_mode)) + pStat->eType = osl_File_Type_Directory; + else if (S_ISREG(file_stat.st_mode)) + pStat->eType = osl_File_Type_Regular; + else if (S_ISFIFO(file_stat.st_mode)) + pStat->eType = osl_File_Type_Fifo; + else if (S_ISSOCK(file_stat.st_mode)) + pStat->eType = osl_File_Type_Socket; + else if (S_ISCHR(file_stat.st_mode) || S_ISBLK(file_stat.st_mode)) + pStat->eType = osl_File_Type_Special; + else + pStat->eType = osl_File_Type_Unknown; + + pStat->uValidFields |= osl_FileStatus_Mask_Type; + } + + void set_file_access_mask(const struct stat& file_stat, oslFileStatus* pStat) + { + // user permissions + if (S_IRUSR & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OwnRead; + + if (S_IWUSR & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OwnWrite; + + if (S_IXUSR & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OwnExe; + + // group permissions + if (S_IRGRP & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_GrpRead; + + if (S_IWGRP & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_GrpWrite; + + if (S_IXGRP & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_GrpExe; + + // others permissions + if (S_IROTH & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OthRead; + + if (S_IWOTH & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OthWrite; + + if (S_IXOTH & file_stat.st_mode) + pStat->uAttributes |= osl_File_Attribute_OthExe; + + pStat->uValidFields |= osl_FileStatus_Mask_Attributes; + } + + /* This code used not to use access(...) because access follows links which + may cause performance problems see #97133. (That apparently references a + no-longer accessible Hamburg-internal bug-tracking system.) + However, contrary to what is stated above the use of access calls is + required on network file systems not using unix semantics (AFS, see + fdo#43095). + */ + void set_file_access_rights(const OString& file_path, oslFileStatus* pStat) + { + pStat->uValidFields |= osl_FileStatus_Mask_Attributes; + + if (osl::access(file_path, W_OK) < 0) + pStat->uAttributes |= osl_File_Attribute_ReadOnly; + + if (osl::access(file_path, X_OK) == 0) + pStat->uAttributes |= osl_File_Attribute_Executable; + } + + void set_file_hidden_status(const OString& file_path, oslFileStatus* pStat) + { + pStat->uAttributes = osl::systemPathIsHiddenFileOrDirectoryEntry(file_path) ? osl_File_Attribute_Hidden : 0; + pStat->uValidFields |= osl_FileStatus_Mask_Attributes; + } + + /* the set_file_access_rights must be called after set_file_hidden_status(...) and + set_file_access_mask(...) because of the hack in set_file_access_rights(...) */ + void set_file_attributes( + const OString& file_path, const struct stat& file_stat, const sal_uInt32 uFieldMask, oslFileStatus* pStat) + { + set_file_hidden_status(file_path, pStat); + set_file_access_mask(file_stat, pStat); + + // we set the file access rights only on demand + // because it's potentially expensive + if (uFieldMask & osl_FileStatus_Mask_Attributes) + set_file_access_rights(file_path, pStat); + } + + void set_file_access_time(const struct stat& file_stat, oslFileStatus* pStat) + { + pStat->aAccessTime.Seconds = file_stat.st_atime; + pStat->aAccessTime.Nanosec = 0; + pStat->uValidFields |= osl_FileStatus_Mask_AccessTime; + } + + void set_file_modify_time(const struct stat& file_stat, oslFileStatus* pStat) + { + pStat->aModifyTime.Seconds = file_stat.st_mtime; + pStat->aModifyTime.Nanosec = 0; + pStat->uValidFields |= osl_FileStatus_Mask_ModifyTime; + } + + void set_file_size(const struct stat& file_stat, oslFileStatus* pStat) + { + if (S_ISREG(file_stat.st_mode)) + { + pStat->uFileSize = file_stat.st_size; + pStat->uValidFields |= osl_FileStatus_Mask_FileSize; + } + } + + /* we only need to call stat or lstat if one of the + following flags is set */ + bool is_stat_call_necessary(sal_uInt32 field_mask, oslFileType file_type) + { + return ( + ((field_mask & osl_FileStatus_Mask_Type) && (file_type == osl_File_Type_Unknown)) || + (field_mask & osl_FileStatus_Mask_Attributes) || + (field_mask & osl_FileStatus_Mask_CreationTime) || + (field_mask & osl_FileStatus_Mask_AccessTime) || + (field_mask & osl_FileStatus_Mask_ModifyTime) || + (field_mask & osl_FileStatus_Mask_FileSize) || + (field_mask & osl_FileStatus_Mask_LinkTargetURL) || + (field_mask & osl_FileStatus_Mask_Validate)); + } + + oslFileError set_link_target_url(const OString& file_path, oslFileStatus* pStat) + { + OString link_target; + if (!osl::realpath(file_path, link_target)) + return oslTranslateFileError(errno); + + OUString url; + oslFileError osl_error = osl::detail::convertPathnameToUrl(link_target, &url); + if (osl_error != osl_File_E_None) + return osl_error; + rtl_uString_assign(&pStat->ustrLinkTargetURL, url.pData); + + pStat->uValidFields |= osl_FileStatus_Mask_LinkTargetURL; + return osl_File_E_None; + } + + oslFileError setup_osl_getFileStatus( + DirectoryItem_Impl * pImpl, oslFileStatus* pStat, OString& file_path) + { + if ((pImpl == nullptr) || (pStat == nullptr)) + return osl_File_E_INVAL; + + file_path = pImpl->m_strFilePath; + OSL_ASSERT(!file_path.isEmpty()); + if (file_path.isEmpty()) + return osl_File_E_INVAL; + + pStat->uValidFields = 0; + return osl_File_E_None; + } + +} + +oslFileError SAL_CALL osl_getFileStatus(oslDirectoryItem Item, oslFileStatus* pStat, sal_uInt32 uFieldMask) +{ + DirectoryItem_Impl * pImpl = static_cast< DirectoryItem_Impl* >(Item); + + OString file_path; + oslFileError osl_error = setup_osl_getFileStatus(pImpl, pStat, file_path); + if (osl_error != osl_File_E_None) + return osl_error; + + struct stat file_stat; + + bool bStatNeeded = is_stat_call_necessary(uFieldMask, pImpl->getFileType()); + if (bStatNeeded && (osl::lstat(file_path, file_stat) != 0)) + return oslTranslateFileError(errno); + + if (bStatNeeded) + { + // we set all these attributes because it's cheap + set_file_type(file_stat, pStat); + set_file_access_time(file_stat, pStat); + set_file_modify_time(file_stat, pStat); + set_file_size(file_stat, pStat); + set_file_attributes(file_path, file_stat, uFieldMask, pStat); + + // file exists semantic of osl_FileStatus_Mask_Validate + if ((uFieldMask & osl_FileStatus_Mask_LinkTargetURL) && S_ISLNK(file_stat.st_mode)) + { + osl_error = set_link_target_url(file_path, pStat); + if (osl_error != osl_File_E_None) + return osl_error; + } + } +#ifdef _DIRENT_HAVE_D_TYPE + else if (uFieldMask & osl_FileStatus_Mask_Type) + { + pStat->eType = pImpl->getFileType(); + pStat->uValidFields |= osl_FileStatus_Mask_Type; + } +#endif /* _DIRENT_HAVE_D_TYPE */ + + if (uFieldMask & osl_FileStatus_Mask_FileURL) + { + OUString url; + if ((osl_error = osl::detail::convertPathnameToUrl(file_path, &url)) != osl_File_E_None) + return osl_error; + rtl_uString_assign(&pStat->ustrFileURL, url.pData); + + pStat->uValidFields |= osl_FileStatus_Mask_FileURL; + } + + if (uFieldMask & osl_FileStatus_Mask_FileName) + { + OString name; + osl_systemPathGetFileNameOrLastDirectoryPart(file_path.pData, &name.pData); + bool ok = rtl_convertStringToUString( + &pStat->ustrFileName, name.getStr(), name.getLength(), osl_getThreadTextEncoding(), + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT + | RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT)); + assert(ok); (void)ok; + pStat->uValidFields |= osl_FileStatus_Mask_FileName; + } + return osl_File_E_None; +} + +static oslFileError osl_psz_setFileAttributes( const char* pszFilePath, sal_uInt64 uAttributes ) +{ + oslFileError osl_error = osl_File_E_None; + mode_t nNewMode = 0; + + OSL_ENSURE(!(osl_File_Attribute_Hidden & uAttributes), "osl_File_Attribute_Hidden doesn't work under Unix"); + + if (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 0000000000..be98df95f1 --- /dev/null +++ b/sal/osl/unx/file_url.cxx @@ -0,0 +1,965 @@ +/* -*- 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 <algorithm> +#include <cassert> +#include <cstring> +#include <stdexcept> +#include <string_view> +#include <limits.h> +#include <errno.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.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(OUStringChar(aHex[c >> 4]) + OUStringChar(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 0000000000..3ffd9e06ba --- /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 0000000000..e20b8a27d0 --- /dev/null +++ b/sal/osl/unx/file_volume.cxx @@ -0,0 +1,330 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/file.h> + +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include "file_error_transl.hxx" +#include "file_url.hxx" +#include "system.hxx" + +#include <errno.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_STATFS_H +#undef HAVE_STATFS_H +#endif + +#if defined(LINUX) && defined(__FreeBSD_kernel__) +#undef LINUX +#define FREEBSD 1 +#endif + +#if defined(__sun) + +#include <sys/mnttab.h> +#include <sys/statvfs.h> +#define HAVE_STATFS_H + +#elif defined(LINUX) +#include <sys/statfs.h> +#define HAVE_STATFS_H + +#elif defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD) || defined(DRAGONFLY) + +#include <sys/param.h> +#include <sys/ucred.h> +#include <sys/mount.h> +#define HAVE_STATFS_H + +#elif defined(MACOSX) + +#include <sys/param.h> +#include <sys/mount.h> +#define HAVE_STATFS_H + +#endif /* HAVE_STATFS_H */ + +/************************************************************************ + * ToDo + * + * - Fix: check for corresponding struct sizes in exported functions + * - check size/use of oslVolumeInfo + ***********************************************************************/ + +/****************************************************************************** + * + * C-String Function Declarations + * + *****************************************************************************/ + +static oslFileError osl_psz_getVolumeInformation(const char* , oslVolumeInfo* pInfo, sal_uInt32 uFieldMask); + +/****************************************************************************/ +/* osl_getVolumeInformation */ +/****************************************************************************/ + +oslFileError osl_getVolumeInformation( rtl_uString* ustrDirectoryURL, oslVolumeInfo* pInfo, sal_uInt32 uFieldMask ) +{ + char path[PATH_MAX]; + oslFileError eRet; + + OSL_ASSERT( ustrDirectoryURL ); + OSL_ASSERT( pInfo ); + + /* convert directory url to system path */ + eRet = FileURLToPath( path, PATH_MAX, ustrDirectoryURL ); + if( eRet != osl_File_E_None ) + return eRet; + +#ifdef MACOSX + if ( macxp_resolveAlias( path, PATH_MAX ) != 0 ) + return oslTranslateFileError( errno ); +#endif/* MACOSX */ + + return osl_psz_getVolumeInformation( path, pInfo, uFieldMask); +} + +/****************************************************************************** + * + * C-String Versions of Exported Module Functions + * + *****************************************************************************/ + +#ifdef HAVE_STATFS_H + +#if defined(FREEBSD) || defined(MACOSX) || defined(OPENBSD) || defined(DRAGONFLY) +# define OSL_detail_STATFS_STRUCT struct statfs +# define OSL_detail_STATFS(dir, sfs) statfs((dir), (sfs)) +# define OSL_detail_STATFS_BLKSIZ(a) (static_cast<sal_uInt64>((a).f_bsize)) +# define OSL_detail_STATFS_TYPENAME(a) ((a).f_fstypename) +#if defined(OPENBSD) +# define OSL_detail_STATFS_ISREMOTE(a) (rtl_str_compare((a).f_fstypename, "nfs") == 0) +#else +# define OSL_detail_STATFS_ISREMOTE(a) (((a).f_type & MNT_LOCAL) == 0) +#endif + +/* always return true if queried for the properties of + the file system. If you think this is wrong under any + of the target platforms fix it!!!! */ +# define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) (true) +# define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true) +#endif /* FREEBSD || MACOSX || OPENBSD */ + +#if defined(NETBSD) + +# define OSL_detail_STATFS_STRUCT struct statvfs +# define OSL_detail_STATFS(dir, sfs) statvfs((dir), (sfs)) +# define OSL_detail_STATFS_ISREMOTE(a) (((a).f_flag & ST_LOCAL) == 0) + +# define OSL_detail_STATFS_BLKSIZ(a) ((sal_uInt64)((a).f_bsize)) +# define OSL_detail_STATFS_TYPENAME(a) ((a).f_fstypename) + +# define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) (strcmp((a).f_fstypename, "msdos") != 0 && strcmp((a).f_fstypename, "ntfs") != 0 && strcmp((a).f_fstypename, "smbfs") != 0) +# define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (strcmp((a).f_fstypename, "msdos") != 0) +#endif /* NETBSD */ + +#if defined(LINUX) +# define OSL_detail_NFS_SUPER_MAGIC 0x6969 +# define OSL_detail_SMB_SUPER_MAGIC 0x517B +# define OSL_detail_MSDOS_SUPER_MAGIC 0x4d44 +# define OSL_detail_NTFS_SUPER_MAGIC 0x5346544e +# define OSL_detail_STATFS_STRUCT struct statfs +# define OSL_detail_STATFS(dir, sfs) statfs((dir), (sfs)) +# define OSL_detail_STATFS_BLKSIZ(a) (static_cast<sal_uInt64>((a).f_bsize)) +# define OSL_detail_STATFS_IS_NFS(a) (OSL_detail_NFS_SUPER_MAGIC == (a).f_type) +# define OSL_detail_STATFS_IS_SMB(a) (OSL_detail_SMB_SUPER_MAGIC == (a).f_type) +# define OSL_detail_STATFS_ISREMOTE(a) (OSL_detail_STATFS_IS_NFS((a)) || OSL_detail_STATFS_IS_SMB((a))) +# define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) ((OSL_detail_MSDOS_SUPER_MAGIC != (a).f_type) && (OSL_detail_NTFS_SUPER_MAGIC != (a).f_type)) +# define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) ((OSL_detail_MSDOS_SUPER_MAGIC != (a).f_type)) +#endif /* LINUX */ + +#if defined(__sun) +# define OSL_detail_STATFS_STRUCT struct statvfs +# define OSL_detail_STATFS(dir, sfs) statvfs((dir), (sfs)) +# define OSL_detail_STATFS_BLKSIZ(a) ((sal_uInt64)((a).f_frsize)) +# define OSL_detail_STATFS_TYPENAME(a) ((a).f_basetype) +# define OSL_detail_STATFS_ISREMOTE(a) (rtl_str_compare((a).f_basetype, "nfs") == 0) + +/* always return true if queried for the properties of + the file system. If you think this is wrong under any + of the target platforms fix it!!!! */ +# define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) (true) +# define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true) +#endif /* __sun */ + +# define OSL_detail_STATFS_INIT(a) (memset(&(a), 0, sizeof(OSL_detail_STATFS_STRUCT))) + +#else /* no statfs available */ + +# define OSL_detail_STATFS_STRUCT struct dummy {int i;} +# define OSL_detail_STATFS_INIT(a) ((void)a) +# define OSL_detail_STATFS(dir, sfs) (1) +# define OSL_detail_STATFS_ISREMOTE(sfs) (false) +# define OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(a) (true) +# define OSL_detail_STATFS_IS_CASE_PRESERVING_FS(a) (true) +#endif /* HAVE_STATFS_H */ + +static oslFileError osl_psz_getVolumeInformation ( + const char* pszDirectory, oslVolumeInfo* pInfo, sal_uInt32 uFieldMask) +{ + if (!pInfo) + return osl_File_E_INVAL; + + pInfo->uValidFields = 0; + pInfo->uAttributes = 0; + pInfo->uTotalSpace = 0; + pInfo->uFreeSpace = 0; + pInfo->uUsedSpace = 0; + + if ((uFieldMask + & (osl_VolumeInfo_Mask_Attributes | osl_VolumeInfo_Mask_TotalSpace + | osl_VolumeInfo_Mask_UsedSpace | osl_VolumeInfo_Mask_FreeSpace + | osl_VolumeInfo_Mask_FileSystemName + | osl_VolumeInfo_Mask_FileSystemCaseHandling)) + != 0) + { + OSL_detail_STATFS_STRUCT sfs; + OSL_detail_STATFS_INIT(sfs); + // coverity[fs_check_call : FALSE] + if ((OSL_detail_STATFS(pszDirectory, &sfs)) < (0)) + { + oslFileError result = oslTranslateFileError(errno); + return result; + } + + /* FIXME: how to detect the kind of storage (fixed, cdrom, ...) */ + if (uFieldMask & osl_VolumeInfo_Mask_Attributes) + { + bool const remote = OSL_detail_STATFS_ISREMOTE(sfs); + // extracted from the 'if' to avoid Clang -Wunreachable-code + if (remote) + pInfo->uAttributes |= osl_Volume_Attribute_Remote; + + pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes; + } + + if (uFieldMask & osl_VolumeInfo_Mask_FileSystemCaseHandling) + { + if (OSL_detail_STATFS_IS_CASE_SENSITIVE_FS(sfs)) + pInfo->uAttributes |= osl_Volume_Attribute_Case_Sensitive; + + if (OSL_detail_STATFS_IS_CASE_PRESERVING_FS(sfs)) + pInfo->uAttributes |= osl_Volume_Attribute_Case_Is_Preserved; + + pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes; + } + +#if defined(OSL_detail_STATFS_BLKSIZ) + + if ((uFieldMask & osl_VolumeInfo_Mask_TotalSpace) || + (uFieldMask & osl_VolumeInfo_Mask_UsedSpace)) + { + pInfo->uTotalSpace = OSL_detail_STATFS_BLKSIZ(sfs); + pInfo->uTotalSpace *= static_cast<sal_uInt64>(sfs.f_blocks); + pInfo->uValidFields |= osl_VolumeInfo_Mask_TotalSpace; + } + + if ((uFieldMask & osl_VolumeInfo_Mask_FreeSpace) || + (uFieldMask & osl_VolumeInfo_Mask_UsedSpace)) + { + pInfo->uFreeSpace = OSL_detail_STATFS_BLKSIZ(sfs); + + if (getuid() == 0) + pInfo->uFreeSpace *= static_cast<sal_uInt64>(sfs.f_bfree); + else + pInfo->uFreeSpace *= static_cast<sal_uInt64>(sfs.f_bavail); + + pInfo->uValidFields |= osl_VolumeInfo_Mask_FreeSpace; + } + + if ((pInfo->uValidFields & osl_VolumeInfo_Mask_TotalSpace) && + (pInfo->uValidFields & osl_VolumeInfo_Mask_FreeSpace )) + { + pInfo->uUsedSpace = pInfo->uTotalSpace - pInfo->uFreeSpace; + pInfo->uValidFields |= osl_VolumeInfo_Mask_UsedSpace; + } + +#endif /* OSL_detail_STATFS_BLKSIZ */ + +#if defined(OSL_detail_STATFS_TYPENAME) + + if (uFieldMask & osl_VolumeInfo_Mask_FileSystemName) + { + rtl_string2UString( + &(pInfo->ustrFileSystemName), + OSL_detail_STATFS_TYPENAME(sfs), + rtl_str_getLength(OSL_detail_STATFS_TYPENAME(sfs)), + osl_getThreadTextEncoding(), + OUSTRING_TO_OSTRING_CVTFLAGS); + OSL_ASSERT(pInfo->ustrFileSystemName != nullptr); + + pInfo->uValidFields |= osl_VolumeInfo_Mask_FileSystemName; + } + +#endif /* OSL_detail_STATFS_TYPENAME */ + } + + pInfo->uMaxNameLength = 0; + if (uFieldMask & osl_VolumeInfo_Mask_MaxNameLength) + { + long nLen = pathconf(pszDirectory, _PC_NAME_MAX); + if (nLen > 0) + { + pInfo->uMaxNameLength = static_cast<sal_uInt32>(nLen); + pInfo->uValidFields |= osl_VolumeInfo_Mask_MaxNameLength; + } + } + + pInfo->uMaxPathLength = 0; + if (uFieldMask & osl_VolumeInfo_Mask_MaxPathLength) + { + long nLen = pathconf (pszDirectory, _PC_PATH_MAX); + if (nLen > 0) + { + pInfo->uMaxPathLength = static_cast<sal_uInt32>(nLen); + pInfo->uValidFields |= osl_VolumeInfo_Mask_MaxPathLength; + } + } + + return osl_File_E_None; +} + +oslFileError osl_getVolumeDeviceMountPath( oslVolumeDeviceHandle, rtl_uString ** ) +{ + return osl_File_E_INVAL; +} + +oslFileError osl_acquireVolumeDeviceHandle( oslVolumeDeviceHandle ) +{ + return osl_File_E_INVAL; +} + +oslFileError osl_releaseVolumeDeviceHandle( oslVolumeDeviceHandle ) +{ + return osl_File_E_INVAL; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sal/osl/unx/interlck.cxx b/sal/osl/unx/interlck.cxx new file mode 100644 index 0000000000..8bc63211b3 --- /dev/null +++ b/sal/osl/unx/interlck.cxx @@ -0,0 +1,93 @@ +/* -*- 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_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 0000000000..be3e41fb5e --- /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 0000000000..0da54f4758 --- /dev/null +++ b/sal/osl/unx/module.cxx @@ -0,0 +1,295 @@ +/* -*- 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/file.h> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <assert.h> +#include <dlfcn.h> +#include <limits.h> +#include "file_url.hxx" + +static bool getModulePathFromAddress(void * address, rtl_String ** path) +{ + bool result = false; +#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 + 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 0000000000..e3786e43a1 --- /dev/null +++ b/sal/osl/unx/mutex.cxx @@ -0,0 +1,162 @@ +/* -*- 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 "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 0000000000..7ba961968a --- /dev/null +++ b/sal/osl/unx/nlsupport.cxx @@ -0,0 +1,870 @@ +/* -*- 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 <cassert> +#include <cstring> + +#include <osl/nlsupport.h> +#include <osl/diagnose.h> +#include <osl/process.h> + +#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 <rtl/ustring.hxx> +#include <sal/log.hxx> +#include "system.hxx" +#endif + +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"_ostr; + } + + /* 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 0000000000..9eade33d02 --- /dev/null +++ b/sal/osl/unx/nlsupport.hxx @@ -0,0 +1,41 @@ +/* -*- 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> +#include <rtl/ustring.hxx> + +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 0000000000..f82ea1436c --- /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 0000000000..4dfd75ddf6 --- /dev/null +++ b/sal/osl/unx/pipe.cxx @@ -0,0 +1,524 @@ +/* -*- 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 <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> +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +constexpr OString PIPEDEFAULTPATH = "/tmp"_ostr; +constexpr OString PIPEALTERNATEPATH = "/var/tmp"_ostr; + +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 ""_ostr; +} + +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 0000000000..cebdc6f35f --- /dev/null +++ b/sal/osl/unx/process.cxx @@ -0,0 +1,1203 @@ +/* -*- 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> +#include <fcntl.h> +#include <limits.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +/* + * 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 + +#ifdef IOS +#include <signal.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 <sal/log.hxx> + +#include "createfilehandlefromfd.hxx" +#include "file_url.hxx" +#include "readwrite_helper.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; + + // coverity[lock_order : FALSE] - incorrect report of lock order error + 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; + + // coverity[lock_order : FALSE] - incorrect report of lock order error + if (osl_checkCondition(pChild->m_terminated)) + { + // coverity[lock_order : FALSE] - incorrect report of lock order error + 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 0000000000..0e3f3e6d8e --- /dev/null +++ b/sal/osl/unx/process_impl.cxx @@ -0,0 +1,479 @@ +/* -*- 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 <unistd.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 <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 EMSCRIPTEN + // Just return some dummy file: URL for now to see what happens + OUString fileURL = "vnd.sun.star.pathname:/instdir/program/soffice"; + rtl_uString_acquire(fileURL.pData); + *ppFileURL = fileURL.pData; + return osl_Process_E_None; +#else +#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 +} + +} + +#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) + { + rtl_uString * pSearchPath = nullptr; + osl_getEnvironment (u"PATH"_ustr.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 0000000000..1e7512a24d --- /dev/null +++ b/sal/osl/unx/profile.cxx @@ -0,0 +1,1869 @@ +/* -*- 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 <sal/log.hxx> + +#include <fcntl.h> +#include <limits.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#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 0000000000..e8379f8f0b --- /dev/null +++ b/sal/osl/unx/random.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <oslrandom.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.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 0000000000..f28fb16cd0 --- /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 <errno.h> +#include <limits> +#include <unistd.h> + +#include "readwrite_helper.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 0000000000..133ccd016f --- /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 0000000000..fb6922a2e3 --- /dev/null +++ b/sal/osl/unx/salinit.cxx @@ -0,0 +1,97 @@ +/* -*- 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 "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 0000000000..03e8047e0a --- /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 0000000000..165efafaa1 --- /dev/null +++ b/sal/osl/unx/secimpl.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_SECIMPL_HXX +#define INCLUDED_SAL_OSL_UNX_SECIMPL_HXX + +#include <osl/security.h> + +#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 0000000000..733aa0dfc6 --- /dev/null +++ b/sal/osl/unx/security.cxx @@ -0,0 +1,538 @@ +/* -*- 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> +#include <sys/stat.h> +#include <unistd.h> + +#ifdef IOS +#include <premac.h> +#import <Foundation/Foundation.h> +#include <postmac.h> +#endif + +#include <o3tl/safeint.hxx> +#include <osl/security.h> +#include <rtl/string.hxx> +#include <sal/log.hxx> + +#include <osl/thread.h> +#include <osl/file.h> + +#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)) +#include <rtl/bootstrap.hxx> +#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)); + if (p != nullptr) { + // coverity[overrun-buffer-arg] - theoretically massive n is not due to + // a negative parameter being interpreted as unsigned + 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 0000000000..50c260f9d5 --- /dev/null +++ b/sal/osl/unx/signal.cxx @@ -0,0 +1,450 @@ +/* -*- 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" + +#include "backtrace.h" + +#define MAX_STACK_FRAMES 256 + +#include <osl/diagnose.h> +#include <osl/signal.h> +#include <sal/log.hxx> +#include <sal/macros.h> + +#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 + +#include <signal.h> +#include <unistd.h> + +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 0000000000..e875e415e7 --- /dev/null +++ b/sal/osl/unx/socket.cxx @@ -0,0 +1,2049 @@ +/* -*- 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 <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 <arpa/inet.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <poll.h> +#include <unistd.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 0000000000..dc354db94a --- /dev/null +++ b/sal/osl/unx/sockimpl.hxx @@ -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 . + */ + +#ifndef INCLUDED_SAL_OSL_UNX_SOCKIMPL_HXX +#define INCLUDED_SAL_OSL_UNX_SOCKIMPL_HXX + +#include <osl/interlck.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#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 0000000000..bcead388ba --- /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 0000000000..da1b684af4 --- /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 0000000000..f19bd70531 --- /dev/null +++ b/sal/osl/unx/system.cxx @@ -0,0 +1,164 @@ +/* -*- 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" + +#ifdef NO_PTHREAD_RTL + +#if defined(MACOSX) + +#include <config_features.h> + +#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 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 0000000000..5f5a16aa36 --- /dev/null +++ b/sal/osl/unx/system.hxx @@ -0,0 +1,358 @@ +/* -*- 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 <errno.h> +#include <grp.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 + +#ifdef SYSV +# include <sys/utsname.h> +#endif + +#ifdef LINUX +# ifndef __USE_GNU +# define __USE_GNU +# endif + +# include <pthread.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 __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(__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 + +/* 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 0000000000..7495a3e730 --- /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 0000000000..85259f1667 --- /dev/null +++ b/sal/osl/unx/tempfile.cxx @@ -0,0 +1,338 @@ +/* -*- 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 <stdlib.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> +#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 0000000000..b17f363511 --- /dev/null +++ b/sal/osl/unx/thread.cxx @@ -0,0 +1,1042 @@ +/* -*- 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 <functional> +#include <mutex> +#include <unistd.h> + +#include "system.hxx" +#include "unixerrnostring.hxx" +#include <thread_internal.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 <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; +}; +#define OSL_THREAD_PRIORITY_INITIALIZER { 127, 96, 64, 32, 0 } +#endif + +} + +#if !defined NO_PTHREAD_PRIORITY + +namespace { + +struct osl_thread_global_st +{ + pthread_once_t m_once; + struct osl_thread_priority_st m_priority; +}; + +} + +static struct osl_thread_global_st g_thread = +{ + PTHREAD_ONCE_INIT, + OSL_THREAD_PRIORITY_INITIALIZER +}; + +#endif // !defined NO_PTHREAD_PRIORITY + +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); + +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; +} + +#ifndef NO_PTHREAD_PRIORITY +/***************************************************************************** + @@@ 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() +{ + 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_priority_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_priority_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; +} + +rtl_TextEncoding getThreadTextEncodingForInitialization() +{ + /* determine default text encoding */ + rtl_TextEncoding 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; + } + + return defaultEncoding; +} + +/* 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 0000000000..cf5473ff24 --- /dev/null +++ b/sal/osl/unx/time.cxx @@ -0,0 +1,311 @@ +/* -*- 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 <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 + + // coverity[store_truncates_time_t] - TODO: sal_uInt32 TimeValue::Seconds is only large + // enough for integer time_t values < 2^32 representing dates until year 2106: + 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 0000000000..9e13b04e6c --- /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 0000000000..033b1a435a --- /dev/null +++ b/sal/osl/unx/uunxapi.cxx @@ -0,0 +1,910 @@ +/* -*- 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> + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <utime.h> + +#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 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 0000000000..d4b73a7fbd --- /dev/null +++ b/sal/osl/unx/uunxapi.hxx @@ -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/. + * + * 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 <sys/types.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 0000000000..5a7e17f344 --- /dev/null +++ b/sal/osl/unx/uunxapi.mm @@ -0,0 +1 @@ +#include "uunxapi.cxx" |