diff options
Diffstat (limited to 'sal/osl/unx/pipe.cxx')
-rw-r--r-- | sal/osl/unx/pipe.cxx | 524 |
1 files changed, 524 insertions, 0 deletions
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: */ |