diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/platform | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/platform')
148 files changed, 15759 insertions, 0 deletions
diff --git a/xbmc/platform/CMakeLists.txt b/xbmc/platform/CMakeLists.txt new file mode 100644 index 0000000..4df5326 --- /dev/null +++ b/xbmc/platform/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES Environment.cpp + xbmc.cpp) + +set(HEADERS Environment.h + Filesystem.h + MessagePrinter.h + Platform.h + xbmc.h) + +core_add_library(platform_common) diff --git a/xbmc/platform/Environment.cpp b/xbmc/platform/Environment.cpp new file mode 100644 index 0000000..4655dcc --- /dev/null +++ b/xbmc/platform/Environment.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +/** + * \file platform\Environment.cpp + * \brief Implements CEnvironment class functions. + * + * Some ideas were inspired by PostgreSQL's pgwin32_putenv function. + * Refined, updated, enhanced and modified for XBMC by Karlson2k. + */ + +#include "Environment.h" + +#include <stdlib.h> + + +// --------------------- Main Functions --------------------- + +int CEnvironment::setenv(const std::string &name, const std::string &value, int overwrite /*= 1*/) +{ +#ifdef TARGET_WINDOWS + return (win_setenv(name, value, overwrite ? autoDetect : addOnly) == 0) ? 0 : -1; +#else + if (value.empty() && overwrite != 0) + return ::unsetenv(name.c_str()); + return ::setenv(name.c_str(), value.c_str(), overwrite); +#endif +} + +std::string CEnvironment::getenv(const std::string &name) +{ +#ifdef TARGET_WINDOWS + return win_getenv(name); +#else + char * str = ::getenv(name.c_str()); + if (str == NULL) + return ""; + return str; +#endif +} + +int CEnvironment::unsetenv(const std::string &name) +{ +#ifdef TARGET_WINDOWS + return (win_setenv(name, "", deleteVariable)) == 0 ? 0 : -1; +#else + return ::unsetenv(name.c_str()); +#endif +} + +int CEnvironment::putenv(const std::string &envstring) +{ + if (envstring.empty()) + return 0; + size_t pos = envstring.find('='); + if (pos == 0) // '=' is the first character + return -1; + if (pos == std::string::npos) + return unsetenv(envstring); + if (pos == envstring.length()-1) // '=' is in last position + { + std::string name(envstring); + name.erase(name.length()-1, 1); + return unsetenv(name); + } + std::string name(envstring, 0, pos), value(envstring, pos+1); + + return setenv(name, value); +} + diff --git a/xbmc/platform/Environment.h b/xbmc/platform/Environment.h new file mode 100644 index 0000000..a05974c --- /dev/null +++ b/xbmc/platform/Environment.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/** + * \file utils\Environment.h + * \brief Declares CEnvironment class for platform-independent environment variables manipulations. + * + */ +#include <string> + +/** + * @class CEnvironment + * + * @brief Platform-independent environment variables manipulations. + * + * Provide analog for POSIX functions: + * + setenv + * + unsetenv + * + putenv + * + getenv + * + * You can generally use the functions as you would normally in POSIX-style. + * The differences below are just to make things more convenient through use of std::string (2,3), + * and to also allow the Win32-style of unsetting variables (4,5) if wanted. + * 1. CEnvironment::setenv parameter 'overwrite' is optional, set by default to 1 (allow overwrite). + * 2. CEnvironment::putenv uses copy of provided string (rather than string itself) to change environment, + * so you can free parameter variable right after call of function. + * 3. CEnvironment::getenv returns a copy of environment variable value instead of pointer to value. + * 4. CEnvironment::setenv can be used to unset variables. Just pass empty string for 'value' parameter. + * 5. CEnvironment::putenv can be used to unset variables. Set parameter to 'var=' (Windows style) or + * just 'var' (POSIX style), and 'var' will be unset. + * + * All 'std::string' types are supposed to be in UTF-8 encoding. + * All functions work on all platforms. Special care is taken on Windows platform where Environment is changed for process itself, + * for process runtime library and for all runtime libraries (MSVCRT) loaded by third-party modules. + * Functions internally make all necessary UTF-8 <-> wide conversions.* + */ + +class CEnvironment +{ +public: + /** + * \fn static int CEnvironment::setenv(const std::string &name, const std::string &value, + * int overwrite = 1); + * \brief Sets or unsets environment variable. + * \param name The environment variable name to add/modify/delete. + * \param value The environment variable new value. If set to empty string, variable will be + * deleted from the environment. + * \param overwrite (optional) If set to non-zero, existing variable will be overwritten. If set to zero and + * variable is already present, then variable will be unchanged and function returns success. + * \return Zero on success, non-zero on error. + */ + static int setenv(const std::string &name, const std::string &value, int overwrite = 1); + /** + * \fn static int CEnvironment::unsetenv(const std::string &name); + * \brief Deletes environment variable. + * \param name The environment variable name to delete. + * \return Zero on success, non-zero on error. + */ + static int unsetenv(const std::string &name); + + /** + * \fn static int CEnvironment::putenv(const std::string &envstring); + * \brief Adds/modifies/deletes environment variable. + * \param envstring The variable-value string in form 'var=value'. If set to 'var=' or 'var', then variable + * will be deleted from the environment. + * \return Zero on success, non-zero on error. + */ + static int putenv(const std::string &envstring); + /** + * \fn static std::string CEnvironment::getenv(const std::string &name); + * \brief Gets value of environment variable in UTF-8 encoding. + * \param name The name of environment variable. + * \return Copy of of environment variable value or empty string if variable in not present in environment. + * \sa xbmc_getenvUtf8, xbmc_getenvW + */ + static std::string getenv(const std::string &name); +private: +#ifdef TARGET_WINDOWS + enum updateAction:int + { + addOrUpdateOnly = -2, + deleteVariable = -1, + addOnly = 0, + autoDetect = 1 + }; + static int win_setenv(const std::string &name, const std::string &value = "", updateAction action = autoDetect); + static std::string win_getenv(const std::string &name); +#endif // TARGET_WINDOWS +}; + diff --git a/xbmc/platform/Filesystem.h b/xbmc/platform/Filesystem.h new file mode 100644 index 0000000..732be92 --- /dev/null +++ b/xbmc/platform/Filesystem.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <string> +#include <system_error> +namespace KODI +{ +namespace PLATFORM +{ +namespace FILESYSTEM +{ +struct space_info { + std::uintmax_t capacity; + std::uintmax_t free; + std::uintmax_t available; +}; + +space_info space(const std::string &path, std::error_code &ec); + +std::string temp_directory_path(std::error_code &ec); +std::string create_temp_directory(std::error_code &ec); +std::string temp_file_path(const std::string& suffix, std::error_code& ec); +} +} +} diff --git a/xbmc/platform/MessagePrinter.h b/xbmc/platform/MessagePrinter.h new file mode 100644 index 0000000..f0aa08e --- /dev/null +++ b/xbmc/platform/MessagePrinter.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> +#include <utility> +#include <vector> + +class CMessagePrinter +{ +public: + + /*! \brief Display a normal message to the user during startup + * + * \param[in] message message to display + */ + static void DisplayMessage(const std::string& message); + + /*! \brief Display a warning message to the user during startup + * + * \param[in] warning warning to display + */ + static void DisplayWarning(const std::string& warning); + + /*! \brief Display an error message to the user during startup + * + * \param[in] error error to display + */ + static void DisplayError(const std::string& error); + + /*! \brief Display the help message with command line options available + * + * \param[in] help List of commands and explanations, + help.push_back(std::make_pair("--help", "this displays the help)) + */ + static void DisplayHelpMessage(const std::vector<std::pair<std::string, std::string>>& help); +}; diff --git a/xbmc/platform/Platform.h b/xbmc/platform/Platform.h new file mode 100644 index 0000000..ebb441c --- /dev/null +++ b/xbmc/platform/Platform.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/ComponentContainer.h" + +//! \brief Base class for services. +class IPlatformService +{ +public: + virtual ~IPlatformService() = default; +}; + +/**\brief Class for the Platform object + * + * Contains methods to retrieve platform specific information + * and methods for doing platform specific environment preparation/initialisation + */ +class CPlatform : public CComponentContainer<IPlatformService> +{ +public: + /**\brief Creates the Platform object + * + *@return the platform object + */ + static CPlatform *CreateInstance(); + + /**\brief C'tor */ + CPlatform() = default; + + /**\brief D'tor */ + virtual ~CPlatform() = default; + + /**\brief Called at an early stage of application startup + * + * This method can be used to do platform specific environment preparation + * or initialisation (like setting environment variables for example) + */ + virtual bool InitStageOne() { return true; } + + /**\brief Called at a middle stage of application startup + * + * This method can be used for starting platform specific services that + * do not depend on windowing/gui. (eg macos XBMCHelper) + */ + virtual bool InitStageTwo() { return true; } + + /**\brief Called at a late stage of application startup + * + * This method can be used for starting platform specific Window/GUI related + * services/components. (eg , WS-Discovery Daemons) + */ + virtual bool InitStageThree() { return true; } + + /**\brief Called at a late stage of application shutdown + * + * This method should be used to cleanup resources allocated in InitStageOne + */ + virtual void DeinitStageOne() {} + + /**\brief Called at a middle stage of application shutdown + * + * This method should be used to cleanup resources allocated in InitStageTwo + */ + virtual void DeinitStageTwo() {} + + /**\brief Called at an early stage of application shutdown + * + * This method should be used to cleanup resources allocated in InitStageThree + */ + virtual void DeinitStageThree() {} + + /**\brief Flag whether disabled add-ons - installed via packagemanager or manually - should be + * offered for configuration and activation on kodi startup for this platform + */ + virtual bool IsConfigureAddonsAtStartupEnabled() { return false; } + + /**\brief Flag whether this platform supports user installation of binary add-ons. + */ + virtual bool SupportsUserInstalledBinaryAddons() { return true; } + + /**\brief Print platform specific info to log + * + * Logs platform specific system info during application creation startup + */ + virtual void PlatformSyslog() {} + + /**\brief Get a platform service instance. + */ + template<class T> + std::shared_ptr<T> GetService() + { + return this->GetComponent<T>(); + } +}; diff --git a/xbmc/platform/common/speech/CMakeLists.txt b/xbmc/platform/common/speech/CMakeLists.txt new file mode 100644 index 0000000..ac9752b --- /dev/null +++ b/xbmc/platform/common/speech/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES SpeechRecognitionStub.cpp) + +set(HEADERS SpeechRecognitionStub.h) + +core_add_library(platform_common_speech) diff --git a/xbmc/platform/common/speech/SpeechRecognitionStub.cpp b/xbmc/platform/common/speech/SpeechRecognitionStub.cpp new file mode 100644 index 0000000..2f76863 --- /dev/null +++ b/xbmc/platform/common/speech/SpeechRecognitionStub.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2012-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SpeechRecognitionStub.h" + +std::shared_ptr<speech::ISpeechRecognition> speech::ISpeechRecognition::CreateInstance() +{ + // speech recognition not implemented + return {}; +} diff --git a/xbmc/platform/common/speech/SpeechRecognitionStub.h b/xbmc/platform/common/speech/SpeechRecognitionStub.h new file mode 100644 index 0000000..ec44bc5 --- /dev/null +++ b/xbmc/platform/common/speech/SpeechRecognitionStub.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "speech/ISpeechRecognition.h" + +// This is a stub for all platforms without a speech recognition implementation +// Saves us from feature/platform ifdeffery. +class CSpeechRecognitionStub : public speech::ISpeechRecognition +{ +public: + void StartSpeechRecognition( + const std::shared_ptr<speech::ISpeechRecognitionListener>& listener) override + { + } +}; diff --git a/xbmc/platform/freebsd/CMakeLists.txt b/xbmc/platform/freebsd/CMakeLists.txt new file mode 100644 index 0000000..9b8782f --- /dev/null +++ b/xbmc/platform/freebsd/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SOURCES ../linux/AppParamParserLinux.cpp + CPUInfoFreebsd.cpp + OptionalsReg.cpp + ../linux/OptionalsReg.cpp + ../linux/TimeUtils.cpp + MemUtils.cpp + PlatformFreebsd.cpp) + +set(HEADERS ../linux/AppParamParserLinux.cpp + CPUInfoFreebsd.h + OptionalsReg.h + ../linux/OptionalsReg.h + ../linux/TimeUtils.h + PlatformFreebsd.h) + +if(ALSA_FOUND) + list(APPEND SOURCES ../linux/FDEventMonitor.cpp) + list(APPEND HEADERS ../linux/FDEventMonitor.h) +endif() + +if(DBUS_FOUND) + list(APPEND SOURCES ../linux/DBusMessage.cpp + ../linux/DBusUtil.cpp) + list(APPEND HEADERS ../linux/DBusMessage.h + ../linux/DBusUtil.h) +endif() + +core_add_library(freebsdsupport) diff --git a/xbmc/platform/freebsd/CPUInfoFreebsd.cpp b/xbmc/platform/freebsd/CPUInfoFreebsd.cpp new file mode 100644 index 0000000..5128a72 --- /dev/null +++ b/xbmc/platform/freebsd/CPUInfoFreebsd.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CPUInfoFreebsd.h" + +#include "utils/Temperature.h" +#include "utils/log.h" + +#include <array> +#include <vector> + +// clang-format off +/* sys/types.h must be included early, esp. before sysy/systl.h, otherwise: + /usr/include/sys/sysctl.h:1117:25: error: unknown type name 'u_int' */ + +#include <sys/types.h> +// clang-format on + +#if defined(__i386__) || defined(__x86_64__) +#include <cpuid.h> +#elif __has_include(<sys/auxv.h>) +#include <sys/auxv.h> +#endif + +#include <sys/resource.h> +#include <sys/sysctl.h> + +namespace +{ + +struct CpuData +{ +public: + std::size_t GetActiveTime() const { return state[CP_USER] + state[CP_NICE] + state[CP_SYS]; } + + std::size_t GetIdleTime() const { return state[CP_INTR] + state[CP_IDLE]; } + + std::size_t GetTotalTime() const { return GetActiveTime() + GetIdleTime(); } + + std::size_t state[CPUSTATES]; +}; + +} // namespace + +std::shared_ptr<CCPUInfo> CCPUInfo::GetCPUInfo() +{ + return std::make_shared<CCPUInfoFreebsd>(); +} + +CCPUInfoFreebsd::CCPUInfoFreebsd() +{ + int count = 0; + size_t countLength = sizeof(count); + if (sysctlbyname("hw.ncpu", &count, &countLength, nullptr, 0) == 0) + m_cpuCount = count; + else + m_cpuCount = 1; + + std::array<char, 512> cpuModel; + size_t length = cpuModel.size(); + if (sysctlbyname("hw.model", cpuModel.data(), &length, nullptr, 0) == 0) + m_cpuModel = cpuModel.data(); + + for (int i = 0; i < m_cpuCount; i++) + { + CoreInfo core; + core.m_id = i; + m_cores.emplace_back(core); + } +#if defined(__i386__) || defined(__x86_64__) + uint32_t eax, ebx, ecx, edx; + + m_cpuVendor.clear(); + + if (__get_cpuid(CPUID_INFOTYPE_MANUFACTURER, &eax, &ebx, &ecx, &edx)) + { + m_cpuVendor.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuVendor.append(reinterpret_cast<const char*>(&edx), 4); + m_cpuVendor.append(reinterpret_cast<const char*>(&ecx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &ebx, &ecx, &edx)) + { + if (eax >= CPUID_INFOTYPE_PROCESSOR_3) + { + m_cpuModel.clear(); + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_1, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_2, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_3, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + } + } + + m_cpuModel = m_cpuModel.substr(0, m_cpuModel.find(char(0))); // remove extra null terminations + + if (__get_cpuid(CPUID_INFOTYPE_STANDARD, &eax, &eax, &ecx, &edx)) + { + if (edx & CPUID_00000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + + // Set MMX2 when SSE is present as SSE is a superset of MMX2 and Intel doesn't set the MMX2 cap + if (edx & CPUID_00000001_EDX_SSE) + m_cpuFeatures |= (CPU_FEATURE_SSE | CPU_FEATURE_MMX2); + + if (edx & CPUID_00000001_EDX_SSE2) + m_cpuFeatures |= CPU_FEATURE_SSE2; + + if (ecx & CPUID_00000001_ECX_SSE3) + m_cpuFeatures |= CPU_FEATURE_SSE3; + + if (ecx & CPUID_00000001_ECX_SSSE3) + m_cpuFeatures |= CPU_FEATURE_SSSE3; + + if (ecx & CPUID_00000001_ECX_SSE4) + m_cpuFeatures |= CPU_FEATURE_SSE4; + + if (ecx & CPUID_00000001_ECX_SSE42) + m_cpuFeatures |= CPU_FEATURE_SSE42; + } + + if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &eax, &ecx, &edx)) + { + if (eax >= CPUID_INFOTYPE_EXTENDED) + { + if (edx & CPUID_80000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + + if (edx & CPUID_80000001_EDX_MMX2) + m_cpuFeatures |= CPU_FEATURE_MMX2; + + if (edx & CPUID_80000001_EDX_3DNOW) + m_cpuFeatures |= CPU_FEATURE_3DNOW; + + if (edx & CPUID_80000001_EDX_3DNOWEXT) + m_cpuFeatures |= CPU_FEATURE_3DNOWEXT; + } + } +#endif + +#if defined(HAS_NEON) +#if defined(__ARM_NEON) + m_cpuFeatures |= CPU_FEATURE_NEON; +#elif __has_include(<sys/auxv.h>) + unsigned long hwcap = 0; + elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap)); + if (hwcap & HWCAP_NEON) + m_cpuFeatures |= CPU_FEATURE_NEON; +#endif +#endif +} + +int CCPUInfoFreebsd::GetUsedPercentage() +{ + if (!m_nextUsedReadTime.IsTimePast()) + return m_lastUsedPercentage; + + size_t len = sizeof(long); + + if (sysctlbyname("kern.cp_times", nullptr, &len, nullptr, 0) != 0) + return false; + + std::vector<long> cptimes(len); + size_t cptimesLength = cptimes.size(); + if (sysctlbyname("kern.cp_times", cptimes.data(), &cptimesLength, nullptr, 0) != 0) + return false; + + size_t activeTime{0}; + size_t idleTime{0}; + size_t totalTime{0}; + + std::vector<CpuData> cpuData; + + for (int i = 0; i < m_cpuCount; i++) + { + CpuData info; + + for (size_t state = 0; state < CPUSTATES; state++) + { + info.state[state] = cptimes[i * CPUSTATES + state]; + } + + activeTime += info.GetActiveTime(); + idleTime += info.GetIdleTime(); + totalTime += info.GetTotalTime(); + + cpuData.emplace_back(info); + } + + activeTime -= m_activeTime; + idleTime -= m_idleTime; + totalTime -= m_totalTime; + + m_activeTime += activeTime; + m_idleTime += idleTime; + m_totalTime += totalTime; + + m_lastUsedPercentage = activeTime * 100.0f / totalTime; + m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS); + + for (size_t core = 0; core < cpuData.size(); core++) + { + auto activeTime = cpuData[core].GetActiveTime() - m_cores[core].m_activeTime; + auto idleTime = cpuData[core].GetIdleTime() - m_cores[core].m_idleTime; + auto totalTime = cpuData[core].GetTotalTime() - m_cores[core].m_totalTime; + + m_cores[core].m_usagePercent = activeTime * 100.0 / totalTime; + + m_cores[core].m_activeTime += activeTime; + m_cores[core].m_idleTime += idleTime; + m_cores[core].m_totalTime += totalTime; + } + + return static_cast<int>(m_lastUsedPercentage); +} + +float CCPUInfoFreebsd::GetCPUFrequency() +{ + int hz = 0; + size_t len = sizeof(hz); + if (sysctlbyname("dev.cpu.0.freq", &hz, &len, nullptr, 0) != 0) + hz = 0; + + return static_cast<float>(hz); +} + + +bool CCPUInfoFreebsd::GetTemperature(CTemperature& temperature) +{ + if (CheckUserTemperatureCommand(temperature)) + return true; + + int value; + size_t len = sizeof(value); + + /* Temperature is in Kelvin * 10 */ + if (sysctlbyname("dev.cpu.0.temperature", &value, &len, nullptr, 0) != 0) + return false; + + temperature = CTemperature::CreateFromKelvin(static_cast<double>(value) / 10.0); + temperature.SetValid(true); + + return true; +} diff --git a/xbmc/platform/freebsd/CPUInfoFreebsd.h b/xbmc/platform/freebsd/CPUInfoFreebsd.h new file mode 100644 index 0000000..f1743ce --- /dev/null +++ b/xbmc/platform/freebsd/CPUInfoFreebsd.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Temperature.h" + +#include "platform/posix/CPUInfoPosix.h" + +class CCPUInfoFreebsd : public CCPUInfoPosix +{ +public: + CCPUInfoFreebsd(); + ~CCPUInfoFreebsd() = default; + + int GetUsedPercentage() override; + float GetCPUFrequency() override; + bool GetTemperature(CTemperature& temperature) override; +}; diff --git a/xbmc/platform/freebsd/MemUtils.cpp b/xbmc/platform/freebsd/MemUtils.cpp new file mode 100644 index 0000000..de3e42c --- /dev/null +++ b/xbmc/platform/freebsd/MemUtils.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/MemUtils.h" + +#include <array> +#include <cstdlib> +#include <cstring> +#include <stdio.h> + +#include <unistd.h> /* FreeBSD can't write standalone headers */ +#include <sys/sysctl.h> /* FreeBSD can't write standalone headers */ +#include <sys/types.h> + +namespace KODI +{ +namespace MEMORY +{ + +void* AlignedMalloc(size_t s, size_t alignTo) +{ + void* p; + posix_memalign(&p, alignTo, s); + + return p; +} + +void AlignedFree(void* p) +{ + free(p); +} + +void GetMemoryStatus(MemoryStatus* buffer) +{ + if (!buffer) + return; + + /* sysctl hw.physmem */ + size_t len = 0; + + /* physmem */ + size_t physmem = 0; + len = sizeof(physmem); + if (sysctlbyname("hw.physmem", &physmem, &len, NULL, 0) == 0) + { + buffer->totalPhys = physmem; + } + + /* pagesize */ + size_t pagesize = 0; + len = sizeof(pagesize); + if (sysctlbyname("hw.pagesize", &pagesize, &len, NULL, 0) != 0) + pagesize = 4096; + + /* mem_inactive */ + size_t mem_inactive = 0; + len = sizeof(mem_inactive); + if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &len, NULL, 0) == 0) + mem_inactive *= pagesize; + + /* mem_cache */ + size_t mem_cache = 0; + len = sizeof(mem_cache); + if (sysctlbyname("vm.stats.vm.v_cache_count", &mem_cache, &len, NULL, 0) == 0) + mem_cache *= pagesize; + + /* mem_free */ + size_t mem_free = 0; + len = sizeof(mem_free); + if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &len, NULL, 0) == 0) + mem_free *= pagesize; + + /* mem_avail = mem_inactive + mem_cache + mem_free */ + buffer->availPhys = mem_inactive + mem_cache + mem_free; +} + +} +} diff --git a/xbmc/platform/freebsd/OptionalsReg.cpp b/xbmc/platform/freebsd/OptionalsReg.cpp new file mode 100644 index 0000000..ed414f1 --- /dev/null +++ b/xbmc/platform/freebsd/OptionalsReg.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "OptionalsReg.h" + + +//----------------------------------------------------------------------------- +// OSS +//----------------------------------------------------------------------------- + +#ifdef HAS_OSS +#include "cores/AudioEngine/Sinks/AESinkOSS.h" +bool OPTIONALS::OSSRegister() +{ + CAESinkOSS::Register(); + return true; +} +#endif diff --git a/xbmc/platform/freebsd/OptionalsReg.h b/xbmc/platform/freebsd/OptionalsReg.h new file mode 100644 index 0000000..36837d0 --- /dev/null +++ b/xbmc/platform/freebsd/OptionalsReg.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2005-2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +//----------------------------------------------------------------------------- +// OSS +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +bool OSSRegister(); +} diff --git a/xbmc/platform/freebsd/PlatformFreebsd.cpp b/xbmc/platform/freebsd/PlatformFreebsd.cpp new file mode 100644 index 0000000..b65501b --- /dev/null +++ b/xbmc/platform/freebsd/PlatformFreebsd.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlatformFreebsd.h" + +#include "utils/StringUtils.h" + +#include "platform/freebsd/OptionalsReg.h" +#include "platform/linux/powermanagement/LinuxPowerSyscall.h" + +// clang-format off +#if defined(HAS_GLES) +#if defined(HAVE_WAYLAND) +#include "windowing/wayland/WinSystemWaylandEGLContextGLES.h" +#endif +#if defined(HAVE_X11) +#include "windowing/X11/WinSystemX11GLESContext.h" +#endif +#if defined(HAVE_GBM) +#include "windowing/gbm/WinSystemGbmGLESContext.h" +#endif +#endif + +#if defined(HAS_GL) +#if defined(HAVE_WAYLAND) +#include "windowing/wayland/WinSystemWaylandEGLContextGL.h" +#endif +#if defined(HAVE_X11) +#include "windowing/X11/WinSystemX11GLContext.h" +#endif +#if defined(HAVE_GBM) +#include "windowing/gbm/WinSystemGbmGLContext.h" +#endif +#endif +// clang-format on + +#include <cstdlib> + +CPlatform* CPlatform::CreateInstance() +{ + return new CPlatformFreebsd(); +} + +bool CPlatformFreebsd::InitStageOne() +{ + if (!CPlatformPosix::InitStageOne()) + return false; + + setenv("OS", "Linux", true); // for python scripts that check the OS + +#if defined(HAS_GLES) +#if defined(HAVE_WAYLAND) + KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGLES::Register(); +#endif +#if defined(HAVE_X11) + KODI::WINDOWING::X11::CWinSystemX11GLESContext::Register(); +#endif +#if defined(HAVE_GBM) + KODI::WINDOWING::GBM::CWinSystemGbmGLESContext::Register(); +#endif +#endif + +#if defined(HAS_GL) +#if defined(HAVE_WAYLAND) + KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGL::Register(); +#endif +#if defined(HAVE_X11) + KODI::WINDOWING::X11::CWinSystemX11GLContext::Register(); +#endif +#if defined(HAVE_GBM) + KODI::WINDOWING::GBM::CWinSystemGbmGLContext::Register(); +#endif +#endif + + CLinuxPowerSyscall::Register(); + + std::string envSink; + if (getenv("KODI_AE_SINK")) + envSink = getenv("KODI_AE_SINK"); + + if (StringUtils::EqualsNoCase(envSink, "ALSA")) + { + OPTIONALS::ALSARegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "PULSE")) + { + OPTIONALS::PulseAudioRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "OSS")) + { + OPTIONALS::OSSRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "SNDIO")) + { + OPTIONALS::SndioRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "ALSA+PULSE")) + { + OPTIONALS::ALSARegister(); + OPTIONALS::PulseAudioRegister(); + } + else + { + if (!OPTIONALS::PulseAudioRegister()) + { + if (!OPTIONALS::ALSARegister()) + { + if (!OPTIONALS::SndioRegister()) + { + OPTIONALS::OSSRegister(); + } + } + } + } + + m_lirc.reset(OPTIONALS::LircRegister()); + + return true; +} diff --git a/xbmc/platform/freebsd/PlatformFreebsd.h b/xbmc/platform/freebsd/PlatformFreebsd.h new file mode 100644 index 0000000..48a4151 --- /dev/null +++ b/xbmc/platform/freebsd/PlatformFreebsd.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/linux/OptionalsReg.h" +#include "platform/posix/PlatformPosix.h" + +#include <memory> + +class CPlatformFreebsd : public CPlatformPosix +{ +public: + CPlatformFreebsd() = default; + ~CPlatformFreebsd() override = default; + + bool InitStageOne() override; + +private: + std::unique_ptr<OPTIONALS::CLircContainer, OPTIONALS::delete_CLircContainer> m_lirc; +}; diff --git a/xbmc/platform/freebsd/network/CMakeLists.txt b/xbmc/platform/freebsd/network/CMakeLists.txt new file mode 100644 index 0000000..25d8ee8 --- /dev/null +++ b/xbmc/platform/freebsd/network/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCES NetworkFreebsd.cpp) +set(HEADERS NetworkFreebsd.h) + +core_add_library(platform_freebsd_network) diff --git a/xbmc/platform/freebsd/network/NetworkFreebsd.cpp b/xbmc/platform/freebsd/network/NetworkFreebsd.cpp new file mode 100644 index 0000000..94a0a41 --- /dev/null +++ b/xbmc/platform/freebsd/network/NetworkFreebsd.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "NetworkFreebsd.h" + +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <array> +#include <errno.h> + +#include <arpa/inet.h> +#include <ifaddrs.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <resolv.h> +#include <sys/sockio.h> +#include <sys/wait.h> + +CNetworkInterfaceFreebsd::CNetworkInterfaceFreebsd(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]) + : CNetworkInterfacePosix(network, interfaceName, interfaceMacAddrRaw) +{ +} + +std::string CNetworkInterfaceFreebsd::GetCurrentDefaultGateway() const +{ + std::string result; + + size_t needed; + int mib[6]; + char *buf, *next, *lim; + char line[16]; + struct rt_msghdr* rtm; + struct sockaddr* sa; + struct sockaddr_in* sockin; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + return result; + + if ((buf = (char*)malloc(needed)) == NULL) + return result; + + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) + { + free(buf); + return result; + } + + lim = buf + needed; + for (next = buf; next < lim; next += rtm->rtm_msglen) + { + rtm = (struct rt_msghdr*)next; + sa = (struct sockaddr*)(rtm + 1); + sa = (struct sockaddr*)(SA_SIZE(sa) + (char*)sa); + sockin = (struct sockaddr_in*)sa; + if (inet_ntop(AF_INET, &sockin->sin_addr.s_addr, line, sizeof(line)) == NULL) + { + free(buf); + return result; + } + result = line; + break; + } + free(buf); + + return result; +} + +bool CNetworkInterfaceFreebsd::GetHostMacAddress(unsigned long host_ip, std::string& mac) const +{ + bool ret = false; + size_t needed; + char *buf, *next; + struct rt_msghdr* rtm; + struct sockaddr_inarp* sin; + struct sockaddr_dl* sdl; + constexpr std::array<int, 6> mib = { + CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_LLINFO, + }; + + mac = ""; + + if (sysctl(mib.data(), mib.size(), nullptr, &needed, nullptr, 0) == 0) + { + buf = (char*)malloc(needed); + if (buf) + { + if (sysctl(mib.data(), mib.size(), buf, &needed, nullptr, 0) == 0) + { + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) + { + + rtm = (struct rt_msghdr*)next; + sin = (struct sockaddr_inarp*)(rtm + 1); + sdl = (struct sockaddr_dl*)(sin + 1); + + if (host_ip != sin->sin_addr.s_addr || sdl->sdl_alen < 6) + continue; + + u_char* cp = (u_char*)LLADDR(sdl); + + mac = StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", cp[0], cp[1], + cp[2], cp[3], cp[4], cp[5]); + ret = true; + break; + } + } + free(buf); + } + } + return ret; +} + +std::unique_ptr<CNetworkBase> CNetworkBase::GetNetwork() +{ + return std::make_unique<CNetworkFreebsd>(); +} + +CNetworkFreebsd::CNetworkFreebsd() : CNetworkPosix() +{ + queryInterfaceList(); +} + +void CNetworkFreebsd::GetMacAddress(const std::string& interfaceName, char rawMac[6]) +{ + memset(rawMac, 0, 6); + +#if !defined(IFT_ETHER) +#define IFT_ETHER 0x6 /* Ethernet CSMACD */ +#endif + const struct sockaddr_dl* dlAddr = NULL; + const uint8_t* base = NULL; + // Query the list of interfaces. + struct ifaddrs* list; + struct ifaddrs* interface; + + if (getifaddrs(&list) < 0) + { + return; + } + + for (interface = list; interface != NULL; interface = interface->ifa_next) + { + if (interfaceName == interface->ifa_name) + { + if ((interface->ifa_addr->sa_family == AF_LINK) && + (((const struct sockaddr_dl*)interface->ifa_addr)->sdl_type == IFT_ETHER)) + { + dlAddr = (const struct sockaddr_dl*)interface->ifa_addr; + base = (const uint8_t*)&dlAddr->sdl_data[dlAddr->sdl_nlen]; + + if (dlAddr->sdl_alen > 5) + { + memcpy(rawMac, base, 6); + } + } + break; + } + } + + freeifaddrs(list); +} + +void CNetworkFreebsd::queryInterfaceList() +{ + char macAddrRaw[6]; + m_interfaces.clear(); + + // Query the list of interfaces. + struct ifaddrs* list; + if (getifaddrs(&list) < 0) + return; + + struct ifaddrs* cur; + for (cur = list; cur != NULL; cur = cur->ifa_next) + { + if (cur->ifa_addr->sa_family != AF_INET) + continue; + + GetMacAddress(cur->ifa_name, macAddrRaw); + + // only add interfaces with non-zero mac addresses + if (macAddrRaw[0] || macAddrRaw[1] || macAddrRaw[2] || macAddrRaw[3] || macAddrRaw[4] || + macAddrRaw[5]) + // Add the interface. + m_interfaces.push_back(new CNetworkInterfaceFreebsd(this, cur->ifa_name, macAddrRaw)); + } + + freeifaddrs(list); +} + +std::vector<std::string> CNetworkFreebsd::GetNameServers() +{ + std::vector<std::string> result; + + res_init(); + + for (int i = 0; i < _res.nscount; i++) + { + std::string ns = inet_ntoa(_res.nsaddr_list[i].sin_addr); + result.push_back(ns); + } + + return result; +} + +bool CNetworkFreebsd::PingHost(unsigned long remote_ip, unsigned int timeout_ms) +{ + char cmd_line[64]; + + struct in_addr host_ip; + host_ip.s_addr = remote_ip; + + sprintf(cmd_line, "ping -c 1 -t %d %s", timeout_ms / 1000 + (timeout_ms % 1000) != 0, + inet_ntoa(host_ip)); + + int status = -1; + status = system(cmd_line); + int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + + // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ; + // 0 reply + // 1 no reply + // else some error + + if (result < 0 || result > 1) + CLog::Log(LOGERROR, "Ping fail : status = {}, errno = {} : '{}'", status, errno, cmd_line); + + return result == 0; +} diff --git a/xbmc/platform/freebsd/network/NetworkFreebsd.h b/xbmc/platform/freebsd/network/NetworkFreebsd.h new file mode 100644 index 0000000..92b00d7 --- /dev/null +++ b/xbmc/platform/freebsd/network/NetworkFreebsd.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/network/NetworkPosix.h" + +#include <string> +#include <vector> + +class CNetworkInterfaceFreebsd : public CNetworkInterfacePosix +{ +public: + CNetworkInterfaceFreebsd(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]); + ~CNetworkInterfaceFreebsd() override = default; + + std::string GetCurrentDefaultGateway() const override; + bool GetHostMacAddress(unsigned long host, std::string& mac) const override; +}; + +class CNetworkFreebsd : public CNetworkPosix +{ +public: + CNetworkFreebsd(); + ~CNetworkFreebsd() override = default; + + bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) override; + std::vector<std::string> GetNameServers() override; + +private: + void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) override; + void queryInterfaceList() override; +}; diff --git a/xbmc/platform/linux/AppParamParserLinux.cpp b/xbmc/platform/linux/AppParamParserLinux.cpp new file mode 100644 index 0000000..3a1adf9 --- /dev/null +++ b/xbmc/platform/linux/AppParamParserLinux.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AppParamParserLinux.h" + +#include "CompileInfo.h" +#include "application/AppParams.h" +#include "utils/StringUtils.h" + +#include <algorithm> +#include <array> +#include <iostream> +#include <vector> + +namespace +{ +std::vector<std::string> availableWindowSystems = CCompileInfo::GetAvailableWindowSystems(); +std::array<std::string, 1> availableLogTargets = {"console"}; + +constexpr const char* windowingText = + R"""( +Selected window system not available: {} + Available window systems: {} +)"""; + +constexpr const char* loggingText = + R"""( +Selected logging target not available: {} + Available log targest: {} +)"""; + +constexpr const char* helpText = + R"""( +Linux Specific Arguments: + --windowing=<system> Select which windowing method to use. + Available window systems are: {} + --logging=<target> Select which log target to use (log file will always be used in conjunction). + Available log targets are: {} +)"""; + + +} // namespace + +CAppParamParserLinux::CAppParamParserLinux() : CAppParamParser() +{ +} + +CAppParamParserLinux::~CAppParamParserLinux() = default; + +void CAppParamParserLinux::ParseArg(const std::string& arg) +{ + CAppParamParser::ParseArg(arg); + + if (arg.substr(0, 12) == "--windowing=") + { + if (std::find(availableWindowSystems.begin(), availableWindowSystems.end(), arg.substr(12)) != + availableWindowSystems.end()) + GetAppParams()->SetWindowing(arg.substr(12)); + else + { + std::cout << StringUtils::Format(windowingText, arg.substr(12), + StringUtils::Join(availableWindowSystems, ", ")); + exit(0); + } + } + else if (arg.substr(0, 10) == "--logging=") + { + if (std::find(availableLogTargets.begin(), availableLogTargets.end(), arg.substr(10)) != + availableLogTargets.end()) + { + GetAppParams()->SetLogTarget(arg.substr(10)); + } + else + { + std::cout << StringUtils::Format(loggingText, arg.substr(10), + StringUtils::Join(availableLogTargets, ", ")); + exit(0); + } + } +} + +void CAppParamParserLinux::DisplayHelp() +{ + CAppParamParser::DisplayHelp(); + + std::cout << StringUtils::Format(helpText, StringUtils::Join(availableWindowSystems, ", "), + StringUtils::Join(availableLogTargets, ", ")); +} diff --git a/xbmc/platform/linux/AppParamParserLinux.h b/xbmc/platform/linux/AppParamParserLinux.h new file mode 100644 index 0000000..ea866ca --- /dev/null +++ b/xbmc/platform/linux/AppParamParserLinux.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "application/AppParamParser.h" + +class CAppParamParserLinux : public CAppParamParser +{ +public: + CAppParamParserLinux(); + ~CAppParamParserLinux(); + +protected: + void ParseArg(const std::string& arg) override; + void DisplayHelp() override; +}; diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt new file mode 100644 index 0000000..31f4cef --- /dev/null +++ b/xbmc/platform/linux/CMakeLists.txt @@ -0,0 +1,32 @@ +set(SOURCES AppParamParserLinux.cpp + CPUInfoLinux.cpp + MemUtils.cpp + OptionalsReg.cpp + PlatformLinux.cpp + SysfsPath.cpp + TimeUtils.cpp) + +set(HEADERS AppParamParserLinux.h + CPUInfoLinux.h + OptionalsReg.h + PlatformLinux.h + SysfsPath.h + TimeUtils.h) + +if(ALSA_FOUND) + list(APPEND SOURCES FDEventMonitor.cpp) + list(APPEND HEADERS FDEventMonitor.h) +endif() + +if(DBUS_FOUND) + list(APPEND SOURCES DBusMessage.cpp + DBusUtil.cpp) + list(APPEND HEADERS DBusMessage.h + DBusUtil.h) +endif() + +if(ADDONS_CONFIGURE_AT_STARTUP) + add_compile_definitions(ADDONS_CONFIGURE_AT_STARTUP) +endif() + +core_add_library(linuxsupport) diff --git a/xbmc/platform/linux/CPUInfoLinux.cpp b/xbmc/platform/linux/CPUInfoLinux.cpp new file mode 100644 index 0000000..ef34bbd --- /dev/null +++ b/xbmc/platform/linux/CPUInfoLinux.cpp @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CPUInfoLinux.h" + +#include "utils/StringUtils.h" +#include "utils/Temperature.h" + +#include "platform/linux/SysfsPath.h" + +#include <exception> +#include <fstream> +#include <regex> +#include <sstream> +#include <vector> + +#if (defined(__arm__) && defined(HAS_NEON)) || defined(__aarch64__) +#include <asm/hwcap.h> +#include <sys/auxv.h> +#elif defined(__i386__) || defined(__x86_64__) +#include <cpuid.h> +#endif + +#include <unistd.h> + +namespace +{ +enum CpuStates +{ + STATE_USER, + STATE_NICE, + STATE_SYSTEM, + STATE_IDLE, + STATE_IOWAIT, + STATE_IRQ, + STATE_SOFTIRQ, + STATE_STEAL, + STATE_GUEST, + STATE_GUEST_NICE, + STATE_MAX +}; + +struct CpuData +{ +public: + std::size_t GetActiveTime() const + { + return state[STATE_USER] + state[STATE_NICE] + state[STATE_SYSTEM] + state[STATE_IRQ] + + state[STATE_SOFTIRQ] + state[STATE_STEAL] + state[STATE_GUEST] + state[STATE_GUEST_NICE]; + } + + std::size_t GetIdleTime() const { return state[STATE_IDLE] + state[STATE_IOWAIT]; } + + std::size_t GetTotalTime() const { return GetActiveTime() + GetIdleTime(); } + + std::string cpu; + std::size_t state[STATE_MAX]; +}; +} // namespace + +std::shared_ptr<CCPUInfo> CCPUInfo::GetCPUInfo() +{ + return std::make_shared<CCPUInfoLinux>(); +} + +CCPUInfoLinux::CCPUInfoLinux() +{ + CSysfsPath machinePath{"/sys/bus/soc/devices/soc0/machine"}; + if (machinePath.Exists()) + m_cpuHardware = machinePath.Get<std::string>().value_or(""); + + CSysfsPath familyPath{"/sys/bus/soc/devices/soc0/family"}; + if (familyPath.Exists()) + m_cpuSoC = familyPath.Get<std::string>().value_or(""); + + CSysfsPath socPath{"/sys/bus/soc/devices/soc0/soc_id"}; + if (socPath.Exists()) + m_cpuSoC += " " + socPath.Get<std::string>().value_or(""); + + CSysfsPath revisionPath{"/sys/bus/soc/devices/soc0/revision"}; + if (revisionPath.Exists()) + m_cpuRevision = revisionPath.Get<std::string>().value_or(""); + + CSysfsPath serialPath{"/sys/bus/soc/devices/soc0/serial_number"}; + if (serialPath.Exists()) + m_cpuSerial = serialPath.Get<std::string>().value_or(""); + + const std::string freqStr{"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"}; + CSysfsPath freqPath{freqStr}; + if (freqPath.Exists()) + m_freqPath = freqStr; + + const std::array<std::string, 4> modules = { + "coretemp", + "k10temp", + "scpi_sensors", + "imx_thermal_zone", + }; + + for (int i = 0; i < 20; i++) + { + CSysfsPath path{"/sys/class/hwmon/hwmon" + std::to_string(i) + "/name"}; + if (!path.Exists()) + continue; + + auto name = path.Get<std::string>(); + + if (!name.has_value()) + continue; + + for (const auto& module : modules) + { + if (module == name) + { + std::string tempStr{"/sys/class/hwmon/hwmon" + std::to_string(i) + "/temp1_input"}; + CSysfsPath tempPath{tempStr}; + if (!tempPath.Exists()) + continue; + + m_tempPath = tempStr; + break; + } + } + + if (!m_tempPath.empty()) + break; + } + + m_cpuCount = sysconf(_SC_NPROCESSORS_ONLN); + + for (int core = 0; core < m_cpuCount; core++) + { + CoreInfo coreInfo; + coreInfo.m_id = core; + m_cores.emplace_back(coreInfo); + } + +#if defined(__i386__) || defined(__x86_64__) + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + + m_cpuVendor.clear(); + + if (__get_cpuid(CPUID_INFOTYPE_MANUFACTURER, &eax, &ebx, &ecx, &edx)) + { + m_cpuVendor.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuVendor.append(reinterpret_cast<const char*>(&edx), 4); + m_cpuVendor.append(reinterpret_cast<const char*>(&ecx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &ebx, &ecx, &edx)) + { + if (eax >= CPUID_INFOTYPE_PROCESSOR_3) + { + m_cpuModel.clear(); + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_1, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_2, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + + if (__get_cpuid(CPUID_INFOTYPE_PROCESSOR_3, &eax, &ebx, &ecx, &edx)) + { + m_cpuModel.append(reinterpret_cast<const char*>(&eax), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ebx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&ecx), 4); + m_cpuModel.append(reinterpret_cast<const char*>(&edx), 4); + } + } + } + + if (__get_cpuid(CPUID_INFOTYPE_STANDARD, &eax, &eax, &ecx, &edx)) + { + if (edx & CPUID_00000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + + if (edx & CPUID_00000001_EDX_SSE) + m_cpuFeatures |= CPU_FEATURE_SSE; + + if (edx & CPUID_00000001_EDX_SSE2) + m_cpuFeatures |= CPU_FEATURE_SSE2; + + if (ecx & CPUID_00000001_ECX_SSE3) + m_cpuFeatures |= CPU_FEATURE_SSE3; + + if (ecx & CPUID_00000001_ECX_SSSE3) + m_cpuFeatures |= CPU_FEATURE_SSSE3; + + if (ecx & CPUID_00000001_ECX_SSE4) + m_cpuFeatures |= CPU_FEATURE_SSE4; + + if (ecx & CPUID_00000001_ECX_SSE42) + m_cpuFeatures |= CPU_FEATURE_SSE42; + } + + if (__get_cpuid(CPUID_INFOTYPE_EXTENDED_IMPLEMENTED, &eax, &eax, &ecx, &edx)) + { + if (eax >= CPUID_INFOTYPE_EXTENDED) + { + if (edx & CPUID_80000001_EDX_MMX) + m_cpuFeatures |= CPU_FEATURE_MMX; + + if (edx & CPUID_80000001_EDX_MMX2) + m_cpuFeatures |= CPU_FEATURE_MMX2; + + if (edx & CPUID_80000001_EDX_3DNOW) + m_cpuFeatures |= CPU_FEATURE_3DNOW; + + if (edx & CPUID_80000001_EDX_3DNOWEXT) + m_cpuFeatures |= CPU_FEATURE_3DNOWEXT; + } + } +#else + std::ifstream cpuinfo("/proc/cpuinfo"); + std::regex re(".*: (.*)$"); + + for (std::string line; std::getline(cpuinfo, line);) + { + std::smatch match; + + if (std::regex_match(line, match, re)) + { + if (match.size() == 2) + { + std::ssub_match value = match[1]; + + if (line.find("model name") != std::string::npos) + { + if (m_cpuModel.empty()) + m_cpuModel = value.str(); + } + + if (line.find("BogoMIPS") != std::string::npos) + { + if (m_cpuBogoMips.empty()) + m_cpuBogoMips = value.str(); + } + + if (line.find("Hardware") != std::string::npos) + { + if (m_cpuHardware.empty()) + m_cpuHardware = value.str(); + } + + if (line.find("Serial") != std::string::npos) + { + if (m_cpuSerial.empty()) + m_cpuSerial = value.str(); + } + + if (line.find("Revision") != std::string::npos) + { + if (m_cpuRevision.empty()) + m_cpuRevision = value.str(); + } + } + } + } +#endif + + m_cpuModel = m_cpuModel.substr(0, m_cpuModel.find(char(0))); // remove extra null terminations + +#if defined(HAS_NEON) && defined(__arm__) + if (getauxval(AT_HWCAP) & HWCAP_NEON) + m_cpuFeatures |= CPU_FEATURE_NEON; +#endif + +#if defined(HAS_NEON) && defined(__aarch64__) + if (getauxval(AT_HWCAP) & HWCAP_ASIMD) + m_cpuFeatures |= CPU_FEATURE_NEON; +#endif + + // Set MMX2 when SSE is present as SSE is a superset of MMX2 and Intel doesn't set the MMX2 cap + if (m_cpuFeatures & CPU_FEATURE_SSE) + m_cpuFeatures |= CPU_FEATURE_MMX2; +} + +int CCPUInfoLinux::GetUsedPercentage() +{ + if (!m_nextUsedReadTime.IsTimePast()) + return m_lastUsedPercentage; + + std::vector<CpuData> cpuData; + + std::ifstream infile("/proc/stat"); + + for (std::string line; std::getline(infile, line);) + { + if (line.find("cpu") != std::string::npos) + { + std::istringstream ss(line); + CpuData info; + + ss >> info.cpu; + + for (int i = 0; i < STATE_MAX; i++) + { + ss >> info.state[i]; + } + + cpuData.emplace_back(info); + } + } + + auto activeTime = cpuData.front().GetActiveTime() - m_activeTime; + auto idleTime = cpuData.front().GetIdleTime() - m_idleTime; + auto totalTime = cpuData.front().GetTotalTime() - m_totalTime; + + m_activeTime += activeTime; + m_idleTime += idleTime; + m_totalTime += totalTime; + + m_lastUsedPercentage = activeTime * 100.0f / totalTime; + m_nextUsedReadTime.Set(MINIMUM_TIME_BETWEEN_READS); + + cpuData.erase(cpuData.begin()); + + for (std::size_t core = 0; core < cpuData.size(); core++) + { + auto activeTime = cpuData[core].GetActiveTime() - m_cores[core].m_activeTime; + auto idleTime = cpuData[core].GetIdleTime() - m_cores[core].m_idleTime; + auto totalTime = cpuData[core].GetTotalTime() - m_cores[core].m_totalTime; + + m_cores[core].m_usagePercent = activeTime * 100.0 / totalTime; + + m_cores[core].m_activeTime += activeTime; + m_cores[core].m_idleTime += idleTime; + m_cores[core].m_totalTime += totalTime; + } + + return static_cast<int>(m_lastUsedPercentage); +} + +float CCPUInfoLinux::GetCPUFrequency() +{ + if (m_freqPath.empty()) + return 0; + + auto freq = CSysfsPath(m_freqPath).Get<float>(); + return freq.has_value() ? *freq / 1000.0f : 0.0f; +} + +bool CCPUInfoLinux::GetTemperature(CTemperature& temperature) +{ + if (CheckUserTemperatureCommand(temperature)) + return true; + + if (m_tempPath.empty()) + return false; + + auto temp = CSysfsPath(m_tempPath).Get<double>(); + if (!temp.has_value()) + return false; + + double value = *temp / 1000.0; + + temperature = CTemperature::CreateFromCelsius(value); + temperature.SetValid(true); + + return true; +} diff --git a/xbmc/platform/linux/CPUInfoLinux.h b/xbmc/platform/linux/CPUInfoLinux.h new file mode 100644 index 0000000..ed79afc --- /dev/null +++ b/xbmc/platform/linux/CPUInfoLinux.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Temperature.h" + +#include "platform/posix/CPUInfoPosix.h" + +#include <string> + +class CCPUInfoLinux : public CCPUInfoPosix +{ +public: + CCPUInfoLinux(); + ~CCPUInfoLinux() = default; + + int GetUsedPercentage() override; + float GetCPUFrequency() override; + bool GetTemperature(CTemperature& temperature) override; + +private: + std::string m_freqPath; + std::string m_tempPath; +}; diff --git a/xbmc/platform/linux/DBusMessage.cpp b/xbmc/platform/linux/DBusMessage.cpp new file mode 100644 index 0000000..ab09c3e --- /dev/null +++ b/xbmc/platform/linux/DBusMessage.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "DBusMessage.h" + +#include "DBusUtil.h" +#include "settings/AdvancedSettings.h" +#include "utils/log.h" + +CDBusMessage::CDBusMessage(const char *destination, const char *object, const char *interface, const char *method) +{ + m_message.reset(dbus_message_new_method_call(destination, object, interface, method)); + if (!m_message) + { + // Fails only due to memory allocation failure + throw std::runtime_error("dbus_message_new_method_call"); + } + m_haveArgs = false; + + CLog::Log(LOGDEBUG, LOGDBUS, "DBus: Creating message to {} on {} with interface {} and method {}", + destination, object, interface, method); +} + +CDBusMessage::CDBusMessage(const std::string& destination, const std::string& object, const std::string& interface, const std::string& method) +: CDBusMessage(destination.c_str(), object.c_str(), interface.c_str(), method.c_str()) +{ +} + +void DBusMessageDeleter::operator()(DBusMessage* message) const +{ + dbus_message_unref(message); +} + +void CDBusMessage::AppendObjectPath(const char *object) +{ + AppendWithType(DBUS_TYPE_OBJECT_PATH, &object); +} + +template<> +void CDBusMessage::AppendArgument<bool>(const bool arg) +{ + // dbus_bool_t width might not match C++ bool width + dbus_bool_t convArg = (arg == true); + AppendWithType(DBUS_TYPE_BOOLEAN, &convArg); +} + +template<> +void CDBusMessage::AppendArgument<std::string>(const std::string arg) +{ + AppendArgument(arg.c_str()); +} + +void CDBusMessage::AppendArgument(const char **arrayString, unsigned int length) +{ + PrepareArgument(); + DBusMessageIter sub; + if (!dbus_message_iter_open_container(&m_args, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &sub)) + { + throw std::runtime_error("dbus_message_iter_open_container"); + } + + for (unsigned int i = 0; i < length; i++) + { + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &arrayString[i])) + { + throw std::runtime_error("dbus_message_iter_append_basic"); + } + } + + if (!dbus_message_iter_close_container(&m_args, &sub)) + { + throw std::runtime_error("dbus_message_iter_close_container"); + } +} + +void CDBusMessage::AppendWithType(int type, const void* value) +{ + PrepareArgument(); + if (!dbus_message_iter_append_basic(&m_args, type, value)) + { + throw std::runtime_error("dbus_message_iter_append_basic"); + } +} + +DBusMessageIter * CDBusMessage::GetArgumentIter() { + PrepareArgument(); + return &m_args; +} + +DBusMessage *CDBusMessage::SendSystem() +{ + return Send(DBUS_BUS_SYSTEM); +} + +DBusMessage *CDBusMessage::SendSession() +{ + return Send(DBUS_BUS_SESSION); +} + +DBusMessage *CDBusMessage::SendSystem(CDBusError& error) +{ + return Send(DBUS_BUS_SYSTEM, error); +} + +DBusMessage *CDBusMessage::SendSession(CDBusError& error) +{ + return Send(DBUS_BUS_SESSION, error); +} + +bool CDBusMessage::SendAsyncSystem() +{ + return SendAsync(DBUS_BUS_SYSTEM); +} + +bool CDBusMessage::SendAsyncSession() +{ + return SendAsync(DBUS_BUS_SESSION); +} + +DBusMessage *CDBusMessage::Send(DBusBusType type) +{ + CDBusError error; + CDBusConnection con; + if (!con.Connect(type)) + return nullptr; + + DBusMessage *returnMessage = Send(con, error); + + if (error) + error.Log(); + + return returnMessage; +} + +DBusMessage *CDBusMessage::Send(DBusBusType type, CDBusError& error) +{ + CDBusConnection con; + if (!con.Connect(type, error)) + return nullptr; + + DBusMessage *returnMessage = Send(con, error); + + return returnMessage; +} + +bool CDBusMessage::SendAsync(DBusBusType type) +{ + CDBusConnection con; + if (!con.Connect(type)) + return false; + + return dbus_connection_send(con, m_message.get(), nullptr); +} + +DBusMessage *CDBusMessage::Send(DBusConnection *con, CDBusError& error) +{ + m_reply.reset(dbus_connection_send_with_reply_and_block(con, m_message.get(), -1, error)); + return m_reply.get(); +} + +void CDBusMessage::PrepareArgument() +{ + if (!m_haveArgs) + dbus_message_iter_init_append(m_message.get(), &m_args); + + m_haveArgs = true; +} + +bool CDBusMessage::InitializeReplyIter(DBusMessageIter* iter) +{ + if (!m_reply) + { + throw std::logic_error("Cannot get reply arguments of message that does not have reply"); + } + if (!dbus_message_iter_init(m_reply.get(), iter)) + { + CLog::Log(LOGWARNING, "Tried to obtain reply arguments from message that has zero arguments"); + return false; + } + return true; +} + +bool CDBusMessage::CheckTypeAndGetValue(DBusMessageIter* iter, int expectType, void* dest) +{ + const int haveType = dbus_message_iter_get_arg_type(iter); + if (haveType != expectType) + { + CLog::Log(LOGDEBUG, "DBus argument type mismatch: expected {}, got {}", expectType, haveType); + return false; + } + + dbus_message_iter_get_basic(iter, dest); + return true; +} diff --git a/xbmc/platform/linux/DBusMessage.h b/xbmc/platform/linux/DBusMessage.h new file mode 100644 index 0000000..80c7cd6 --- /dev/null +++ b/xbmc/platform/linux/DBusMessage.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <memory> +#include <string> +#include <type_traits> + +#include <dbus/dbus.h> + +class CDBusError; + +template<typename T> +struct ToDBusType; +template<> struct ToDBusType<bool> { static constexpr int TYPE = DBUS_TYPE_BOOLEAN; }; +template<> struct ToDBusType<char*> { static constexpr int TYPE = DBUS_TYPE_STRING; }; +template<> struct ToDBusType<const char*> { static constexpr int TYPE = DBUS_TYPE_STRING; }; +template<> struct ToDBusType<std::uint8_t> { static constexpr int TYPE = DBUS_TYPE_BYTE; }; +template<> struct ToDBusType<std::int16_t> { static constexpr int TYPE = DBUS_TYPE_INT16; }; +template<> struct ToDBusType<std::uint16_t> { static constexpr int TYPE = DBUS_TYPE_UINT16; }; +template<> struct ToDBusType<std::int32_t> { static constexpr int TYPE = DBUS_TYPE_INT32; }; +template<> struct ToDBusType<std::uint32_t> { static constexpr int TYPE = DBUS_TYPE_UINT32; }; +template<> struct ToDBusType<std::int64_t> { static constexpr int TYPE = DBUS_TYPE_INT64; }; +template<> struct ToDBusType<std::uint64_t> { static constexpr int TYPE = DBUS_TYPE_UINT64; }; +template<> struct ToDBusType<double> { static constexpr int TYPE = DBUS_TYPE_DOUBLE; }; + +template<typename T> +using ToDBusTypeFromPointer = ToDBusType<typename std::remove_pointer<T>::type>; + +struct DBusMessageDeleter +{ + void operator()(DBusMessage* message) const; +}; +using DBusMessagePtr = std::unique_ptr<DBusMessage, DBusMessageDeleter>; + +class CDBusMessage +{ +public: + CDBusMessage(const char *destination, const char *object, const char *interface, const char *method); + CDBusMessage(std::string const& destination, std::string const& object, std::string const& interface, std::string const& method); + + void AppendObjectPath(const char *object); + + template<typename T> + void AppendArgument(const T arg) + { + AppendWithType(ToDBusType<T>::TYPE, &arg); + } + void AppendArgument(const char **arrayString, unsigned int length); + + template<typename TFirst> + void AppendArguments(const TFirst first) + { + AppendArgument(first); + // Recursion end + } + template<typename TFirst, typename... TArgs> + void AppendArguments(const TFirst first, const TArgs... args) + { + AppendArgument(first); + AppendArguments(args...); + } + + DBusMessageIter * GetArgumentIter(); + + /** + * Retrieve simple arguments from DBus reply message + * + * You MUST use the correct fixed-width integer typedefs (e.g. std::uint16_t) + * corresponding to the DBus types for the variables or you will get potentially + * differing behavior between architectures since the DBus argument type detection + * is based on the width of the type. + * + * Complex arguments (arrays, structs) are not supported. + * + * Returned pointers for strings are only valid until the instance of this class + * is deleted. + * + * \throw std::logic_error if the message has no reply + * \return whether all arguments could be retrieved (false on argument type + * mismatch or when more arguments were to be retrieved than there are + * in the message) + */ + template<typename... TArgs> + bool GetReplyArguments(TArgs*... args) + { + DBusMessageIter iter; + if (!InitializeReplyIter(&iter)) + { + return false; + } + return GetReplyArgumentsWithIter(&iter, args...); + } + + DBusMessage *SendSystem(); + DBusMessage *SendSession(); + DBusMessage *SendSystem(CDBusError& error); + DBusMessage *SendSession(CDBusError& error); + + bool SendAsyncSystem(); + bool SendAsyncSession(); + + DBusMessage *Send(DBusBusType type); + DBusMessage *Send(DBusBusType type, CDBusError& error); + DBusMessage *Send(DBusConnection *con, CDBusError& error); +private: + void AppendWithType(int type, const void* value); + bool SendAsync(DBusBusType type); + + void PrepareArgument(); + + bool InitializeReplyIter(DBusMessageIter* iter); + bool CheckTypeAndGetValue(DBusMessageIter* iter, int expectType, void* dest); + template<typename TFirst> + bool GetReplyArgumentsWithIter(DBusMessageIter* iter, TFirst* first) + { + // Recursion end + return CheckTypeAndGetValue(iter, ToDBusTypeFromPointer<TFirst>::TYPE, first); + } + template<typename TFirst, typename... TArgs> + bool GetReplyArgumentsWithIter(DBusMessageIter* iter, TFirst* first, TArgs*... args) + { + if (!CheckTypeAndGetValue(iter, ToDBusTypeFromPointer<TFirst>::TYPE, first)) + { + return false; + } + // Ignore return value, if we try to read past the end of the message this + // will be catched by the type check (past-end type is DBUS_TYPE_INVALID) + dbus_message_iter_next(iter); + return GetReplyArgumentsWithIter(iter, args...); + } + + DBusMessagePtr m_message; + DBusMessagePtr m_reply = nullptr; + DBusMessageIter m_args; + bool m_haveArgs; +}; + +template<> +void CDBusMessage::AppendArgument<bool>(const bool arg); +template<> +void CDBusMessage::AppendArgument<std::string>(const std::string arg); diff --git a/xbmc/platform/linux/DBusUtil.cpp b/xbmc/platform/linux/DBusUtil.cpp new file mode 100644 index 0000000..2efb75c --- /dev/null +++ b/xbmc/platform/linux/DBusUtil.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "DBusUtil.h" + +#include "utils/log.h" + +CVariant CDBusUtil::GetVariant(const char *destination, const char *object, const char *interface, const char *property) +{ +//dbus-send --system --print-reply --dest=destination object org.freedesktop.DBus.Properties.Get string:interface string:property + CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "Get"); + CVariant result; + + message.AppendArgument(interface); + message.AppendArgument(property); + DBusMessage *reply = message.SendSystem(); + + if (reply) + { + DBusMessageIter iter; + + if (dbus_message_iter_init(reply, &iter)) + { + if (!dbus_message_has_signature(reply, "v")) + CLog::Log(LOGERROR, "DBus: wrong signature on Get - should be \"v\" but was {}", + dbus_message_iter_get_signature(&iter)); + else + result = ParseVariant(&iter); + } + } + + return result; +} + +CVariant CDBusUtil::GetAll(const char *destination, const char *object, const char *interface) +{ + CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "GetAll"); + CVariant properties; + message.AppendArgument(interface); + DBusMessage *reply = message.SendSystem(); + if (reply) + { + DBusMessageIter iter; + if (dbus_message_iter_init(reply, &iter)) + { + if (!dbus_message_has_signature(reply, "a{sv}")) + CLog::Log(LOGERROR, "DBus: wrong signature on GetAll - should be \"a{sv}\" but was {}", + dbus_message_iter_get_signature(&iter)); + else + { + do + { + DBusMessageIter sub; + dbus_message_iter_recurse(&iter, &sub); + do + { + DBusMessageIter dict; + dbus_message_iter_recurse(&sub, &dict); + do + { + const char * key = NULL; + + dbus_message_iter_get_basic(&dict, &key); + if (!dbus_message_iter_next(&dict)) + break; + + CVariant value = ParseVariant(&dict); + + if (!value.isNull()) + properties[key] = value; + + } while (dbus_message_iter_next(&dict)); + + } while (dbus_message_iter_next(&sub)); + + } while (dbus_message_iter_next(&iter)); + } + } + } + + return properties; +} + +CVariant CDBusUtil::ParseVariant(DBusMessageIter *itr) +{ + DBusMessageIter variant; + dbus_message_iter_recurse(itr, &variant); + + return ParseType(&variant); +} + +CVariant CDBusUtil::ParseType(DBusMessageIter *itr) +{ + CVariant value; + const char * string = NULL; + dbus_int32_t int32 = 0; + dbus_uint32_t uint32 = 0; + dbus_int64_t int64 = 0; + dbus_uint64_t uint64 = 0; + dbus_bool_t boolean = false; + double doublev = 0; + + int type = dbus_message_iter_get_arg_type(itr); + switch (type) + { + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_STRING: + dbus_message_iter_get_basic(itr, &string); + value = string; + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(itr, &uint32); + value = (uint64_t)uint32; + break; + case DBUS_TYPE_BYTE: + case DBUS_TYPE_INT32: + dbus_message_iter_get_basic(itr, &int32); + value = (int64_t)int32; + break; + case DBUS_TYPE_UINT64: + dbus_message_iter_get_basic(itr, &uint64); + value = (uint64_t)uint64; + break; + case DBUS_TYPE_INT64: + dbus_message_iter_get_basic(itr, &int64); + value = (int64_t)int64; + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(itr, &boolean); + value = (bool)boolean; + break; + case DBUS_TYPE_DOUBLE: + dbus_message_iter_get_basic(itr, &doublev); + value = doublev; + break; + case DBUS_TYPE_ARRAY: + DBusMessageIter array; + dbus_message_iter_recurse(itr, &array); + + value = CVariant::VariantTypeArray; + + do + { + CVariant item = ParseType(&array); + if (!item.isNull()) + value.push_back(item); + } while (dbus_message_iter_next(&array)); + break; + } + + return value; +} + +bool CDBusUtil::TryMethodCall(DBusBusType bus, const char* destination, const char* object, const char* interface, const char* method) +{ + CDBusMessage message(destination, object, interface, method); + CDBusError error; + message.Send(bus, error); + if (error) + { + error.Log(LOGDEBUG, std::string("DBus method call to ") + interface + "." + method + " at " + object + " of " + destination + " failed"); + } + return !error; +} + +bool CDBusUtil::TryMethodCall(DBusBusType bus, std::string const& destination, std::string const& object, std::string const& interface, std::string const& method) +{ + return TryMethodCall(bus, destination.c_str(), object.c_str(), interface.c_str(), method.c_str()); +} + +CDBusConnection::CDBusConnection() = default; + +bool CDBusConnection::Connect(DBusBusType bus, bool openPrivate) +{ + CDBusError error; + Connect(bus, error, openPrivate); + if (error) + { + error.Log(LOGWARNING, "DBus connection failed"); + return false; + } + + return true; +} + +bool CDBusConnection::Connect(DBusBusType bus, CDBusError& error, bool openPrivate) +{ + if (m_connection) + { + throw std::logic_error("Cannot reopen connected DBus connection"); + } + + m_connection.get_deleter().closeBeforeUnref = openPrivate; + + if (openPrivate) + { + m_connection.reset(dbus_bus_get_private(bus, error)); + } + else + { + m_connection.reset(dbus_bus_get(bus, error)); + } + + return !!m_connection; +} + +CDBusConnection::operator DBusConnection*() +{ + return m_connection.get(); +} + +void CDBusConnection::DBusConnectionDeleter::operator()(DBusConnection* connection) const +{ + if (closeBeforeUnref) + { + dbus_connection_close(connection); + } + dbus_connection_unref(connection); +} + +void CDBusConnection::Destroy() +{ + m_connection.reset(); +} + + +CDBusError::CDBusError() +{ + dbus_error_init(&m_error); +} + +CDBusError::~CDBusError() +{ + Reset(); +} + +void CDBusError::Reset() +{ + dbus_error_free(&m_error); +} + +CDBusError::operator DBusError*() +{ + return &m_error; +} + +bool CDBusError::IsSet() const +{ + return dbus_error_is_set(&m_error); +} + +CDBusError::operator bool() +{ + return IsSet(); +} + +CDBusError::operator bool() const +{ + return IsSet(); +} + +std::string CDBusError::Name() const +{ + if (!IsSet()) + { + throw std::logic_error("Cannot retrieve name of unset DBus error"); + } + return m_error.name; +} + +std::string CDBusError::Message() const +{ + if (!IsSet()) + { + throw std::logic_error("Cannot retrieve message of unset DBus error"); + } + return m_error.message; +} + +void CDBusError::Log(std::string const& message) const +{ + Log(LOGERROR, message); +} + +void CDBusError::Log(int level, const std::string& message) const +{ + if (!IsSet()) + { + throw std::logic_error("Cannot log unset DBus error"); + } + CLog::Log(level, "{}: {} - {}", message, m_error.name, m_error.message); +} diff --git a/xbmc/platform/linux/DBusUtil.h b/xbmc/platform/linux/DBusUtil.h new file mode 100644 index 0000000..57349ac --- /dev/null +++ b/xbmc/platform/linux/DBusUtil.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusMessage.h" +#include "utils/Variant.h" + +#include <memory> +#include <string> + +#include <dbus/dbus.h> + +class CDBusUtil +{ +public: + static CVariant GetAll(const char *destination, const char *object, const char *interface); + static CVariant GetVariant(const char *destination, const char *object, const char *interface, const char *property); + /** + * Try to call a DBus method and return whether the call succeeded + */ + static bool TryMethodCall(DBusBusType bus, const char* destination, const char* object, const char* interface, const char* method); + /** + * Try to call a DBus method and return whether the call succeeded + */ + static bool TryMethodCall(DBusBusType bus, std::string const& destination, std::string const& object, std::string const& interface, std::string const& method); + +private: + static CVariant ParseType(DBusMessageIter *itr); + static CVariant ParseVariant(DBusMessageIter *itr); +}; + +class CDBusConnection +{ +public: + CDBusConnection(); + bool Connect(DBusBusType bus, bool openPrivate = false); + bool Connect(DBusBusType bus, CDBusError& error, bool openPrivate = false); + void Destroy(); + operator DBusConnection*(); + +private: + CDBusConnection(CDBusConnection const& other) = delete; + CDBusConnection& operator=(CDBusConnection const& other) = delete; + + struct DBusConnectionDeleter + { + DBusConnectionDeleter() {} + bool closeBeforeUnref = false; + void operator()(DBusConnection* connection) const; + }; + std::unique_ptr<DBusConnection, DBusConnectionDeleter> m_connection; +}; + +class CDBusError +{ +public: + CDBusError(); + ~CDBusError(); + operator DBusError*(); + bool IsSet() const; + /** + * Reset this error wrapper + * + * If there was an error, it was handled and this error wrapper should be used + * again in a new call, it must be reset before that call. + */ + void Reset(); + // Keep because operator DBusError* would be used for if-statements on + // non-const CDBusError instead + operator bool(); + operator bool() const; + std::string Name() const; + std::string Message() const; + void Log(std::string const& message = "DBus error") const; + void Log(int level, std::string const& message = "DBus error") const; + +private: + CDBusError(CDBusError const& other) = delete; + CDBusError& operator=(CDBusError const& other) = delete; + + DBusError m_error; +}; diff --git a/xbmc/platform/linux/FDEventMonitor.cpp b/xbmc/platform/linux/FDEventMonitor.cpp new file mode 100644 index 0000000..9cd27c1 --- /dev/null +++ b/xbmc/platform/linux/FDEventMonitor.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FDEventMonitor.h" + +#include "threads/SingleLock.h" +#include "utils/log.h" + +#include <errno.h> +#include <mutex> + +#include <poll.h> +#include <sys/eventfd.h> +#include <unistd.h> + +#include "PlatformDefs.h" + +CFDEventMonitor::CFDEventMonitor() : + CThread("FDEventMonitor") +{ +} + +CFDEventMonitor::~CFDEventMonitor() +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + InterruptPoll(); + + if (m_wakeupfd >= 0) + { + /* sets m_bStop */ + StopThread(false); + + /* wake up the poll() call */ + eventfd_write(m_wakeupfd, 1); + + /* Wait for the thread to stop */ + { + CSingleExit exit(m_mutex); + StopThread(true); + } + + close(m_wakeupfd); + } +} + +void CFDEventMonitor::AddFD(const MonitoredFD& monitoredFD, int& id) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + InterruptPoll(); + + AddFDLocked(monitoredFD, id); + + StartMonitoring(); +} + +void CFDEventMonitor::AddFDs(const std::vector<MonitoredFD>& monitoredFDs, + std::vector<int>& ids) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + InterruptPoll(); + + for (unsigned int i = 0; i < monitoredFDs.size(); ++i) + { + int id; + AddFDLocked(monitoredFDs[i], id); + ids.push_back(id); + } + + StartMonitoring(); +} + +void CFDEventMonitor::RemoveFD(int id) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + InterruptPoll(); + + if (m_monitoredFDs.erase(id) != 1) + { + CLog::Log(LOGERROR, "CFDEventMonitor::RemoveFD - Tried to remove non-existing monitoredFD {}", + id); + } + + UpdatePollDescs(); +} + +void CFDEventMonitor::RemoveFDs(const std::vector<int>& ids) +{ + std::unique_lock<CCriticalSection> lock(m_mutex); + InterruptPoll(); + + for (unsigned int i = 0; i < ids.size(); ++i) + { + if (m_monitoredFDs.erase(ids[i]) != 1) + { + CLog::Log(LOGERROR, + "CFDEventMonitor::RemoveFDs - Tried to remove non-existing monitoredFD {} while " + "removing {} FDs", + ids[i], (unsigned)ids.size()); + } + } + + UpdatePollDescs(); +} + +void CFDEventMonitor::Process() +{ + eventfd_t dummy; + + while (!m_bStop) + { + std::unique_lock<CCriticalSection> lock(m_mutex); + std::unique_lock<CCriticalSection> pollLock(m_pollMutex); + + /* + * Leave the main mutex here to allow another thread to + * lock it while we are in poll(). + * By then calling InterruptPoll() the other thread can + * wake up poll and wait for the processing to pause at + * the above lock(m_mutex). + */ + lock.unlock(); + + int err = poll(&m_pollDescs[0], m_pollDescs.size(), -1); + + if (err < 0 && errno != EINTR) + { + CLog::Log(LOGERROR, "CFDEventMonitor::Process - poll() failed, error {}, stopping monitoring", + errno); + StopThread(false); + } + + // Something woke us up - either there is data available or we are being + // paused/stopped via m_wakeupfd. + + for (unsigned int i = 0; i < m_pollDescs.size(); ++i) + { + struct pollfd& pollDesc = m_pollDescs[i]; + int id = m_monitoredFDbyPollDescs[i]; + const MonitoredFD& monitoredFD = m_monitoredFDs[id]; + + if (pollDesc.revents) + { + if (monitoredFD.callback) + { + monitoredFD.callback(id, pollDesc.fd, pollDesc.revents, + monitoredFD.callbackData); + } + + if (pollDesc.revents & (POLLERR | POLLHUP | POLLNVAL)) + { + CLog::Log(LOGERROR, + "CFDEventMonitor::Process - polled fd {} got revents 0x{:x}, removing it", + pollDesc.fd, pollDesc.revents); + + /* Probably would be nice to inform our caller that their FD was + * dropped, but oh well... */ + m_monitoredFDs.erase(id); + UpdatePollDescs(); + } + + pollDesc.revents = 0; + } + } + + /* flush wakeup fd */ + eventfd_read(m_wakeupfd, &dummy); + + } +} + +void CFDEventMonitor::AddFDLocked(const MonitoredFD& monitoredFD, int& id) +{ + id = m_nextID; + + while (m_monitoredFDs.count(id)) + { + ++id; + } + m_nextID = id + 1; + + m_monitoredFDs[id] = monitoredFD; + + AddPollDesc(id, monitoredFD.fd, monitoredFD.events); +} + +void CFDEventMonitor::AddPollDesc(int id, int fd, short events) +{ + struct pollfd newPollFD; + newPollFD.fd = fd; + newPollFD.events = events; + newPollFD.revents = 0; + + m_pollDescs.push_back(newPollFD); + m_monitoredFDbyPollDescs.push_back(id); +} + +void CFDEventMonitor::UpdatePollDescs() +{ + m_monitoredFDbyPollDescs.clear(); + m_pollDescs.clear(); + + for (const auto& it : m_monitoredFDs) + { + AddPollDesc(it.first, it.second.fd, it.second.events); + } +} + +void CFDEventMonitor::StartMonitoring() +{ + if (!IsRunning()) + { + /* Start the monitoring thread */ + + m_wakeupfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (m_wakeupfd < 0) + { + CLog::Log(LOGERROR, "CFDEventMonitor::StartMonitoring - Failed to create eventfd, error {}", + errno); + return; + } + + /* Add wakeup fd to the fd list */ + int id; + AddFDLocked(MonitoredFD(m_wakeupfd, POLLIN, NULL, NULL), id); + + Create(false); + } +} + +void CFDEventMonitor::InterruptPoll() +{ + if (m_wakeupfd >= 0) + { + eventfd_write(m_wakeupfd, 1); + /* wait for the poll() result handling (if any) to end */ + std::unique_lock<CCriticalSection> pollLock(m_pollMutex); + } +} diff --git a/xbmc/platform/linux/FDEventMonitor.h b/xbmc/platform/linux/FDEventMonitor.h new file mode 100644 index 0000000..c0b8395 --- /dev/null +++ b/xbmc/platform/linux/FDEventMonitor.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/Platform.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <map> +#include <vector> + +#include <sys/epoll.h> + +/** + * Monitor a file descriptor with callback on poll() events. + */ +class CFDEventMonitor : public IPlatformService, private CThread +{ +public: + + typedef void (*EventCallback)(int id, int fd, short revents, void *data); + + struct MonitoredFD + { + int fd = -1; /**< File descriptor to be monitored */ + short events = 0; /**< Events to be monitored (see poll(2)) */ + + EventCallback callback = nullptr; /** Callback to be called on events */ + void *callbackData = nullptr; /** data parameter for EventCallback */ + + MonitoredFD(int fd_, short events_, EventCallback callback_, void *callbackData_) : + fd(fd_), events(events_), callback(callback_), callbackData(callbackData_) {} + MonitoredFD() = default; + }; + + CFDEventMonitor(); + ~CFDEventMonitor() override; + + void AddFD(const MonitoredFD& monitoredFD, int& id); + void AddFDs(const std::vector<MonitoredFD>& monitoredFDs, std::vector<int>& ids); + + void RemoveFD(int id); + void RemoveFDs(const std::vector<int>& ids); + +protected: + void Process() override; + +private: + void AddFDLocked(const MonitoredFD& monitoredFD, int& id); + + void AddPollDesc(int id, int fd, short events); + void UpdatePollDescs(); + + void StartMonitoring(); + void InterruptPoll(); + + std::map<int, MonitoredFD> m_monitoredFDs; + + /* these are kept synchronized */ + std::vector<int> m_monitoredFDbyPollDescs; + std::vector<struct pollfd> m_pollDescs; + + int m_nextID = 0; + int m_wakeupfd = -1; + + CCriticalSection m_mutex; + CCriticalSection m_pollMutex; +}; diff --git a/xbmc/platform/linux/MemUtils.cpp b/xbmc/platform/linux/MemUtils.cpp new file mode 100644 index 0000000..09dd22f --- /dev/null +++ b/xbmc/platform/linux/MemUtils.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/MemUtils.h" + +#include <cstdlib> +#include <fstream> + +namespace KODI +{ +namespace MEMORY +{ + +void* AlignedMalloc(size_t s, size_t alignTo) +{ + void* p = nullptr; + int res = posix_memalign(&p, alignTo, s); + if (res == EINVAL) + { + throw std::runtime_error("Failed to align memory, alignment is not a multiple of 2"); + } + else if (res == ENOMEM) + { + throw std::runtime_error("Failed to align memory, insufficient memory available"); + } + return p; +} + +void AlignedFree(void* p) +{ + if (!p) + return; + + free(p); +} + +void GetMemoryStatus(MemoryStatus* buffer) +{ + if (!buffer) + return; + + std::ifstream file("/proc/meminfo"); + + if (!file.is_open()) + return; + + uint64_t buffers; + uint64_t cached; + uint64_t free; + uint64_t total; + uint64_t reclaimable; + + std::string token; + + while (file >> token) + { + if (token == "Buffers:") + file >> buffers; + if (token == "Cached:") + file >> cached; + if (token == "MemFree:") + file >> free; + if (token == "MemTotal:") + file >> total; + if (token == "SReclaimable:") + file >> reclaimable; + } + + buffer->totalPhys = total * 1024; + buffer->availPhys = (free + cached + reclaimable + buffers) * 1024; +} + +} +} diff --git a/xbmc/platform/linux/OptionalsReg.cpp b/xbmc/platform/linux/OptionalsReg.cpp new file mode 100644 index 0000000..82fb176 --- /dev/null +++ b/xbmc/platform/linux/OptionalsReg.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "OptionalsReg.h" + + +//----------------------------------------------------------------------------- +// ALSA +//----------------------------------------------------------------------------- + +#ifdef HAS_ALSA +#include "cores/AudioEngine/Sinks/AESinkALSA.h" +bool OPTIONALS::ALSARegister() +{ + CAESinkALSA::Register(); + return true; +} +#else +bool OPTIONALS::ALSARegister() +{ + return false; +} +#endif + +//----------------------------------------------------------------------------- +// PulseAudio +//----------------------------------------------------------------------------- + +#ifdef HAS_PULSEAUDIO +#include "cores/AudioEngine/Sinks/AESinkPULSE.h" +bool OPTIONALS::PulseAudioRegister() +{ + bool ret = CAESinkPULSE::Register(); + return ret; +} +#else +bool OPTIONALS::PulseAudioRegister() +{ + return false; +} +#endif + +//----------------------------------------------------------------------------- +// Pipewire +//----------------------------------------------------------------------------- + +#ifdef HAS_PIPEWIRE +#include "cores/AudioEngine/Sinks/pipewire/AESinkPipewire.h" +bool OPTIONALS::PipewireRegister() +{ + bool ret = AE::SINK::CAESinkPipewire::Register(); + return ret; +} +#else +bool OPTIONALS::PipewireRegister() +{ + return false; +} +#endif + +//----------------------------------------------------------------------------- +// sndio +//----------------------------------------------------------------------------- + +#ifdef HAS_SNDIO +#include "cores/AudioEngine/Sinks/AESinkSNDIO.h" +bool OPTIONALS::SndioRegister() +{ + CAESinkSNDIO::Register(); + return true; +} +#else +bool OPTIONALS::SndioRegister() +{ + return false; +} +#endif + +//----------------------------------------------------------------------------- +// Lirc +//----------------------------------------------------------------------------- + +#ifdef HAS_LIRC +#include "platform/linux/input/LIRC.h" +#include "ServiceBroker.h" +class OPTIONALS::CLircContainer +{ +public: + CLircContainer() + { + m_lirc.Start(); + } +protected: + CLirc m_lirc; +}; +#else +class OPTIONALS::CLircContainer +{ +}; +#endif + +OPTIONALS::CLircContainer* OPTIONALS::LircRegister() +{ + return new CLircContainer(); +} +void OPTIONALS::delete_CLircContainer::operator()(CLircContainer *p) const +{ + delete p; +} diff --git a/xbmc/platform/linux/OptionalsReg.h b/xbmc/platform/linux/OptionalsReg.h new file mode 100644 index 0000000..75fdb6a --- /dev/null +++ b/xbmc/platform/linux/OptionalsReg.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +//----------------------------------------------------------------------------- +// ALSA +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +bool ALSARegister(); +} + +//----------------------------------------------------------------------------- +// PulseAudio +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +bool PulseAudioRegister(); +} + +//----------------------------------------------------------------------------- +// Pipewire +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +bool PipewireRegister(); +} + +//----------------------------------------------------------------------------- +// sndio +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +bool SndioRegister(); +} + +//----------------------------------------------------------------------------- +// Lirc +//----------------------------------------------------------------------------- + +namespace OPTIONALS +{ +class CLircContainer; +CLircContainer* LircRegister(); +struct delete_CLircContainer +{ + void operator()(CLircContainer *p) const; +}; +} diff --git a/xbmc/platform/linux/PlatformLinux.cpp b/xbmc/platform/linux/PlatformLinux.cpp new file mode 100644 index 0000000..a7af098 --- /dev/null +++ b/xbmc/platform/linux/PlatformLinux.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlatformLinux.h" + +#if defined(HAS_ALSA) +#include "cores/AudioEngine/Sinks/alsa/ALSADeviceMonitor.h" +#include "cores/AudioEngine/Sinks/alsa/ALSAHControlMonitor.h" +#endif + +#include "utils/StringUtils.h" + +#if defined(HAS_ALSA) +#include "platform/linux/FDEventMonitor.h" +#endif + +#include "platform/linux/powermanagement/LinuxPowerSyscall.h" + +// clang-format off +#if defined(HAS_GLES) +#if defined(HAVE_WAYLAND) +#include "windowing/wayland/WinSystemWaylandEGLContextGLES.h" +#endif +#if defined(HAVE_X11) +#include "windowing/X11/WinSystemX11GLESContext.h" +#endif +#if defined(HAVE_GBM) +#include "windowing/gbm/WinSystemGbmGLESContext.h" +#endif +#endif + +#if defined(HAS_GL) +#if defined(HAVE_WAYLAND) +#include "windowing/wayland/WinSystemWaylandEGLContextGL.h" +#endif +#if defined(HAVE_X11) +#include "windowing/X11/WinSystemX11GLContext.h" +#endif +#if defined(HAVE_GBM) +#include "windowing/gbm/WinSystemGbmGLContext.h" +#endif +#endif +// clang-format on + +#include <cstdlib> + +CPlatform* CPlatform::CreateInstance() +{ + return new CPlatformLinux(); +} + +bool CPlatformLinux::InitStageOne() +{ + if (!CPlatformPosix::InitStageOne()) + return false; + + setenv("OS", "Linux", true); // for python scripts that check the OS + +#if defined(HAS_GLES) +#if defined(HAVE_WAYLAND) + KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGLES::Register(); +#endif +#if defined(HAVE_X11) + KODI::WINDOWING::X11::CWinSystemX11GLESContext::Register(); +#endif +#if defined(HAVE_GBM) + KODI::WINDOWING::GBM::CWinSystemGbmGLESContext::Register(); +#endif +#endif + +#if defined(HAS_GL) +#if defined(HAVE_WAYLAND) + KODI::WINDOWING::WAYLAND::CWinSystemWaylandEGLContextGL::Register(); +#endif +#if defined(HAVE_X11) + KODI::WINDOWING::X11::CWinSystemX11GLContext::Register(); +#endif +#if defined(HAVE_GBM) + KODI::WINDOWING::GBM::CWinSystemGbmGLContext::Register(); +#endif +#endif + + CLinuxPowerSyscall::Register(); + + std::string envSink; + if (getenv("KODI_AE_SINK")) + envSink = getenv("KODI_AE_SINK"); + + if (StringUtils::EqualsNoCase(envSink, "ALSA")) + { + OPTIONALS::ALSARegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "PULSE")) + { + OPTIONALS::PulseAudioRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "PIPEWIRE")) + { + OPTIONALS::PipewireRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "SNDIO")) + { + OPTIONALS::SndioRegister(); + } + else if (StringUtils::EqualsNoCase(envSink, "ALSA+PULSE")) + { + OPTIONALS::ALSARegister(); + OPTIONALS::PulseAudioRegister(); + } + else + { + if (!OPTIONALS::PulseAudioRegister()) + { + if (!OPTIONALS::PipewireRegister()) + { + if (!OPTIONALS::ALSARegister()) + { + OPTIONALS::SndioRegister(); + } + } + } + } + + m_lirc.reset(OPTIONALS::LircRegister()); + +#if defined(HAS_ALSA) + RegisterComponent(std::make_shared<CFDEventMonitor>()); +#if defined(HAVE_LIBUDEV) + RegisterComponent(std::make_shared<CALSADeviceMonitor>()); +#endif +#if !defined(HAVE_X11) + RegisterComponent(std::make_shared<CALSAHControlMonitor>()); +#endif +#endif // HAS_ALSA + return true; +} + +void CPlatformLinux::DeinitStageOne() +{ +#if defined(HAS_ALSA) +#if !defined(HAVE_X11) + DeregisterComponent(typeid(CALSAHControlMonitor)); +#endif +#if defined(HAVE_LIBUDEV) + DeregisterComponent(typeid(CALSADeviceMonitor)); +#endif + DeregisterComponent(typeid(CFDEventMonitor)); +#endif // HAS_ALSA +} + +bool CPlatformLinux::IsConfigureAddonsAtStartupEnabled() +{ +#if defined(ADDONS_CONFIGURE_AT_STARTUP) + return true; +#else + return false; +#endif +} diff --git a/xbmc/platform/linux/PlatformLinux.h b/xbmc/platform/linux/PlatformLinux.h new file mode 100644 index 0000000..b74deb6 --- /dev/null +++ b/xbmc/platform/linux/PlatformLinux.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/linux/OptionalsReg.h" +#include "platform/posix/PlatformPosix.h" + +#include <memory> + +class CPlatformLinux : public CPlatformPosix +{ +public: + CPlatformLinux() = default; + + ~CPlatformLinux() override = default; + + bool InitStageOne() override; + void DeinitStageOne() override; + + bool IsConfigureAddonsAtStartupEnabled() override; + +private: + std::unique_ptr<OPTIONALS::CLircContainer, OPTIONALS::delete_CLircContainer> m_lirc; +}; diff --git a/xbmc/platform/linux/SysfsPath.cpp b/xbmc/platform/linux/SysfsPath.cpp new file mode 100644 index 0000000..af4f9cb --- /dev/null +++ b/xbmc/platform/linux/SysfsPath.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SysfsPath.h" + +#include <exception> + +bool CSysfsPath::Exists() +{ + std::ifstream file(m_path); + + if (!file.is_open()) + return false; + + return true; +} + +template<> +std::optional<std::string> CSysfsPath::Get() +{ + try + { + std::ifstream file(m_path); + + std::string value; + + std::getline(file, value); + + if (file.bad()) + { + CLog::LogF(LOGERROR, "error reading from '{}'", m_path); + return std::nullopt; + } + + return value; + } + catch (const std::exception& e) + { + CLog::LogF(LOGERROR, "exception reading from '{}': {}", m_path, e.what()); + return std::nullopt; + } +} diff --git a/xbmc/platform/linux/SysfsPath.h b/xbmc/platform/linux/SysfsPath.h new file mode 100644 index 0000000..3c19703 --- /dev/null +++ b/xbmc/platform/linux/SysfsPath.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/log.h" + +#include <exception> +#include <fstream> +#include <optional> +#include <string> + +class CSysfsPath +{ +public: + CSysfsPath() = default; + CSysfsPath(const std::string& path) : m_path(path) {} + ~CSysfsPath() = default; + + bool Exists(); + + template<typename T> + std::optional<T> Get() + { + try + { + std::ifstream file(m_path); + + T value; + + file >> value; + + if (file.bad()) + { + CLog::LogF(LOGERROR, "error reading from '{}'", m_path); + return std::nullopt; + } + + return value; + } + catch (const std::exception& e) + { + CLog::LogF(LOGERROR, "exception reading from '{}': {}", m_path, e.what()); + return std::nullopt; + } + } + +private: + std::string m_path; +}; + +template<> +std::optional<std::string> CSysfsPath::Get<std::string>(); diff --git a/xbmc/platform/linux/TimeUtils.cpp b/xbmc/platform/linux/TimeUtils.cpp new file mode 100644 index 0000000..55cce92 --- /dev/null +++ b/xbmc/platform/linux/TimeUtils.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TimeUtils.h" + +std::int64_t KODI::LINUX::TimespecDifference(timespec const& start, timespec const& end) +{ + return (end.tv_sec - start.tv_sec) * UINT64_C(1000000000) + (end.tv_nsec - start.tv_nsec); +}
\ No newline at end of file diff --git a/xbmc/platform/linux/TimeUtils.h b/xbmc/platform/linux/TimeUtils.h new file mode 100644 index 0000000..abbeb13 --- /dev/null +++ b/xbmc/platform/linux/TimeUtils.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstdint> +#include <time.h> + +namespace KODI +{ +namespace LINUX +{ + +/** + * Calculate difference between two timespecs in nanoseconds + * \param start earlier time + * \param end later time + * \return (end - start) in nanoseconds + */ +std::int64_t TimespecDifference(timespec const& start, timespec const& end); + +} +} diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt new file mode 100644 index 0000000..ebb2ccd --- /dev/null +++ b/xbmc/platform/linux/input/CMakeLists.txt @@ -0,0 +1,27 @@ +set(SOURCES "") +set(HEADERS "") + +if(LIRCCLIENT_FOUND) + list(APPEND SOURCES LIRC.cpp) + list(APPEND HEADERS LIRC.h) +endif() + +if("gbm" IN_LIST CORE_PLATFORM_NAME_LC) + if(LIBINPUT_FOUND) + list(APPEND SOURCES LibInputHandler.cpp + LibInputKeyboard.cpp + LibInputPointer.cpp + LibInputSettings.cpp + LibInputTouch.cpp) + + list(APPEND HEADERS LibInputHandler.h + LibInputKeyboard.h + LibInputPointer.h + LibInputSettings.h + LibInputTouch.h) + endif() +endif() + +if(SOURCES) + core_add_library(input_linux) +endif() diff --git a/xbmc/platform/linux/input/LIRC.cpp b/xbmc/platform/linux/input/LIRC.cpp new file mode 100644 index 0000000..6900f5c --- /dev/null +++ b/xbmc/platform/linux/input/LIRC.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2007-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LIRC.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/SingleLock.h" +#include "utils/log.h" + +#include <mutex> + +#include <fcntl.h> +#include <lirc/lirc_client.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "PlatformDefs.h" + +using namespace std::chrono_literals; + +CLirc::CLirc() : CThread("Lirc") +{ +} + +CLirc::~CLirc() +{ + { + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_fd > 0) + shutdown(m_fd, SHUT_RDWR); + } + StopThread(); +} + +void CLirc::Start() +{ + Create(); + SetPriority(ThreadPriority::LOWEST); +} + +void CLirc::Process() +{ + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + throw std::runtime_error("CSettingsComponent needs to exist before starting CLirc"); + + auto settings = settingsComponent->GetSettings(); + if (!settings) + throw std::runtime_error("CSettings needs to exist before starting CLirc"); + + while (!settings->IsLoaded()) + CThread::Sleep(1000ms); + + m_profileId = settingsComponent->GetProfileManager()->GetCurrentProfileId(); + m_irTranslator.Load("Lircmap.xml"); + + // make sure work-around (CheckDaemon) uses the same socket path as lirc_init + const char* socket_path = getenv("LIRC_SOCKET_PATH"); + socket_path = socket_path ? socket_path : "/var/run/lirc/lircd"; + setenv("LIRC_SOCKET_PATH", socket_path, 0); + + while (!m_bStop) + { + { + std::unique_lock<CCriticalSection> lock(m_critSection); + + // lirc_client is buggy because it does not close socket, if connect fails + // work around by checking if daemon is running before calling lirc_init + if (!CheckDaemon()) + { + CSingleExit lock(m_critSection); + CThread::Sleep(1000ms); + continue; + } + + m_fd = lirc_init(const_cast<char*>("kodi"), 0); + if (m_fd <= 0) + { + CSingleExit lock(m_critSection); + CThread::Sleep(1000ms); + continue; + } + } + + char *code; + while (!m_bStop) + { + int ret = lirc_nextcode(&code); + if (ret < 0) + { + lirc_deinit(); + CThread::Sleep(1000ms); + break; + } + if (code != nullptr) + { + int profileId = settingsComponent->GetProfileManager()->GetCurrentProfileId(); + if (m_profileId != profileId) + { + m_profileId = profileId; + m_irTranslator.Load("Lircmap.xml"); + } + ProcessCode(code); + free(code); + } + } + } + + lirc_deinit(); +} + +void CLirc::ProcessCode(char *buf) +{ + // Parse the result. Sample line: + // 000000037ff07bdd 00 OK mceusb + char scanCode[128]; + char buttonName[128]; + char repeatStr[4]; + char deviceName[128]; + sscanf(buf, "%s %s %s %s", &scanCode[0], &repeatStr[0], &buttonName[0], &deviceName[0]); + + // Some template LIRC configuration have button names in apostrophes or quotes. + // If we got a quoted button name, strip 'em + unsigned int buttonNameLen = strlen(buttonName); + if (buttonNameLen > 2 && + ((buttonName[0] == '\'' && buttonName[buttonNameLen-1] == '\'') || + ((buttonName[0] == '"' && buttonName[buttonNameLen-1] == '"')))) + { + memmove(buttonName, buttonName + 1, buttonNameLen - 2); + buttonName[buttonNameLen - 2] = '\0'; + } + + int button = m_irTranslator.TranslateButton(deviceName, buttonName); + + char *end = nullptr; + long repeat = strtol(repeatStr, &end, 16); + if (!end || *end != 0) + CLog::Log(LOGERROR, "LIRC: invalid non-numeric character in expression {}", repeatStr); + + if (repeat == 0) + { + CLog::Log(LOGDEBUG, "LIRC: - NEW {} {} {} {} ({})", &scanCode[0], &repeatStr[0], &buttonName[0], + &deviceName[0], buttonName); + m_firstClickTime = std::chrono::steady_clock::now(); + + XBMC_Event newEvent = {}; + newEvent.type = XBMC_BUTTON; + newEvent.keybutton.button = button; + newEvent.keybutton.holdtime = 0; + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); + return; + } + else if (repeat > CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_remoteDelay) + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_BUTTON; + newEvent.keybutton.button = button; + + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_firstClickTime); + + newEvent.keybutton.holdtime = duration.count(); + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); + } +} + +bool CLirc::CheckDaemon() +{ + const char* socket_path = getenv("LIRC_SOCKET_PATH"); + + struct sockaddr_un addr_un; + if (strlen(socket_path) + 1 > sizeof(addr_un.sun_path)) + { + return false; + } + + addr_un.sun_family = AF_UNIX; + strcpy(addr_un.sun_path, socket_path); + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + return false; + } + + if (connect(fd, reinterpret_cast<struct sockaddr*>(&addr_un), sizeof(addr_un)) == -1) + { + close(fd); + return false; + } + + close(fd); + return true; +} diff --git a/xbmc/platform/linux/input/LIRC.h b/xbmc/platform/linux/input/LIRC.h new file mode 100644 index 0000000..f08638d --- /dev/null +++ b/xbmc/platform/linux/input/LIRC.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "input/IRTranslator.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <chrono> +#include <string> + +class CLirc : CThread +{ +public: + CLirc(); + ~CLirc() override; + void Start(); + +protected: + void Process() override; + void ProcessCode(char *buf); + bool CheckDaemon(); + + int m_fd = -1; + std::chrono::time_point<std::chrono::steady_clock> m_firstClickTime; + CCriticalSection m_critSection; + CIRTranslator m_irTranslator; + int m_profileId; +}; diff --git a/xbmc/platform/linux/input/LibInputHandler.cpp b/xbmc/platform/linux/input/LibInputHandler.cpp new file mode 100644 index 0000000..a8c39f5 --- /dev/null +++ b/xbmc/platform/linux/input/LibInputHandler.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LibInputHandler.h" + +#include "LibInputKeyboard.h" +#include "LibInputPointer.h" +#include "LibInputSettings.h" +#include "LibInputTouch.h" +#include "ServiceBroker.h" +#include "interfaces/AnnouncementManager.h" +#include "utils/log.h" + +#include <algorithm> +#include <string.h> + +#include <fcntl.h> +#include <linux/input.h> +#include <sys/epoll.h> +#include <sys/ioctl.h> +#include <unistd.h> + +static int open_restricted(const char *path, int flags, void __attribute__((unused)) *user_data) +{ + int fd = open(path, flags); + + if (fd < 0) + { + CLog::Log(LOGERROR, "{} - failed to open {} ({})", __FUNCTION__, path, strerror(errno)); + return -errno; + } + + auto ret = ioctl(fd, EVIOCGRAB, (void*)1); + if (ret < 0) + { + CLog::Log(LOGDEBUG, "{} - grab requested, but failed for {} ({})", __FUNCTION__, path, + strerror(errno)); + } + + return fd; +} + +static void close_restricted(int fd, void __attribute__((unused)) *user_data) +{ + close(fd); +} + +static const struct libinput_interface m_interface = +{ + open_restricted, + close_restricted, +}; + +static void LogHandler(libinput __attribute__((unused)) *libinput, libinput_log_priority priority, const char *format, va_list args) +{ + if (priority == LIBINPUT_LOG_PRIORITY_DEBUG) + { + char buf[512]; + int n = vsnprintf(buf, sizeof(buf), format, args); + if (n > 0) + CLog::Log(LOGDEBUG, "libinput: {}", buf); + } +} + +CLibInputHandler::CLibInputHandler() : CThread("libinput") +{ + m_udev = udev_new(); + if (!m_udev) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to get udev context for libinput", + __FUNCTION__); + return; + } + + m_li = libinput_udev_create_context(&m_interface, nullptr, m_udev); + if (!m_li) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to get libinput context", __FUNCTION__); + return; + } + + libinput_log_set_handler(m_li, LogHandler); + libinput_log_set_priority(m_li, LIBINPUT_LOG_PRIORITY_DEBUG); + + auto ret = libinput_udev_assign_seat(m_li, "seat0"); + if (ret < 0) + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to assign seat", __FUNCTION__); + + m_liFd = libinput_get_fd(m_li); + + m_keyboard.reset(new CLibInputKeyboard()); + m_pointer.reset(new CLibInputPointer()); + m_touch.reset(new CLibInputTouch()); + m_settings.reset(new CLibInputSettings(this)); + + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +CLibInputHandler::~CLibInputHandler() +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + StopThread(); + + libinput_unref(m_li); + udev_unref(m_udev); +} + +void CLibInputHandler::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag & (ANNOUNCEMENT::System)) + { + if (message == "OnSleep") + libinput_suspend(m_li); + else if (message == "OnWake") + { + auto ret = libinput_resume(m_li); + if (ret < 0) + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to resume monitoring", __FUNCTION__); + } + } +} + +bool CLibInputHandler::SetKeymap(const std::string& layout) +{ + return m_keyboard->SetKeymap(layout); +} + +void CLibInputHandler::Start() +{ + Create(); + SetPriority(ThreadPriority::LOWEST); +} + +void CLibInputHandler::Process() +{ + int epollFd = epoll_create1(EPOLL_CLOEXEC); + if (epollFd < 0) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to create epoll file descriptor: {}", + __FUNCTION__, strerror(-errno)); + return; + } + + epoll_event event; + event.events = EPOLLIN; + event.data.fd = m_liFd; + + auto ret = epoll_ctl(epollFd, EPOLL_CTL_ADD, m_liFd, &event); + if (ret < 0) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to add file descriptor to epoll: {}", + __FUNCTION__, strerror(-errno)); + close(epollFd); + return; + } + + while (!m_bStop) + { + epoll_wait(epollFd, &event, 1, 200); + + ret = libinput_dispatch(m_li); + if (ret < 0) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - libinput_dispatch failed: {}", __FUNCTION__, + strerror(-errno)); + close(epollFd); + return; + } + + libinput_event *ev; + while ((ev = libinput_get_event(m_li)) != nullptr) + { + ProcessEvent(ev); + libinput_event_destroy(ev); + } + } + + ret = close(epollFd); + if (ret < 0) + { + CLog::Log(LOGERROR, "CLibInputHandler::{} - failed to close epoll file descriptor: {}", + __FUNCTION__, strerror(-errno)); + return; + } +} + +void CLibInputHandler::ProcessEvent(libinput_event *ev) +{ + libinput_event_type type = libinput_event_get_type(ev); + libinput_device *dev = libinput_event_get_device(ev); + + switch (type) + { + case LIBINPUT_EVENT_DEVICE_ADDED: + DeviceAdded(dev); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + DeviceRemoved(dev); + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + m_pointer->ProcessButton(libinput_event_get_pointer_event(ev)); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + m_pointer->ProcessMotion(libinput_event_get_pointer_event(ev)); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + m_pointer->ProcessMotionAbsolute(libinput_event_get_pointer_event(ev)); + break; + case LIBINPUT_EVENT_POINTER_AXIS: + m_pointer->ProcessAxis(libinput_event_get_pointer_event(ev)); + break; + case LIBINPUT_EVENT_KEYBOARD_KEY: + m_keyboard->ProcessKey(libinput_event_get_keyboard_event(ev)); + m_keyboard->UpdateLeds(dev); + break; + case LIBINPUT_EVENT_TOUCH_DOWN: + m_touch->ProcessTouchDown(libinput_event_get_touch_event(ev)); + break; + case LIBINPUT_EVENT_TOUCH_MOTION: + m_touch->ProcessTouchMotion(libinput_event_get_touch_event(ev)); + break; + case LIBINPUT_EVENT_TOUCH_UP: + m_touch->ProcessTouchUp(libinput_event_get_touch_event(ev)); + break; + case LIBINPUT_EVENT_TOUCH_CANCEL: + m_touch->ProcessTouchCancel(libinput_event_get_touch_event(ev)); + break; + case LIBINPUT_EVENT_TOUCH_FRAME: + m_touch->ProcessTouchFrame(libinput_event_get_touch_event(ev)); + break; + + default: + break; + } +} + +void CLibInputHandler::DeviceAdded(libinput_device *dev) +{ + const char *sysname = libinput_device_get_sysname(dev); + const char *name = libinput_device_get_name(dev); + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_TOUCH)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - touch type device added: {} ({})", __FUNCTION__, + name, sysname); + m_devices.push_back(libinput_device_ref(dev)); + } + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_POINTER)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - pointer type device added: {} ({})", __FUNCTION__, + name, sysname); + m_devices.push_back(libinput_device_ref(dev)); + } + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - keyboard type device added: {} ({})", __FUNCTION__, + name, sysname); + m_devices.push_back(libinput_device_ref(dev)); + m_keyboard->GetRepeat(dev); + } +} + +void CLibInputHandler::DeviceRemoved(libinput_device *dev) +{ + const char *sysname = libinput_device_get_sysname(dev); + const char *name = libinput_device_get_name(dev); + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_TOUCH)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - touch type device removed: {} ({})", __FUNCTION__, + name, sysname); + auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev)); + m_devices.erase(device); + } + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_POINTER)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - pointer type device removed: {} ({})", __FUNCTION__, + name, sysname); + auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev)); + m_devices.erase(device); + } + + if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) + { + CLog::Log(LOGDEBUG, "CLibInputHandler::{} - keyboard type device removed: {} ({})", + __FUNCTION__, name, sysname); + auto device = std::find(m_devices.begin(), m_devices.end(), libinput_device_unref(dev)); + m_devices.erase(device); + } +} diff --git a/xbmc/platform/linux/input/LibInputHandler.h b/xbmc/platform/linux/input/LibInputHandler.h new file mode 100644 index 0000000..659d086 --- /dev/null +++ b/xbmc/platform/linux/input/LibInputHandler.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/IAnnouncer.h" +#include "threads/Thread.h" + +#include <memory> +#include <vector> + +#include <libinput.h> +#include <libudev.h> + +class CLibInputKeyboard; +class CLibInputPointer; +class CLibInputSettings; +class CLibInputTouch; + +class CLibInputHandler : CThread, public ANNOUNCEMENT::IAnnouncer +{ +public: + CLibInputHandler(); + ~CLibInputHandler() override; + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + + void Start(); + + bool SetKeymap(const std::string& layout); + +private: + void Process() override; + void ProcessEvent(libinput_event *ev); + void DeviceAdded(libinput_device *dev); + void DeviceRemoved(libinput_device *dev); + + udev *m_udev; + libinput *m_li; + int m_liFd; + + std::unique_ptr<CLibInputKeyboard> m_keyboard; + std::unique_ptr<CLibInputPointer> m_pointer; + std::unique_ptr<CLibInputSettings> m_settings; + std::unique_ptr<CLibInputTouch> m_touch; + std::vector<libinput_device*> m_devices; +}; + diff --git a/xbmc/platform/linux/input/LibInputKeyboard.cpp b/xbmc/platform/linux/input/LibInputKeyboard.cpp new file mode 100644 index 0000000..657588a --- /dev/null +++ b/xbmc/platform/linux/input/LibInputKeyboard.cpp @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LibInputKeyboard.h" + +#include "LibInputSettings.h" +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/log.h" + +#include <algorithm> +#include <map> +#include <string.h> + +#include <fcntl.h> +#include <linux/input.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <xkbcommon/xkbcommon-keysyms.h> +#include <xkbcommon/xkbcommon-names.h> + +namespace +{ +constexpr int REPEAT_DELAY = 400; +constexpr int REPEAT_RATE = 80; + +static const std::map<xkb_keysym_t, XBMCKey> xkbMap = +{ + // Function keys before start of ASCII printable character range + { XKB_KEY_BackSpace, XBMCK_BACKSPACE }, + { XKB_KEY_Tab, XBMCK_TAB }, + { XKB_KEY_Clear, XBMCK_CLEAR }, + { XKB_KEY_Return, XBMCK_RETURN }, + { XKB_KEY_Pause, XBMCK_PAUSE }, + { XKB_KEY_Escape, XBMCK_ESCAPE }, + + // ASCII printable range - not included here + + // Function keys after end of ASCII printable character range + { XKB_KEY_Delete, XBMCK_DELETE }, + + // Multimedia keys + { XKB_KEY_XF86Back, XBMCK_BROWSER_BACK }, + { XKB_KEY_XF86Forward, XBMCK_BROWSER_FORWARD }, + { XKB_KEY_XF86Refresh, XBMCK_BROWSER_REFRESH }, + { XKB_KEY_XF86Stop, XBMCK_BROWSER_STOP }, + { XKB_KEY_XF86Search, XBMCK_BROWSER_SEARCH }, + // XKB_KEY_XF86Favorites could be XBMCK_BROWSER_FAVORITES or XBMCK_FAVORITES, + // XBMCK_FAVORITES was chosen here because it is more general + { XKB_KEY_XF86HomePage, XBMCK_BROWSER_HOME }, + { XKB_KEY_XF86AudioMute, XBMCK_VOLUME_MUTE }, + { XKB_KEY_XF86AudioLowerVolume, XBMCK_VOLUME_DOWN }, + { XKB_KEY_XF86AudioRaiseVolume, XBMCK_VOLUME_UP }, + { XKB_KEY_XF86AudioNext, XBMCK_MEDIA_NEXT_TRACK }, + { XKB_KEY_XF86AudioPrev, XBMCK_MEDIA_PREV_TRACK }, + { XKB_KEY_XF86AudioStop, XBMCK_MEDIA_STOP }, + { XKB_KEY_XF86AudioPause, XBMCK_MEDIA_PLAY_PAUSE }, + { XKB_KEY_XF86Mail, XBMCK_LAUNCH_MAIL }, + { XKB_KEY_XF86Select, XBMCK_LAUNCH_MEDIA_SELECT }, + { XKB_KEY_XF86Launch0, XBMCK_LAUNCH_APP1 }, + { XKB_KEY_XF86Launch1, XBMCK_LAUNCH_APP2 }, + { XKB_KEY_XF86WWW, XBMCK_LAUNCH_FILE_BROWSER }, + { XKB_KEY_XF86AudioMedia, XBMCK_LAUNCH_MEDIA_CENTER }, + { XKB_KEY_XF86AudioRewind, XBMCK_MEDIA_REWIND }, + { XKB_KEY_XF86AudioForward, XBMCK_MEDIA_FASTFORWARD }, + + // Numeric keypad + { XKB_KEY_KP_0, XBMCK_KP0 }, + { XKB_KEY_KP_1, XBMCK_KP1 }, + { XKB_KEY_KP_2, XBMCK_KP2 }, + { XKB_KEY_KP_3, XBMCK_KP3 }, + { XKB_KEY_KP_4, XBMCK_KP4 }, + { XKB_KEY_KP_5, XBMCK_KP5 }, + { XKB_KEY_KP_6, XBMCK_KP6 }, + { XKB_KEY_KP_7, XBMCK_KP7 }, + { XKB_KEY_KP_8, XBMCK_KP8 }, + { XKB_KEY_KP_9, XBMCK_KP9 }, + { XKB_KEY_KP_Decimal, XBMCK_KP_PERIOD }, + { XKB_KEY_KP_Divide, XBMCK_KP_DIVIDE }, + { XKB_KEY_KP_Multiply, XBMCK_KP_MULTIPLY }, + { XKB_KEY_KP_Subtract, XBMCK_KP_MINUS }, + { XKB_KEY_KP_Add, XBMCK_KP_PLUS }, + { XKB_KEY_KP_Enter, XBMCK_KP_ENTER }, + { XKB_KEY_KP_Equal, XBMCK_KP_EQUALS }, + + // Arrows + Home/End pad + { XKB_KEY_Up, XBMCK_UP }, + { XKB_KEY_Down, XBMCK_DOWN }, + { XKB_KEY_Right, XBMCK_RIGHT }, + { XKB_KEY_Left, XBMCK_LEFT }, + { XKB_KEY_Insert, XBMCK_INSERT }, + { XKB_KEY_Home, XBMCK_HOME }, + { XKB_KEY_End, XBMCK_END }, + { XKB_KEY_Page_Up, XBMCK_PAGEUP }, + { XKB_KEY_Page_Down, XBMCK_PAGEDOWN }, + + // Key state modifier keys + { XKB_KEY_Num_Lock, XBMCK_NUMLOCK }, + { XKB_KEY_Caps_Lock, XBMCK_CAPSLOCK }, + { XKB_KEY_Scroll_Lock, XBMCK_SCROLLOCK }, + { XKB_KEY_Shift_R, XBMCK_RSHIFT }, + { XKB_KEY_Shift_L, XBMCK_LSHIFT }, + { XKB_KEY_Control_R, XBMCK_RCTRL }, + { XKB_KEY_Control_L, XBMCK_LCTRL }, + { XKB_KEY_Alt_R, XBMCK_RALT }, + { XKB_KEY_Alt_L, XBMCK_LALT }, + { XKB_KEY_Meta_R, XBMCK_RMETA }, + { XKB_KEY_Meta_L, XBMCK_LMETA }, + { XKB_KEY_Super_R, XBMCK_RSUPER }, + { XKB_KEY_Super_L, XBMCK_LSUPER }, + // XKB does not have XBMCK_MODE/"Alt Gr" - probably equal to XKB_KEY_Alt_R + { XKB_KEY_Multi_key, XBMCK_COMPOSE }, + + // Miscellaneous function keys + { XKB_KEY_Help, XBMCK_HELP }, + { XKB_KEY_Print, XBMCK_PRINT }, + { XKB_KEY_Sys_Req, XBMCK_SYSREQ}, + { XKB_KEY_Break, XBMCK_BREAK }, + { XKB_KEY_Menu, XBMCK_MENU }, + { XKB_KEY_XF86PowerOff, XBMCK_POWER }, + { XKB_KEY_EcuSign, XBMCK_EURO }, + { XKB_KEY_Undo, XBMCK_UNDO }, + { XKB_KEY_XF86Sleep, XBMCK_SLEEP }, + // Unmapped: XBMCK_GUIDE, XBMCK_SETTINGS, XBMCK_INFO + { XKB_KEY_XF86Red, XBMCK_RED }, + { XKB_KEY_XF86Green, XBMCK_GREEN }, + { XKB_KEY_XF86Yellow, XBMCK_YELLOW }, + { XKB_KEY_XF86Blue, XBMCK_BLUE }, + // Unmapped: XBMCK_ZOOM, XBMCK_TEXT + { XKB_KEY_XF86Favorites, XBMCK_FAVORITES }, + { XKB_KEY_XF86HomePage, XBMCK_HOMEPAGE }, + // Unmapped: XBMCK_CONFIG, XBMCK_EPG + + // Media keys + { XKB_KEY_XF86Eject, XBMCK_EJECT }, + { XKB_KEY_Cancel, XBMCK_STOP }, + { XKB_KEY_XF86AudioRecord, XBMCK_RECORD }, + // XBMCK_REWIND clashes with XBMCK_MEDIA_REWIND + { XKB_KEY_XF86Phone, XBMCK_PHONE }, + { XKB_KEY_XF86AudioPlay, XBMCK_PLAY }, + { XKB_KEY_XF86AudioRandomPlay, XBMCK_SHUFFLE } + // XBMCK_FASTFORWARD clashes with XBMCK_MEDIA_FASTFORWARD +}; +} // namespace + +CLibInputKeyboard::CLibInputKeyboard() + : m_repeatTimer(std::bind(&CLibInputKeyboard::KeyRepeatTimeout, this)) +{ + m_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!m_ctx) + { + CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to create xkb context", __FUNCTION__); + return; + } + + std::string layout = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CLibInputSettings::SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT); + + if (!SetKeymap(layout)) + { + CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed set default keymap", __FUNCTION__); + return; + } +} + +CLibInputKeyboard::~CLibInputKeyboard() +{ + xkb_state_unref(m_state); + xkb_keymap_unref(m_keymap); + xkb_context_unref(m_ctx); +} + +bool CLibInputKeyboard::SetKeymap(const std::string& layout) +{ + xkb_state_unref(m_state); + xkb_keymap_unref(m_keymap); + + xkb_rule_names names; + + names.rules = nullptr; + names.model = nullptr; + names.layout = layout.c_str(); + names.variant = nullptr; + names.options = nullptr; + + m_keymap = xkb_keymap_new_from_names(m_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!m_keymap) + { + CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to compile keymap", __FUNCTION__); + return false; + } + + m_state = xkb_state_new(m_keymap); + if (!m_state) + { + CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to create xkb state", __FUNCTION__); + return false; + } + + m_modindex[0] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL); + m_modindex[1] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT); + m_modindex[2] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT); + m_modindex[3] = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO); + + m_ledindex[0] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM); + m_ledindex[1] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS); + m_ledindex[2] = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL); + + m_leds = 0; + + return true; +} + +void CLibInputKeyboard::ProcessKey(libinput_event_keyboard *e) +{ + if (!m_ctx || !m_keymap || !m_state) + return; + + XBMC_Event event = {}; + + const uint32_t xkbkey = libinput_event_keyboard_get_key(e) + 8; + const bool pressed = libinput_event_keyboard_get_key_state(e) == LIBINPUT_KEY_STATE_PRESSED; + + event.type = pressed ? XBMC_KEYDOWN : XBMC_KEYUP; + xkb_state_update_key(m_state, xkbkey, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); + + const xkb_keysym_t keysym = xkb_state_key_get_one_sym(m_state, xkbkey); + + int mod = XBMCKMOD_NONE; + + xkb_state_component modtype = xkb_state_component(XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + if (xkb_state_mod_index_is_active(m_state, m_modindex[0], modtype) && ((keysym != XBMCK_LCTRL) || !pressed)) + mod |= XBMCKMOD_CTRL; + if (xkb_state_mod_index_is_active(m_state, m_modindex[0], modtype) && ((keysym != XBMCK_RCTRL) || !pressed)) + mod |= XBMCKMOD_CTRL; + if (xkb_state_mod_index_is_active(m_state, m_modindex[1], modtype) && ((keysym != XBMCK_LALT) || !pressed)) + mod |= XBMCKMOD_ALT; + if (xkb_state_mod_index_is_active(m_state, m_modindex[1], modtype) && ((keysym != XBMCK_RALT) || !pressed)) + mod |= XBMCKMOD_ALT; + if (xkb_state_mod_index_is_active(m_state, m_modindex[2], modtype) && ((keysym != XBMCK_LSHIFT) || !pressed)) + mod |= XBMCKMOD_SHIFT; + if (xkb_state_mod_index_is_active(m_state, m_modindex[2], modtype) && ((keysym != XBMCK_RSHIFT) || !pressed)) + mod |= XBMCKMOD_SHIFT; + if (xkb_state_mod_index_is_active(m_state, m_modindex[3], modtype) && ((keysym != XBMCK_LMETA) || !pressed)) + mod |= XBMCKMOD_META; + if (xkb_state_mod_index_is_active(m_state, m_modindex[3], modtype) && ((keysym != XBMCK_RMETA) || !pressed)) + mod |= XBMCKMOD_META; + + m_leds = 0; + + if (xkb_state_led_index_is_active(m_state, m_ledindex[0]) && ((keysym != XBMCK_NUMLOCK) || !pressed)) + { + m_leds |= LIBINPUT_LED_NUM_LOCK; + mod |= XBMCKMOD_NUM; + } + if (xkb_state_led_index_is_active(m_state, m_ledindex[1]) && ((keysym != XBMCK_CAPSLOCK) || !pressed)) + { + m_leds |= LIBINPUT_LED_CAPS_LOCK; + mod |= XBMCKMOD_CAPS; + } + if (xkb_state_led_index_is_active(m_state, m_ledindex[2]) && ((keysym != XBMCK_SCROLLOCK) || !pressed)) + { + m_leds |= LIBINPUT_LED_SCROLL_LOCK; + //mod |= XBMCK_SCROLLOCK; + } + + uint32_t unicode = xkb_state_key_get_utf32(m_state, xkbkey); + if (unicode > std::numeric_limits<std::uint16_t>::max()) + { + // Kodi event system only supports UTF16, so ignore the codepoint if it does not fit + unicode = 0; + } + + uint32_t scancode = libinput_event_keyboard_get_key(e); + if (scancode > std::numeric_limits<unsigned char>::max()) + { + // Kodi scancodes are limited to unsigned char, pretend the scancode is unknown on overflow + scancode = 0; + } + + event.key.keysym.mod = XBMCMod(mod); + event.key.keysym.sym = XBMCKeyForKeysym(keysym, scancode); + event.key.keysym.scancode = scancode; + event.key.keysym.unicode = unicode; + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); + + if (pressed && xkb_keymap_key_repeats(m_keymap, xkbkey)) + { + libinput_event *ev = libinput_event_keyboard_get_base_event(e); + libinput_device *dev = libinput_event_get_device(ev); + auto data = m_repeatData.find(dev); + if (data != m_repeatData.end()) + { + CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - using delay: {}ms repeat: {}ms", __FUNCTION__, + data->second.at(0), data->second.at(1)); + + m_repeatRate = data->second.at(1); + m_repeatTimer.Stop(true); + m_repeatEvent = event; + m_repeatTimer.Start(std::chrono::milliseconds(data->second.at(0)), false); + } + } + else + { + m_repeatTimer.Stop(); + } +} + +XBMCKey CLibInputKeyboard::XBMCKeyForKeysym(xkb_keysym_t sym, uint32_t scancode) +{ + if (sym >= 'A' && sym <= 'Z') + { + return static_cast<XBMCKey> (sym + 'a' - 'A'); + } + else if (sym >= XKB_KEY_space && sym <= XKB_KEY_asciitilde) + { + return static_cast<XBMCKey> (sym); + } + else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F15) + { + return static_cast<XBMCKey> (XBMCK_F1 + ((int)sym - XKB_KEY_F1)); + } + + auto xkbmapping = xkbMap.find(sym); + if (xkbmapping != xkbMap.end()) + return xkbmapping->second; + + return XBMCK_UNKNOWN; +} + +void CLibInputKeyboard::KeyRepeatTimeout() +{ + m_repeatTimer.RestartAsync(std::chrono::milliseconds(m_repeatRate)); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(m_repeatEvent); +} + +void CLibInputKeyboard::UpdateLeds(libinput_device *dev) +{ + libinput_device_led_update(dev, static_cast<libinput_led>(m_leds)); +} + +void CLibInputKeyboard::GetRepeat(libinput_device *dev) +{ + int kbdrep[2] = {REPEAT_DELAY, REPEAT_RATE}; + const char *name = libinput_device_get_name(dev); + const char *sysname = libinput_device_get_sysname(dev); + std::string path("/dev/input/"); + path.append(sysname); + auto fd = open(path.c_str(), O_RDONLY); + + if (fd < 0) + { + CLog::Log(LOGERROR, "CLibInputKeyboard::{} - failed to open {} ({})", __FUNCTION__, sysname, + strerror(errno)); + } + else + { + auto ret = ioctl(fd, EVIOCGREP, &kbdrep); + if (ret < 0) + CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - could not get key repeat for {} ({})", + __FUNCTION__, sysname, strerror(errno)); + + CLog::Log(LOGDEBUG, "CLibInputKeyboard::{} - delay: {}ms repeat: {}ms for {} ({})", + __FUNCTION__, kbdrep[0], kbdrep[1], name, sysname); + close(fd); + } + + std::vector<int> kbdrepvec(std::begin(kbdrep), std::end(kbdrep)); + + auto data = m_repeatData.find(dev); + if (data == m_repeatData.end()) + { + m_repeatData.insert(std::make_pair(dev, kbdrepvec)); + } +} diff --git a/xbmc/platform/linux/input/LibInputKeyboard.h b/xbmc/platform/linux/input/LibInputKeyboard.h new file mode 100644 index 0000000..76aaeff --- /dev/null +++ b/xbmc/platform/linux/input/LibInputKeyboard.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/Timer.h" +#include "windowing/XBMC_events.h" + +#include <map> +#include <vector> + +#include <libinput.h> +#include <xkbcommon/xkbcommon.h> + +class CLibInputKeyboard +{ +public: + CLibInputKeyboard(); + ~CLibInputKeyboard(); + + void ProcessKey(libinput_event_keyboard *e); + void UpdateLeds(libinput_device *dev); + void GetRepeat(libinput_device *dev); + + bool SetKeymap(const std::string& layout); + +private: + XBMCKey XBMCKeyForKeysym(xkb_keysym_t sym, uint32_t scancode); + void KeyRepeatTimeout(); + + xkb_context *m_ctx = nullptr; + xkb_keymap *m_keymap = nullptr; + xkb_state *m_state = nullptr; + xkb_mod_index_t m_modindex[4]; + xkb_led_index_t m_ledindex[3]; + + int m_leds; + + XBMC_Event m_repeatEvent; + std::map<libinput_device*, std::vector<int>> m_repeatData; + CTimer m_repeatTimer; + int m_repeatRate; +}; diff --git a/xbmc/platform/linux/input/LibInputPointer.cpp b/xbmc/platform/linux/input/LibInputPointer.cpp new file mode 100644 index 0000000..42d2bbe --- /dev/null +++ b/xbmc/platform/linux/input/LibInputPointer.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LibInputPointer.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "input/mouse/MouseStat.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include <algorithm> +#include <string.h> + +#include <linux/input.h> + +void CLibInputPointer::ProcessButton(libinput_event_pointer *e) +{ + const uint32_t b = libinput_event_pointer_get_button(e); + const bool pressed = libinput_event_pointer_get_button_state(e) == LIBINPUT_BUTTON_STATE_PRESSED; + unsigned char xbmc_button = 0; + + switch (b) + { + case BTN_LEFT: + xbmc_button = XBMC_BUTTON_LEFT; + break; + case BTN_RIGHT: + xbmc_button = XBMC_BUTTON_RIGHT; + break; + case BTN_MIDDLE: + xbmc_button = XBMC_BUTTON_MIDDLE; + break; + case BTN_SIDE: + xbmc_button = XBMC_BUTTON_X1; + break; + case BTN_EXTRA: + xbmc_button = XBMC_BUTTON_X2; + break; + case BTN_FORWARD: + xbmc_button = XBMC_BUTTON_X3; + break; + case BTN_BACK: + xbmc_button = XBMC_BUTTON_X4; + break; + default: + break; + } + + XBMC_Event event = {}; + + event.type = pressed ? XBMC_MOUSEBUTTONDOWN : XBMC_MOUSEBUTTONUP; + event.button.button = xbmc_button; + event.button.x = static_cast<uint16_t>(m_pos.X); + event.button.y = static_cast<uint16_t>(m_pos.Y); + + CLog::Log(LOGDEBUG, + "CLibInputPointer::{} - event.type: {}, event.button.button: {}, event.button.x: {}, " + "event.button.y: {}", + __FUNCTION__, event.type, event.button.button, event.button.x, event.button.y); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); +} + +void CLibInputPointer::ProcessMotion(libinput_event_pointer *e) +{ + const double dx = libinput_event_pointer_get_dx(e); + const double dy = libinput_event_pointer_get_dy(e); + + m_pos.X += static_cast<int>(dx); + m_pos.Y += static_cast<int>(dy); + + // limit the mouse to the screen width + m_pos.X = std::min(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), m_pos.X); + m_pos.X = std::max(0, m_pos.X); + + // limit the mouse to the screen height + m_pos.Y = std::min(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), m_pos.Y); + m_pos.Y = std::max(0, m_pos.Y); + + XBMC_Event event = {}; + + event.type = XBMC_MOUSEMOTION; + event.motion.x = static_cast<uint16_t>(m_pos.X); + event.motion.y = static_cast<uint16_t>(m_pos.Y); + + CLog::Log(LOGDEBUG, + "CLibInputPointer::{} - event.type: {}, event.motion.x: {}, event.motion.y: {}", + __FUNCTION__, event.type, event.motion.x, event.motion.y); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); +} + +void CLibInputPointer::ProcessMotionAbsolute(libinput_event_pointer *e) +{ + m_pos.X = static_cast<int>(libinput_event_pointer_get_absolute_x_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth())); + m_pos.Y = static_cast<int>(libinput_event_pointer_get_absolute_y_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())); + + XBMC_Event event = {}; + event.type = XBMC_MOUSEMOTION; + event.motion.x = static_cast<uint16_t>(m_pos.X); + event.motion.y = static_cast<uint16_t>(m_pos.Y); + + CLog::Log(LOGDEBUG, + "CLibInputPointer::{} - event.type: {}, event.motion.x: {}, event.motion.y: {}", + __FUNCTION__, event.type, event.motion.x, event.motion.y); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); +} + +void CLibInputPointer::ProcessAxis(libinput_event_pointer *e) +{ + unsigned char scroll = 0; + if (!libinput_event_pointer_has_axis(e, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) + return; + + const double v = libinput_event_pointer_get_axis_value(e, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + if (v < 0) + scroll = XBMC_BUTTON_WHEELUP; + else + scroll = XBMC_BUTTON_WHEELDOWN; + + XBMC_Event event = {}; + + event.type = XBMC_MOUSEBUTTONDOWN; + event.button.button = scroll; + event.button.x = static_cast<uint16_t>(m_pos.X); + event.button.y = static_cast<uint16_t>(m_pos.Y); + + CLog::Log(LOGDEBUG, "CLibInputPointer::{} - scroll: {}, event.button.x: {}, event.button.y: {}", + __FUNCTION__, scroll == XBMC_BUTTON_WHEELUP ? "up" : "down", event.button.x, + event.button.y); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); + + event.type = XBMC_MOUSEBUTTONUP; + + if (appPort) + appPort->OnEvent(event); +} diff --git a/xbmc/platform/linux/input/LibInputPointer.h b/xbmc/platform/linux/input/LibInputPointer.h new file mode 100644 index 0000000..608b438 --- /dev/null +++ b/xbmc/platform/linux/input/LibInputPointer.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <libinput.h> + +struct pos +{ + int X; + int Y; +}; + +class CLibInputPointer +{ +public: + CLibInputPointer() = default; + ~CLibInputPointer() = default; + + void ProcessButton(libinput_event_pointer *e); + void ProcessMotion(libinput_event_pointer *e); + void ProcessMotionAbsolute(libinput_event_pointer *e); + void ProcessAxis(libinput_event_pointer *e); + +private: + struct pos m_pos = { 0, 0 }; +}; diff --git a/xbmc/platform/linux/input/LibInputSettings.cpp b/xbmc/platform/linux/input/LibInputSettings.cpp new file mode 100644 index 0000000..25a916e --- /dev/null +++ b/xbmc/platform/linux/input/LibInputSettings.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LibInputSettings.h" + +#include "LibInputHandler.h" +#include "ServiceBroker.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> + +const std::string CLibInputSettings::SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT = "input.libinputkeyboardlayout"; +static std::vector<StringSettingOption> layouts; + +namespace +{ + inline bool LayoutSort(const StringSettingOption& i, const StringSettingOption& j) + { + return (i.value < j.value); + } +} // unnamed namespace + +CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) : + m_libInputHandler(handler) +{ + const auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return; + + auto settingsManager = settings->GetSettingsManager(); + if (!settingsManager) + return; + + auto setting = settings->GetSetting(SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT); + if (!setting) + { + CLog::Log(LOGERROR, "Failed to load setting for: {}", SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT); + return; + } + + setting->SetVisible(true); + + std::set<std::string> settingSet; + settingSet.insert(SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT); + settingsManager->RegisterCallback(this, settingSet); + settingsManager->RegisterSettingOptionsFiller("libinputkeyboardlayout", + SettingOptionsKeyboardLayoutsFiller); + + /* load the keyboard layouts from xkeyboard-config */ + std::string xkbFile("/usr/share/X11/xkb/rules/base.xml"); + + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(xkbFile)) + { + CLog::Log(LOGWARNING, "CLibInputSettings: unable to open: {}", xkbFile); + return; + } + + const TiXmlElement* rootElement = xmlDoc.RootElement(); + if (!rootElement) + { + CLog::Log(LOGWARNING, "CLibInputSettings: missing or invalid XML root element in: {}", xkbFile); + return; + } + + if (rootElement->ValueStr() != "xkbConfigRegistry") + { + CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML root element {} in: {}", + rootElement->Value(), xkbFile); + return; + } + + const TiXmlElement* layoutListElement = rootElement->FirstChildElement("layoutList"); + if (!layoutListElement) + { + CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", + layoutListElement->Value(), xkbFile); + return; + } + + const TiXmlElement* layoutElement = layoutListElement->FirstChildElement("layout"); + while (layoutElement) + { + const TiXmlElement* configElement = layoutElement->FirstChildElement("configItem"); + if (!configElement) + { + CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", + layoutListElement->Value(), xkbFile); + return; + } + + const TiXmlElement* nameElement = configElement->FirstChildElement("name"); + if (!nameElement) + { + CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", + configElement->Value(), xkbFile); + return; + } + + const TiXmlElement* descriptionElement = configElement->FirstChildElement("description"); + if (!descriptionElement) + { + CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", + configElement->Value(), xkbFile); + return; + } + + std::string layout = nameElement->GetText(); + std::string layoutDescription = descriptionElement->GetText(); + + if (!layout.empty() && !layoutDescription.empty()) + layouts.emplace_back(StringSettingOption(layoutDescription, layout)); + + layoutElement = layoutElement->NextSiblingElement(); + } + + std::sort(layouts.begin(), layouts.end(), LayoutSort); +} + +CLibInputSettings::~CLibInputSettings() +{ + const auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return; + + const std::shared_ptr<CSettings> settings = settingsComponent->GetSettings(); + if (!settings) + return; + + settings->GetSettingsManager()->UnregisterSettingOptionsFiller("libinputkeyboardlayout"); + settings->GetSettingsManager()->UnregisterCallback(this); +} + +void CLibInputSettings::SettingOptionsKeyboardLayoutsFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + list = layouts; +} + +void CLibInputSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT) + { + std::string layout = std::dynamic_pointer_cast<const CSettingString>(setting)->GetValue(); + m_libInputHandler->SetKeymap(layout); + } +} diff --git a/xbmc/platform/linux/input/LibInputSettings.h b/xbmc/platform/linux/input/LibInputSettings.h new file mode 100644 index 0000000..83d5edf --- /dev/null +++ b/xbmc/platform/linux/input/LibInputSettings.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "settings/Settings.h" +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" + +#include <memory> +#include <vector> + +class CLibInputHandler; +struct StringSettingOption; + +class CLibInputSettings : public ISettingCallback, public ISettingsHandler +{ +public: + static const std::string SETTING_INPUT_LIBINPUTKEYBOARDLAYOUT; + + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + static void SettingOptionsKeyboardLayoutsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + + CLibInputSettings(CLibInputHandler *handler); + ~CLibInputSettings() override; + +private: + CLibInputHandler *m_libInputHandler{nullptr}; +}; diff --git a/xbmc/platform/linux/input/LibInputTouch.cpp b/xbmc/platform/linux/input/LibInputTouch.cpp new file mode 100644 index 0000000..545d99a --- /dev/null +++ b/xbmc/platform/linux/input/LibInputTouch.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LibInputTouch.h" + +#include "ServiceBroker.h" +#include "input/touch/generic/GenericTouchActionHandler.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +static inline CPoint GetPos(libinput_event_touch *e) +{ + const double x = libinput_event_touch_get_x_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()); + const double y = libinput_event_touch_get_y_transformed(e, CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); + + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - x: {:f} y: {:f}", __FUNCTION__, x, y); + + return CPoint(x, y); +} + +CLibInputTouch::CLibInputTouch() +{ + m_points.reserve(2); + CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance()); +} + +void CLibInputTouch::CheckSlot(int slot) +{ + if (slot + 1 > static_cast<int>(m_points.size())) + m_points.resize(slot + 1, std::make_pair(TouchInputUnchanged, CPoint(0, 0))); +} + +TouchInput CLibInputTouch::GetEvent(int slot) +{ + CheckSlot(slot); + return m_points.at(slot).first; +} + +void CLibInputTouch::SetEvent(int slot, TouchInput event) +{ + CheckSlot(slot); + m_points.at(slot).first = event; +} + +void CLibInputTouch::SetPosition(int slot, CPoint point) +{ + CheckSlot(slot); + m_points.at(slot).second = point; +} + +void CLibInputTouch::ProcessTouchDown(libinput_event_touch *e) +{ + int slot = libinput_event_touch_get_seat_slot(e); + + SetPosition(slot, GetPos(e)); + SetEvent(slot, TouchInputDown); + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input down", __FUNCTION__); +} + +void CLibInputTouch::ProcessTouchMotion(libinput_event_touch *e) +{ + int slot = libinput_event_touch_get_seat_slot(e); + uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL; + + SetPosition(slot, GetPos(e)); + + if (GetEvent(slot) != TouchInputDown) + SetEvent(slot, TouchInputMove); + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input move", __FUNCTION__); + + CGenericTouchInputHandler::GetInstance().UpdateTouchPointer(slot, GetX(slot), GetY(slot), nanotime); +} + +void CLibInputTouch::ProcessTouchUp(libinput_event_touch *e) +{ + int slot = libinput_event_touch_get_seat_slot(e); + + SetEvent(slot, TouchInputUp); + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input up", __FUNCTION__); +} + +void CLibInputTouch::ProcessTouchCancel(libinput_event_touch *e) +{ + int slot = libinput_event_touch_get_seat_slot(e); + uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL; + + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input cancel", __FUNCTION__); + CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputAbort, GetX(slot), GetY(slot), nanotime, slot); +} + +void CLibInputTouch::ProcessTouchFrame(libinput_event_touch *e) +{ + uint64_t nanotime = libinput_event_touch_get_time_usec(e) * 1000LL; + + for (size_t slot = 0; slot < m_points.size(); ++slot) + { + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input frame: event {}", __FUNCTION__, + GetEvent(slot)); + CLog::Log(LOGDEBUG, "CLibInputTouch::{} - touch input frame: slot {}", __FUNCTION__, slot); + CGenericTouchInputHandler::GetInstance().HandleTouchInput(GetEvent(slot), GetX(slot), GetY(slot), nanotime, slot); + SetEvent(slot, TouchInputUnchanged); + } +} + diff --git a/xbmc/platform/linux/input/LibInputTouch.h b/xbmc/platform/linux/input/LibInputTouch.h new file mode 100644 index 0000000..9805599 --- /dev/null +++ b/xbmc/platform/linux/input/LibInputTouch.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "input/touch/generic/GenericTouchInputHandler.h" +#include "utils/Geometry.h" + +#include <vector> + +#include <libinput.h> + +struct libinput_event_touch; +struct libinput_device; + +class CLibInputTouch +{ +public: + CLibInputTouch(); + void ProcessTouchDown(libinput_event_touch *e); + void ProcessTouchMotion(libinput_event_touch *e); + void ProcessTouchUp(libinput_event_touch *e); + void ProcessTouchCancel(libinput_event_touch *e); + void ProcessTouchFrame(libinput_event_touch *e); + +private: + void CheckSlot(int slot); + TouchInput GetEvent(int slot); + void SetEvent(int slot, TouchInput event); + void SetPosition(int slot, CPoint point); + int GetX(int slot) { return m_points.at(slot).second.x; } + int GetY(int slot) { return m_points.at(slot).second.y; } + + std::vector<std::pair<TouchInput, CPoint>> m_points{std::make_pair(TouchInputUnchanged, CPoint(0, 0))}; +}; diff --git a/xbmc/platform/linux/network/CMakeLists.txt b/xbmc/platform/linux/network/CMakeLists.txt new file mode 100644 index 0000000..44aa2e7 --- /dev/null +++ b/xbmc/platform/linux/network/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCES NetworkLinux.cpp) +set(HEADERS NetworkLinux.h) + +core_add_library(platform_linux_network) diff --git a/xbmc/platform/linux/network/NetworkLinux.cpp b/xbmc/platform/linux/network/NetworkLinux.cpp new file mode 100644 index 0000000..e9a936a --- /dev/null +++ b/xbmc/platform/linux/network/NetworkLinux.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "NetworkLinux.h" + +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <errno.h> +#include <utility> + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <resolv.h> +#include <sys/ioctl.h> + +CNetworkInterfaceLinux::CNetworkInterfaceLinux(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]) + : CNetworkInterfacePosix(network, std::move(interfaceName), interfaceMacAddrRaw) +{ +} + +std::string CNetworkInterfaceLinux::GetCurrentDefaultGateway() const +{ + std::string result; + + FILE* fp = fopen("/proc/net/route", "r"); + if (!fp) + { + // TBD: Error + return result; + } + + char* line = NULL; + char iface[16]; + char dst[128]; + char gateway[128]; + size_t linel = 0; + int n; + int linenum = 0; + while (getdelim(&line, &linel, '\n', fp) > 0) + { + // skip first two lines + if (linenum++ < 1) + continue; + + // search where the word begins + n = sscanf(line, "%15s %127s %127s", iface, dst, gateway); + + if (n < 3) + continue; + + if (strcmp(iface, m_interfaceName.c_str()) == 0 && strcmp(dst, "00000000") == 0 && + strcmp(gateway, "00000000") != 0) + { + unsigned char gatewayAddr[4]; + int len = CNetworkBase::ParseHex(gateway, gatewayAddr); + if (len == 4) + { + struct in_addr in; + in.s_addr = (gatewayAddr[0] << 24) | (gatewayAddr[1] << 16) | (gatewayAddr[2] << 8) | + (gatewayAddr[3]); + result = inet_ntoa(in); + break; + } + } + } + free(line); + fclose(fp); + + return result; +} + +bool CNetworkInterfaceLinux::GetHostMacAddress(unsigned long host_ip, std::string& mac) const +{ + struct arpreq areq; + struct sockaddr_in* sin; + + memset(&areq, 0x0, sizeof(areq)); + + sin = (struct sockaddr_in*)&areq.arp_pa; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = host_ip; + + sin = (struct sockaddr_in*)&areq.arp_ha; + sin->sin_family = ARPHRD_ETHER; + + strncpy(areq.arp_dev, m_interfaceName.c_str(), sizeof(areq.arp_dev)); + areq.arp_dev[sizeof(areq.arp_dev) - 1] = '\0'; + + int result = ioctl(m_network->GetSocket(), SIOCGARP, (caddr_t)&areq); + + if (result != 0) + { + // CLog::Log(LOGERROR, "{} - GetHostMacAddress/ioctl failed with errno ({})", __FUNCTION__, errno); + return false; + } + + struct sockaddr* res = &areq.arp_ha; + mac = StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", (uint8_t)res->sa_data[0], + (uint8_t)res->sa_data[1], (uint8_t)res->sa_data[2], + (uint8_t)res->sa_data[3], (uint8_t)res->sa_data[4], + (uint8_t)res->sa_data[5]); + + for (int i = 0; i < 6; ++i) + if (res->sa_data[i]) + return true; + + return false; +} + +std::unique_ptr<CNetworkBase> CNetworkBase::GetNetwork() +{ + return std::make_unique<CNetworkLinux>(); +} + +CNetworkLinux::CNetworkLinux() : CNetworkPosix() +{ + queryInterfaceList(); +} + +void CNetworkLinux::GetMacAddress(const std::string& interfaceName, char rawMac[6]) +{ + memset(rawMac, 0, 6); + + struct ifreq ifr; + strcpy(ifr.ifr_name, interfaceName.c_str()); + if (ioctl(GetSocket(), SIOCGIFHWADDR, &ifr) >= 0) + { + memcpy(rawMac, ifr.ifr_hwaddr.sa_data, 6); + } +} + +void CNetworkLinux::queryInterfaceList() +{ + char macAddrRaw[6]; + m_interfaces.clear(); + + FILE* fp = fopen("/proc/net/dev", "r"); + if (!fp) + { + // TBD: Error + return; + } + + char* line = NULL; + size_t linel = 0; + int n; + char* p; + int linenum = 0; + while (getdelim(&line, &linel, '\n', fp) > 0) + { + // skip first two lines + if (linenum++ < 2) + continue; + + // search where the word begins + p = line; + while (isspace(*p)) + ++p; + + // read word until : + n = strcspn(p, ": \t"); + p[n] = 0; + + // save the result + std::string interfaceName = p; + GetMacAddress(interfaceName, macAddrRaw); + + // only add interfaces with non-zero mac addresses + if (macAddrRaw[0] || macAddrRaw[1] || macAddrRaw[2] || macAddrRaw[3] || macAddrRaw[4] || + macAddrRaw[5]) + m_interfaces.push_back(new CNetworkInterfaceLinux(this, interfaceName, macAddrRaw)); + } + free(line); + fclose(fp); +} + +std::vector<std::string> CNetworkLinux::GetNameServers() +{ + std::vector<std::string> result; + + res_init(); + + for (int i = 0; i < _res.nscount; i++) + { + std::string ns = inet_ntoa(_res.nsaddr_list[i].sin_addr); + result.push_back(ns); + } + return result; +} + +bool CNetworkLinux::PingHost(unsigned long remote_ip, unsigned int timeout_ms) +{ + char cmd_line[64]; + + struct in_addr host_ip; + host_ip.s_addr = remote_ip; + + sprintf(cmd_line, "ping -c 1 -w %d %s", timeout_ms / 1000 + (timeout_ms % 1000) != 0, + inet_ntoa(host_ip)); + + int status = -1; + status = system(cmd_line); + int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + + // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ; + // 0 reply + // 1 no reply + // else some error + + if (result < 0 || result > 1) + CLog::Log(LOGERROR, "Ping fail : status = {}, errno = {} : '{}'", status, errno, cmd_line); + + return result == 0; +} diff --git a/xbmc/platform/linux/network/NetworkLinux.h b/xbmc/platform/linux/network/NetworkLinux.h new file mode 100644 index 0000000..dc7b4c6 --- /dev/null +++ b/xbmc/platform/linux/network/NetworkLinux.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/network/NetworkPosix.h" + +#include <string> +#include <vector> + +class CNetworkInterfaceLinux : public CNetworkInterfacePosix +{ +public: + CNetworkInterfaceLinux(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]); + ~CNetworkInterfaceLinux() override = default; + + std::string GetCurrentDefaultGateway() const override; + bool GetHostMacAddress(unsigned long host, std::string& mac) const override; +}; + +class CNetworkLinux : public CNetworkPosix +{ +public: + CNetworkLinux(); + ~CNetworkLinux() override = default; + + bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) override; + std::vector<std::string> GetNameServers(void) override; + +private: + void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) override; + void queryInterfaceList() override; +}; diff --git a/xbmc/platform/linux/network/zeroconf/CMakeLists.txt b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt new file mode 100644 index 0000000..b31ab73 --- /dev/null +++ b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt @@ -0,0 +1,8 @@ +if(AVAHI_FOUND) + set(SOURCES ZeroconfAvahi.cpp + ZeroconfBrowserAvahi.cpp) + set(HEADERS ZeroconfAvahi.h + ZeroconfBrowserAvahi.h) + + core_add_library(platform_linux_network_zeroconf) +endif() diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp new file mode 100644 index 0000000..dea038a --- /dev/null +++ b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ZeroconfAvahi.h" + +#include "utils/log.h" + +#include <cassert> +#include <iostream> +#include <sstream> +#include <string> + +#include <avahi-client/client.h> +#include <avahi-common/alternative.h> +#include <avahi-common/error.h> +#include <avahi-common/malloc.h> +#include <avahi-common/thread-watch.h> +#include <unistd.h> //gethostname + +#include "PlatformDefs.h" + +///helper RAII-struct to block event loop for modifications +struct ScopedEventLoopBlock +{ + explicit ScopedEventLoopBlock(AvahiThreadedPoll* fp_poll):mp_poll(fp_poll) + { + avahi_threaded_poll_lock(mp_poll); + } + + ~ScopedEventLoopBlock() + { + avahi_threaded_poll_unlock(mp_poll); + } +private: + AvahiThreadedPoll* mp_poll; +}; + +///helper to store information on howto create an avahi-group to publish +struct CZeroconfAvahi::ServiceInfo +{ + ServiceInfo(const std::string& fcr_type, const std::string& fcr_name, + unsigned int f_port, AvahiStringList *txt, AvahiEntryGroup* fp_group = 0): + m_type(fcr_type), m_name(fcr_name), m_port(f_port), mp_txt(txt), mp_group(fp_group) + { + } + + std::string m_type; + std::string m_name; + unsigned int m_port; + AvahiStringList* mp_txt; + AvahiEntryGroup* mp_group; +}; + +CZeroconfAvahi::CZeroconfAvahi() +{ + if (! (mp_poll = avahi_threaded_poll_new())) + { + CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create threaded poll object"); + //! @todo throw exception? + return; + } + + if (!createClient()) + { + CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create client"); + //yeah, what if not? but should always succeed + } + + //start event loop thread + if (avahi_threaded_poll_start(mp_poll) < 0) + { + CLog::Log(LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Failed to start avahi client thread"); + } +} + +CZeroconfAvahi::~CZeroconfAvahi() +{ + CLog::Log(LOGDEBUG, "CZeroconfAvahi::~CZeroconfAvahi() Going down! cleaning up..."); + + if (mp_poll) + { + //normally we would stop the avahi thread here and do our work, but + //it looks like this does not work -> www.avahi.org/ticket/251 + //so instead of calling + //avahi_threaded_poll_stop(mp_poll); + //we set m_shutdown=true, post an event and wait for it to stop itself + struct timeval tv = { 0, 0 }; //! @todo does tv survive the thread? + AvahiTimeout* lp_timeout; + { + ScopedEventLoopBlock l_block(mp_poll); + const AvahiPoll* cp_apoll = avahi_threaded_poll_get(mp_poll); + m_shutdown = true; + lp_timeout = cp_apoll->timeout_new(cp_apoll, + &tv, + shutdownCallback, + this); + } + + //now wait for the thread to stop + assert(m_thread_id); + pthread_join(m_thread_id, NULL); + avahi_threaded_poll_get(mp_poll)->timeout_free(lp_timeout); + } + + //free the client (frees all browsers, groups, ...) + if (mp_client) + avahi_client_free(mp_client); + if (mp_poll) + avahi_threaded_poll_free(mp_poll); +} + +bool CZeroconfAvahi::doPublishService(const std::string& fcr_identifier, + const std::string& fcr_type, + const std::string& fcr_name, + unsigned int f_port, + const std::vector<std::pair<std::string, std::string> >& txt) +{ + CLog::Log(LOGDEBUG, "CZeroconfAvahi::doPublishService identifier: {} type: {} name:{} port:{}", + fcr_identifier, fcr_type, fcr_name, f_port); + + ScopedEventLoopBlock l_block(mp_poll); + tServiceMap::iterator it = m_services.find(fcr_identifier); + if (it != m_services.end()) + { + //fcr_identifier exists, no update functionality yet, so exit + return false; + } + + //txt records to AvahiStringList + AvahiStringList *txtList = NULL; + for (const auto& it : txt) + { + txtList = avahi_string_list_add_pair(txtList, it.first.c_str(), it.second.c_str()); + } + + //create service info and add it to service map + tServiceMap::mapped_type p_service_info(new CZeroconfAvahi::ServiceInfo(fcr_type, fcr_name, f_port, txtList)); + it = m_services.insert(it, std::make_pair(fcr_identifier, p_service_info)); + + //if client is already running, directly try to add the new service + if ( mp_client && avahi_client_get_state(mp_client) == AVAHI_CLIENT_S_RUNNING ) + { + //client's already running, add this new service + addService(p_service_info, mp_client); + } + else + { + CLog::Log(LOGDEBUG, "CZeroconfAvahi::doPublishService: client not running, queued for publishing"); + } + return true; +} + +bool CZeroconfAvahi::doForceReAnnounceService(const std::string& fcr_identifier) +{ + bool ret = false; + ScopedEventLoopBlock l_block(mp_poll); + tServiceMap::iterator it = m_services.find(fcr_identifier); + if (it != m_services.end() && it->second->mp_group) + { + // to force a reannounce on avahi its enough to reverse the txtrecord list + it->second->mp_txt = avahi_string_list_reverse(it->second->mp_txt); + + // this will trigger the reannouncement + if ((avahi_entry_group_update_service_txt_strlst(it->second->mp_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), + it->second->m_name.c_str(), + it->second->m_type.c_str(), NULL, it->second->mp_txt)) >= 0) + ret = true; + } + + return ret; +} + +bool CZeroconfAvahi::doRemoveService(const std::string& fcr_ident) +{ + CLog::Log(LOGDEBUG, "CZeroconfAvahi::doRemoveService named: {}", fcr_ident); + ScopedEventLoopBlock l_block(mp_poll); + tServiceMap::iterator it = m_services.find(fcr_ident); + if (it == m_services.end()) + { + return false; + } + + if (it->second->mp_group) + { + avahi_entry_group_free(it->second->mp_group); + it->second->mp_group = 0; + } + + if(it->second->mp_txt) + { + avahi_string_list_free(it->second->mp_txt); + it->second->mp_txt = NULL; + } + + m_services.erase(it); + return true; +} + +void CZeroconfAvahi::doStop() +{ + ScopedEventLoopBlock l_block(mp_poll); + for (auto& it : m_services) + { + if (it.second->mp_group) + { + avahi_entry_group_free(it.second->mp_group); + it.second->mp_group = 0; + } + + if (it.second->mp_txt) + { + avahi_string_list_free(it.second->mp_txt); + it.second->mp_txt = nullptr; + } + } + m_services.clear(); +} + +void CZeroconfAvahi::clientCallback(AvahiClient* fp_client, AvahiClientState f_state, void* fp_data) +{ + CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data); + + //store our thread ID and check for shutdown -> check details in destructor + p_instance->m_thread_id = pthread_self(); + + if (p_instance->m_shutdown) + { + avahi_threaded_poll_quit(p_instance->mp_poll); + return; + } + switch(f_state) + { + case AVAHI_CLIENT_S_RUNNING: + CLog::Log(LOGDEBUG, "CZeroconfAvahi::clientCallback: client is up and running"); + p_instance->updateServices(fp_client); + break; + + case AVAHI_CLIENT_FAILURE: + CLog::Log(LOGINFO, "CZeroconfAvahi::clientCallback: client failure. avahi-daemon stopped? Recreating client..."); + //We were forced to disconnect from server. now free and recreate the client object + avahi_client_free(fp_client); + p_instance->mp_client = 0; + //freeing the client also frees all groups and browsers, pointers are undefined afterwards, so fix that now + for (auto& it : p_instance->m_services) + { + it.second->mp_group = 0; + } + p_instance->createClient(); + break; + + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + //HERE WE SHOULD REMOVE ALL OF OUR SERVICES AND "RESCHEDULE" them for later addition + CLog::Log(LOGDEBUG, "CZeroconfAvahi::clientCallback: uiuui; coll or reg, anyways, resetting groups"); + for (const auto& it : p_instance->m_services) + { + if (it.second->mp_group) + avahi_entry_group_reset(it.second->mp_group); + } + break; + + case AVAHI_CLIENT_CONNECTING: + CLog::Log(LOGINFO, "CZeroconfAvahi::clientCallback: avahi server not available. But may become later..."); + break; + } +} + +void CZeroconfAvahi::groupCallback(AvahiEntryGroup *fp_group, AvahiEntryGroupState f_state, void * fp_data) +{ + CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data); + //store our thread ID and check for shutdown -> check details in destructor + p_instance->m_thread_id = pthread_self(); + if (p_instance->m_shutdown) + { + avahi_threaded_poll_quit(p_instance->mp_poll); + return; + } + + switch (f_state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + // The entry group has been established successfully + CLog::Log(LOGDEBUG, "CZeroconfAvahi::groupCallback: Service successfully established"); + break; + + case AVAHI_ENTRY_GROUP_COLLISION : + { + //need to find the ServiceInfo struct for this group + tServiceMap::iterator it = p_instance->m_services.begin(); + for(; it != p_instance->m_services.end(); ++it) + { + if (it->second->mp_group == fp_group) + break; + } + if( it != p_instance->m_services.end() ) { + char* alt_name = avahi_alternative_service_name( it->second->m_name.c_str() ); + it->second->m_name = alt_name; + avahi_free(alt_name); + CLog::Log(LOGINFO, "CZeroconfAvahi::groupCallback: Service name collision. Renamed to: {}", + it->second->m_name); + p_instance->addService(it->second, p_instance->mp_client); + } + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE: + CLog::Log(LOGERROR, "CZeroconfAvahi::groupCallback: Entry group failure: {} ", + (fp_group) + ? avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(fp_group))) + : "Unknown"); + //free the group and set to 0 so it may be added later + if (fp_group) + { + //need to find the ServiceInfo struct for this group + for (auto& it : p_instance->m_services) + { + if (it.second->mp_group == fp_group) + { + avahi_entry_group_free(it.second->mp_group); + it.second->mp_group = 0; + if (it.second->mp_txt) + { + avahi_string_list_free(it.second->mp_txt); + it.second->mp_txt = nullptr; + } + break; + } + } + } + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + default: + break; + } +} + +void CZeroconfAvahi::shutdownCallback(AvahiTimeout *fp_e, void *fp_data) +{ + CZeroconfAvahi* p_instance = static_cast<CZeroconfAvahi*>(fp_data); + //should only be called on shutdown + if (p_instance->m_shutdown) + { + avahi_threaded_poll_quit(p_instance->mp_poll); + } +} + +bool CZeroconfAvahi::createClient() +{ + if (mp_client) + { + avahi_client_free(mp_client); + } + + int error = 0; + mp_client = avahi_client_new(avahi_threaded_poll_get(mp_poll), + AVAHI_CLIENT_NO_FAIL, &clientCallback,this,&error); + if (!mp_client) + { + CLog::Log(LOGERROR, "CZeroconfAvahi::createClient() failed with {}", error); + return false; + } + return true; +} + +void CZeroconfAvahi::updateServices(AvahiClient* fp_client) +{ + for (const auto& it : m_services) + { + if (!it.second->mp_group) + addService(it.second, fp_client); + } +} + +void CZeroconfAvahi::addService(const tServiceMap::mapped_type& fp_service_info, + AvahiClient* fp_client) +{ + assert(fp_client); + CLog::Log(LOGDEBUG, "CZeroconfAvahi::addService() named: {} type: {} port:{}", + fp_service_info->m_name, fp_service_info->m_type, fp_service_info->m_port); + //create the group if it doesn't exist + if (!fp_service_info->mp_group) + { + if (!(fp_service_info->mp_group = avahi_entry_group_new(fp_client, &CZeroconfAvahi::groupCallback, this))) + { + CLog::Log(LOGDEBUG, "CZeroconfAvahi::addService() avahi_entry_group_new() failed: {}", + avahi_strerror(avahi_client_errno(fp_client))); + fp_service_info->mp_group = 0; + return; + } + } + + + // add entries to the group if it's empty + int ret; + if (avahi_entry_group_is_empty(fp_service_info->mp_group)) + { + if ((ret = avahi_entry_group_add_service_strlst(fp_service_info->mp_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), + fp_service_info->m_name.c_str(), + fp_service_info->m_type.c_str(), NULL, NULL, fp_service_info->m_port, fp_service_info->mp_txt)) < 0) + { + if (ret == AVAHI_ERR_COLLISION) + { + char* alt_name = avahi_alternative_service_name(fp_service_info->m_name.c_str()); + fp_service_info->m_name = alt_name; + avahi_free(alt_name); + CLog::Log(LOGINFO, "CZeroconfAvahi::addService: Service name collision. Renamed to: {}", + fp_service_info->m_name); + addService(fp_service_info, fp_client); + return; + } + CLog::Log(LOGERROR, + "CZeroconfAvahi::addService(): failed to add service named:{}@$(HOSTNAME) type:{} " + "port:{}. Error:{} :/ FIXME!", + fp_service_info->m_name, fp_service_info->m_type, fp_service_info->m_port, + avahi_strerror(ret)); + return; + } + } + + // Tell the server to register the service + if ((ret = avahi_entry_group_commit(fp_service_info->mp_group)) < 0) + { + CLog::Log(LOGERROR, "CZeroconfAvahi::addService(): Failed to commit entry group! Error:{}", + avahi_strerror(ret)); + //! @todo what now? reset the group? free it? + } +} diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h new file mode 100644 index 0000000..102df9b --- /dev/null +++ b/xbmc/platform/linux/network/zeroconf/ZeroconfAvahi.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "network/Zeroconf.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> +#include <avahi-common/defs.h> + +struct AvahiThreadedPoll; + +class CZeroconfAvahi : public CZeroconf +{ +public: + CZeroconfAvahi(); + ~CZeroconfAvahi() override; + +protected: + //implement base CZeroConf interface + bool doPublishService(const std::string& fcr_identifier, + const std::string& fcr_type, + const std::string& fcr_name, + unsigned int f_port, + const std::vector<std::pair<std::string, std::string> >& txt) override; + + bool doForceReAnnounceService(const std::string& fcr_identifier) override; + bool doRemoveService(const std::string& fcr_ident) override; + + void doStop() override; + +private: + ///this is where the client calls us if state changes + static void clientCallback(AvahiClient* fp_client, AvahiClientState f_state, void*); + ///here we get notified of group changes + static void groupCallback(AvahiEntryGroup *fp_group, AvahiEntryGroupState f_state, void *); + //shutdown callback; works around a problem in avahi < 0.6.24 see destructor for details + static void shutdownCallback(AvahiTimeout *fp_e, void *); + + ///creates the avahi client; + ///@return true on success + bool createClient(); + + //don't access stuff below without stopping the client thread + //see http://avahi.org/wiki/RunningAvahiClientAsThread + //and use struct ScopedEventLoopBlock + + //helper struct for holding information about creating a service / AvahiEntryGroup + //we have to hold that as it's needed to recreate the service + struct ServiceInfo; + typedef std::map<std::string, std::shared_ptr<ServiceInfo> > tServiceMap; + + //goes through a list of todos and publishs them (takes the client a param, as it might be called from + // from the callbacks) + void updateServices(AvahiClient* fp_client); + //helper that actually does the work of publishing + void addService(const tServiceMap::mapped_type& fp_service_info, AvahiClient* fp_client); + + AvahiClient* mp_client = 0; + AvahiThreadedPoll* mp_poll = 0; + + //this holds all published and unpublished services including info on howto create them + tServiceMap m_services; + + //2 variables below are needed for workaround of avahi bug (see destructor for details) + bool m_shutdown = false; + pthread_t m_thread_id = 0; +}; diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp new file mode 100644 index 0000000..491b98b --- /dev/null +++ b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ZeroconfBrowserAvahi.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "utils/log.h" + +#include <avahi-common/error.h> +#include <avahi-common/malloc.h> +namespace +{ +///helper RAII-struct to block event loop for modifications +struct ScopedEventLoopBlock +{ + explicit ScopedEventLoopBlock ( AvahiThreadedPoll* fp_poll ) : mp_poll ( fp_poll ) + { + avahi_threaded_poll_lock ( mp_poll ); + } + + ~ScopedEventLoopBlock() + { + avahi_threaded_poll_unlock ( mp_poll ); + } +private: + AvahiThreadedPoll* mp_poll; +}; +} + +CZeroconfBrowserAvahi::CZeroconfBrowserAvahi() +{ + if ( ! ( mp_poll = avahi_threaded_poll_new() ) ) + { + CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create threaded poll object" ); + //! @todo throw exception? can this even happen? + return; + } + + if ( !createClient() ) + { + CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Could not create client" ); + //yeah, what if not? but should always succeed (as client_no_fail or something is passed) + } + + //start event loop thread + if ( avahi_threaded_poll_start ( mp_poll ) < 0 ) + { + CLog::Log ( LOGERROR, "CZeroconfAvahi::CZeroconfAvahi(): Failed to start avahi client thread" ); + } +} + +CZeroconfBrowserAvahi::~CZeroconfBrowserAvahi() +{ + CLog::Log ( LOGDEBUG, "CZeroconfAvahi::~CZeroconfAvahi() Going down! cleaning up..." ); + if ( mp_poll ) + { + //stop avahi polling thread + avahi_threaded_poll_stop(mp_poll); + } + //free the client (frees all browsers, groups, ...) + if ( mp_client ) + avahi_client_free ( mp_client ); + if ( mp_poll ) + avahi_threaded_poll_free ( mp_poll ); +} + +bool CZeroconfBrowserAvahi::doAddServiceType ( const std::string& fcr_service_type ) +{ + ScopedEventLoopBlock lock ( mp_poll ); + tBrowserMap::iterator it = m_browsers.find ( fcr_service_type ); + if ( it != m_browsers.end() ) + return false; + else + it = m_browsers.insert ( std::make_pair ( fcr_service_type, ( AvahiServiceBrowser* ) 0 ) ).first; + + //if the client is running, we directly create a browser for the service here + if ( mp_client && avahi_client_get_state ( mp_client ) == AVAHI_CLIENT_S_RUNNING ) + { + AvahiServiceBrowser* browser = createServiceBrowser ( fcr_service_type, mp_client, this); + if ( !browser ) + { + m_browsers.erase ( it ); + return false; + } + else + { + it->second = browser; + return true; + } + } + else + { + CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::doAddServiceType client not available. service browsing queued" ); + return true; + } +} + +bool CZeroconfBrowserAvahi::doRemoveServiceType ( const std::string& fcr_service_type ) +{ + ScopedEventLoopBlock lock ( mp_poll ); + tBrowserMap::iterator it = m_browsers.find ( fcr_service_type ); + if ( it == m_browsers.end() ) + return false; + else + { + if ( it->second ) + { + avahi_service_browser_free ( it->second ); + m_all_for_now_browsers.erase ( it->second ); + } + m_browsers.erase ( it ); + //remove this serviceType from the list of discovered services + for (auto itr = m_discovered_services.begin(); itr != m_discovered_services.end();) + { + if (itr->first.GetType() == fcr_service_type) + itr = m_discovered_services.erase(itr); + else + ++itr; + } + } + return true; +} + +std::vector<CZeroconfBrowser::ZeroconfService> CZeroconfBrowserAvahi::doGetFoundServices() +{ + std::vector<CZeroconfBrowser::ZeroconfService> ret; + ScopedEventLoopBlock lock ( mp_poll ); + ret.reserve ( m_discovered_services.size() ); + for (const auto& it : m_discovered_services) + ret.push_back(it.first); + return ret; +} + +bool CZeroconfBrowserAvahi::doResolveService ( CZeroconfBrowser::ZeroconfService& fr_service, double f_timeout ) +{ + { + //wait for lock on event-loop to schedule resolving + ScopedEventLoopBlock lock ( mp_poll ); + //avahi can only resolve already discovered services, as it needs info from there + tDiscoveredServices::const_iterator it = m_discovered_services.find( fr_service ); + if ( it == m_discovered_services.end() ) + { + CLog::Log ( LOGERROR, "CZeroconfBrowserAvahi::doResolveService called with undiscovered service, resolving is NOT possible" ); + return false; + } + //start resolving + m_resolving_service = fr_service; + m_resolved_event.Reset(); + if ( !avahi_service_resolver_new ( mp_client, it->second.interface, it->second.protocol, + it->first.GetName().c_str(), it->first.GetType().c_str(), it->first.GetDomain().c_str(), + AVAHI_PROTO_UNSPEC, AvahiLookupFlags ( 0 ), resolveCallback, this ) ) + { + CLog::Log(LOGERROR, + "CZeroconfBrowserAvahi::doResolveService Failed to resolve service '{}': {}\n", + it->first.GetName(), avahi_strerror(avahi_client_errno(mp_client))); + return false; + } + } // end of this block releases lock of eventloop + + //wait for resolve to return or timeout + m_resolved_event.Wait(std::chrono::duration<double, std::milli>(f_timeout * 1000)); + { + ScopedEventLoopBlock lock ( mp_poll ); + fr_service = m_resolving_service; + return (!fr_service.GetIP().empty()); + } +} + +void CZeroconfBrowserAvahi::clientCallback ( AvahiClient* fp_client, AvahiClientState f_state, void* fp_data ) +{ + CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data ); + switch ( f_state ) + { + case AVAHI_CLIENT_S_RUNNING: + { + CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: client is up and running" ); + for (auto& it : p_instance->m_browsers) + { + assert(!it.second); + it.second = createServiceBrowser(it.first, fp_client, fp_data); + } + break; + } + case AVAHI_CLIENT_FAILURE: + { + CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: client failure. avahi-daemon stopped? Recreating client..." ); + //We were forced to disconnect from server. now free and recreate the client object + avahi_client_free ( fp_client ); + p_instance->mp_client = 0; + //freeing the client also frees all groups and browsers, pointers are undefined afterwards, so fix that now + for (auto& it : p_instance->m_browsers) + it.second = (AvahiServiceBrowser*)0; + //clean the list of discovered services and update gui (if someone is interested) + p_instance->m_discovered_services.clear(); + CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH ); + message.SetStringParam ( "zeroconf://" ); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage ( message ); + p_instance->createClient(); + break; + } + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + //HERE WE SHOULD REMOVE ALL OF OUR SERVICES AND "RESCHEDULE" them for later addition + CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::clientCallback: This should not happen" ); + break; + + case AVAHI_CLIENT_CONNECTING: + CLog::Log ( LOGINFO, "CZeroconfBrowserAvahi::clientCallback: avahi server not available. But may become later..." ); + break; + } +} +void CZeroconfBrowserAvahi::browseCallback ( + AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, void* fp_data ) +{ + CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( fp_data ); + assert ( browser ); + bool update_gui = false; + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + switch ( event ) + { + case AVAHI_BROWSER_FAILURE: + CLog::Log(LOGERROR, "CZeroconfBrowserAvahi::browseCallback error: {}\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(browser)))); + //! @todo implement + return; + case AVAHI_BROWSER_NEW: + { + CLog::Log( + LOGDEBUG, + "CZeroconfBrowserAvahi::browseCallback NEW: service '{}' of type '{}' in domain '{}'\n", + name, type, domain); + //store the service + ZeroconfService service(name, type, domain); + AvahiSpecificInfos info; + info.interface = interface; + info.protocol = protocol; + p_instance->m_discovered_services.insert ( std::make_pair ( service, info ) ); + //if this browser already sent the all for now message, we need to update the gui now + if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() ) + update_gui = true; + break; + } + case AVAHI_BROWSER_REMOVE: + { + //remove the service + ZeroconfService service(name, type, domain); + p_instance->m_discovered_services.erase ( service ); + CLog::Log(LOGDEBUG, + "CZeroconfBrowserAvahi::browseCallback REMOVE: service '{}' of type '{}' in " + "domain '{}'\n", + name, type, domain); + //if this browser already sent the all for now message, we need to update the gui now + if( p_instance->m_all_for_now_browsers.find(browser) != p_instance->m_all_for_now_browsers.end() ) + update_gui = true; + break; + } + case AVAHI_BROWSER_CACHE_EXHAUSTED: + //do we need that? + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + CLog::Log(LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback all for now (service = {})", type); + //if this browser already sent the all for now message, we need to update the gui now + bool success = p_instance->m_all_for_now_browsers.insert(browser).second; + if(!success) + CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback AVAHI_BROWSER_ALL_FOR_NOW sent twice?!"); + update_gui = true; + break; + } + if ( update_gui ) + { + CGUIMessage message ( GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH ); + message.SetStringParam ( "zeroconf://" ); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage ( message ); + CLog::Log ( LOGDEBUG, "CZeroconfBrowserAvahi::browseCallback sent gui update for path zeroconf://" ); + } +} + +CZeroconfBrowser::ZeroconfService::tTxtRecordMap GetTxtRecords(AvahiStringList *txt) +{ + AvahiStringList *i = NULL; + CZeroconfBrowser::ZeroconfService::tTxtRecordMap recordMap; + + for( i = txt; i; i = i->next ) + { + char *key, *value; + + if( avahi_string_list_get_pair( i, &key, &value, NULL ) < 0 ) + continue; + + recordMap.insert( + std::make_pair( + std::string(key), + std::string(value) + ) + ); + + if( key ) + avahi_free(key); + if( value ) + avahi_free(value); + } + return recordMap; +} + +void CZeroconfBrowserAvahi::resolveCallback( + AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, + const char *name, const char *type, const char *domain, const char *host_name, + const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata ) +{ + assert ( r ); + assert ( userdata ); + CZeroconfBrowserAvahi* p_instance = static_cast<CZeroconfBrowserAvahi*> ( userdata ); + switch ( event ) + { + case AVAHI_RESOLVER_FAILURE: + CLog::Log(LOGERROR, + "CZeroconfBrowserAvahi::resolveCallback Failed to resolve service '{}' of type " + "'{}' in domain '{}': {}\n", + name, type, domain, + avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + case AVAHI_RESOLVER_FOUND: + { + char a[AVAHI_ADDRESS_STR_MAX]; + CLog::Log(LOGDEBUG, + "CZeroconfBrowserAvahi::resolveCallback resolved service '{}' of type '{}' in " + "domain '{}':\n", + name, type, domain); + + avahi_address_snprint ( a, sizeof ( a ), address ); + p_instance->m_resolving_service.SetIP(a); + p_instance->m_resolving_service.SetPort(port); + //get txt-record list + p_instance->m_resolving_service.SetTxtRecords(GetTxtRecords(txt)); + break; + } + } + avahi_service_resolver_free ( r ); + p_instance->m_resolved_event.Set(); +} + +bool CZeroconfBrowserAvahi::createClient() +{ + assert ( mp_poll ); + if ( mp_client ) + { + avahi_client_free ( mp_client ); + } + mp_client = avahi_client_new ( avahi_threaded_poll_get ( mp_poll ), + AVAHI_CLIENT_NO_FAIL, &clientCallback, this, 0 ); + if ( !mp_client ) + { + mp_client = 0; + return false; + } + return true; +} + +AvahiServiceBrowser* CZeroconfBrowserAvahi::createServiceBrowser ( const std::string& fcr_service_type, AvahiClient* fp_client, void* fp_userdata) +{ + assert(fp_client); + AvahiServiceBrowser* ret = avahi_service_browser_new ( fp_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, fcr_service_type.c_str(), + NULL, ( AvahiLookupFlags ) 0, browseCallback, fp_userdata ); + if ( !ret ) + { + CLog::Log( + LOGERROR, + "CZeroconfBrowserAvahi::createServiceBrowser Failed to create service ({}) browser: {}", + avahi_strerror(avahi_client_errno(fp_client)), fcr_service_type); + } + return ret; +} diff --git a/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h new file mode 100644 index 0000000..cc8c6c4 --- /dev/null +++ b/xbmc/platform/linux/network/zeroconf/ZeroconfBrowserAvahi.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "network/ZeroconfBrowser.h" +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <map> +#include <memory> +#include <vector> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/defs.h> +#include <avahi-common/thread-watch.h> + +//platform specific implementation of zeroconfbrowser interface using avahi +class CZeroconfBrowserAvahi : public CZeroconfBrowser +{ + public: + CZeroconfBrowserAvahi(); + ~CZeroconfBrowserAvahi() override; + + private: + ///implementation if CZeroconfBrowser interface + ///@{ + bool doAddServiceType(const std::string& fcr_service_type) override; + bool doRemoveServiceType(const std::string& fcr_service_type) override; + + std::vector<CZeroconfBrowser::ZeroconfService> doGetFoundServices() override; + bool doResolveService(CZeroconfBrowser::ZeroconfService& fr_service, double f_timeout) override; + ///@} + + /// avahi callbacks + ///@{ + static void clientCallback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata); + static void browseCallback( + AvahiServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + void* userdata); + static void resolveCallback( + AvahiServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata); + //helpers + bool createClient(); + static AvahiServiceBrowser* createServiceBrowser(const std::string& fcr_service_type, AvahiClient* fp_client, void* fp_userdata); + + //shared variables between avahi thread and interface + AvahiClient* mp_client = 0 ; + AvahiThreadedPoll* mp_poll = 0 ; + // tBrowserMap maps service types the corresponding browser + typedef std::map<std::string, AvahiServiceBrowser*> tBrowserMap; + tBrowserMap m_browsers; + + // if a browser is in this set, it already sent an ALL_FOR_NOW message + // (needed to bundle GUI_MSG_UPDATE_PATH messages + std::set<AvahiServiceBrowser*> m_all_for_now_browsers; + + //this information is needed for avahi to resolve a service, + //so unfortunately we'll only be able to resolve services already discovered + struct AvahiSpecificInfos + { + AvahiIfIndex interface; + AvahiProtocol protocol; + }; + typedef std::map<CZeroconfBrowser::ZeroconfService, AvahiSpecificInfos> tDiscoveredServices; + tDiscoveredServices m_discovered_services; + CZeroconfBrowser::ZeroconfService m_resolving_service; + CEvent m_resolved_event; +}; diff --git a/xbmc/platform/linux/peripherals/CMakeLists.txt b/xbmc/platform/linux/peripherals/CMakeLists.txt new file mode 100644 index 0000000..626aa40 --- /dev/null +++ b/xbmc/platform/linux/peripherals/CMakeLists.txt @@ -0,0 +1,11 @@ +if(UDEV_FOUND) + list(APPEND SOURCES PeripheralBusUSBLibUdev.cpp) + list(APPEND HEADERS PeripheralBusUSBLibUdev.h) +elseif(LIBUSB_FOUND) + list(APPEND SOURCES PeripheralBusUSBLibUSB.cpp) + list(APPEND HEADERS PeripheralBusUSBLibUSB.h) +endif() + +if(SOURCES) + core_add_library(platform_linux_peripherals) +endif() diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp new file mode 100644 index 0000000..5c788bb --- /dev/null +++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PeripheralBusUSBLibUSB.h" + +#include "peripherals/Peripherals.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <usb.h> + +using namespace PERIPHERALS; + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals& manager) : + CPeripheralBus("PeripBusUSB", manager, PERIPHERAL_BUS_USB) +{ + usb_init(); + usb_find_busses(); + m_busses = usb_get_busses(); + CLog::Log(LOGDEBUG, "{} - using libusb peripheral scanning", __FUNCTION__); +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + struct usb_bus *bus; + usb_find_devices(); + for (bus = m_busses; bus; bus = bus->next) + { + struct usb_device *dev; + for (dev = bus->devices; dev; dev = dev->next) + { + PeripheralScanResult result(m_type); + result.m_iVendorId = dev->descriptor.idVendor; + result.m_iProductId = dev->descriptor.idProduct; + result.m_type = (dev->descriptor.bDeviceClass == USB_CLASS_PER_INTERFACE && dev->descriptor.bNumConfigurations > 0 && + dev->config[0].bNumInterfaces > 0 && dev->config[0].interface[0].num_altsetting > 0) ? + GetType(dev->config[0].interface[0].altsetting[0].bInterfaceClass) : + GetType(dev->descriptor.bDeviceClass); +#ifdef TARGET_FREEBSD + result.m_strLocation = std::to_string(dev->filename); +#else + result.m_strLocation = StringUtils::Format("/bus{}/dev{}", bus->dirname, dev->filename); +#endif + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + } + + return true; +} + +const PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass) +{ + switch (iDeviceClass) + { + case USB_CLASS_HID: + return PERIPHERAL_HID; + case USB_CLASS_COMM: + return PERIPHERAL_NIC; + case USB_CLASS_MASS_STORAGE: + return PERIPHERAL_DISK; + case USB_CLASS_PER_INTERFACE: + case USB_CLASS_AUDIO: + case USB_CLASS_PRINTER: + case USB_CLASS_PTP: + case USB_CLASS_HUB: + case USB_CLASS_DATA: + case USB_CLASS_VENDOR_SPEC: + default: + return PERIPHERAL_UNKNOWN; + } +} diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h new file mode 100644 index 0000000..d2e7e48 --- /dev/null +++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUSB.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +struct usb_bus; + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + explicit CPeripheralBusUSB(CPeripherals& manager); + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results); + + protected: + static const PeripheralType GetType(int iDeviceClass); + struct usb_bus *m_busses; + }; +} diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp new file mode 100644 index 0000000..aeb5e71 --- /dev/null +++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PeripheralBusUSBLibUdev.h" +#include "peripherals/Peripherals.h" +extern "C" { +#include <libudev.h> +} +#include <cassert> +#include <poll.h> +#include "utils/log.h" + +#ifndef USB_CLASS_PER_INTERFACE +#define USB_CLASS_PER_INTERFACE 0 +#endif + +#ifndef USB_CLASS_AUDIO +#define USB_CLASS_AUDIO 1 +#endif + +#ifndef USB_CLASS_COMM +#define USB_CLASS_COMM 2 +#endif + +#ifndef USB_CLASS_HID +#define USB_CLASS_HID 3 +#endif + +#ifndef USB_CLASS_PHYSICAL +#define USB_CLASS_PHYSICAL 5 +#endif + +#ifndef USB_CLASS_PTP +#define USB_CLASS_PTP 6 +#endif + +#ifndef USB_CLASS_PRINTER +#define USB_CLASS_PRINTER 7 +#endif + +#ifndef USB_CLASS_MASS_STORAGE +#define USB_CLASS_MASS_STORAGE 8 +#endif + +#ifndef USB_CLASS_HUB +#define USB_CLASS_HUB 9 +#endif + +#ifndef USB_CLASS_DATA +#define USB_CLASS_DATA 10 +#endif + +#ifndef USB_CLASS_APP_SPEC +#define USB_CLASS_APP_SPEC 0xfe +#endif + +#ifndef USB_CLASS_VENDOR_SPEC +#define USB_CLASS_VENDOR_SPEC 0xff +#endif + +using namespace PERIPHERALS; + +CPeripheralBusUSB::CPeripheralBusUSB(CPeripherals& manager) : + CPeripheralBus("PeripBusUSBUdev", manager, PERIPHERAL_BUS_USB) +{ + /* the Process() method in this class overrides the one in CPeripheralBus, so leave this set to true */ + m_bNeedsPolling = true; + + m_udev = NULL; + m_udevMon = NULL; +} + +CPeripheralBusUSB::~CPeripheralBusUSB(void) +{ + StopThread(true); +} + +bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) +{ + // We don't want this one to be called from outside world + assert(IsCurrentThread()); + + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + struct udev_device *dev(NULL), *parent(NULL); + enumerate = udev_enumerate_new(m_udev); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + + bool bContinue(true); + std::string strPath, strClass; + udev_list_entry_foreach(dev_list_entry, devices) + { + strPath = udev_list_entry_get_name(dev_list_entry); + if (strPath.empty()) + bContinue = false; + + if (bContinue) + { + if (!(parent = udev_device_new_from_syspath(m_udev, strPath.c_str()))) + bContinue = false; + } + + if (bContinue) + { + dev = udev_device_get_parent(udev_device_get_parent(parent)); + if (!dev || !udev_device_get_sysattr_value(dev,"idVendor") || !udev_device_get_sysattr_value(dev, "idProduct")) + bContinue = false; + } + + if (bContinue) + { + strClass = udev_device_get_sysattr_value(dev, "bDeviceClass"); + if (strClass.empty()) + bContinue = false; + } + + if (bContinue) + { + int iClass = PeripheralTypeTranslator::HexStringToInt(strClass.c_str()); + if (iClass == USB_CLASS_PER_INTERFACE) + { + //! @todo just assume this is a HID device for now, since the only devices that we're currently + //! interested in are HID devices + iClass = USB_CLASS_HID; + } + + PeripheralScanResult result(m_type); + result.m_iVendorId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idVendor")); + result.m_iProductId = PeripheralTypeTranslator::HexStringToInt(udev_device_get_sysattr_value(dev, "idProduct")); + result.m_type = GetType(iClass); + result.m_strLocation = udev_device_get_syspath(dev); + result.m_iSequence = GetNumberOfPeripheralsWithId(result.m_iVendorId, result.m_iProductId); + if (!results.ContainsResult(result)) + results.m_results.push_back(result); + } + + bContinue = true; + if (parent) + { + /* unref the _parent_ device */ + udev_device_unref(parent); + parent = NULL; + } + } + /* Free the enumerator object */ + udev_enumerate_unref(enumerate); + + return true; +} + +PeripheralType CPeripheralBusUSB::GetType(int iDeviceClass) +{ + switch (iDeviceClass) + { + case USB_CLASS_HID: + return PERIPHERAL_HID; + case USB_CLASS_COMM: + return PERIPHERAL_NIC; + case USB_CLASS_MASS_STORAGE: + return PERIPHERAL_DISK; + case USB_CLASS_PER_INTERFACE: + case USB_CLASS_AUDIO: + case USB_CLASS_PRINTER: + case USB_CLASS_PTP: + case USB_CLASS_HUB: + case USB_CLASS_DATA: + case USB_CLASS_VENDOR_SPEC: + default: + return PERIPHERAL_UNKNOWN; + } +} + +void CPeripheralBusUSB::Process(void) +{ + if (!(m_udev = udev_new())) + { + CLog::Log(LOGERROR, "{} - failed to allocate udev context", __FUNCTION__); + return; + } + + /* set up a devices monitor that listen for any device change */ + m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev"); + + /* filter to only receive usb events */ + if (udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "usb", nullptr) < 0) + { + CLog::Log(LOGERROR, "Could not limit filter on USB only"); + } + + CLog::Log(LOGDEBUG, "{} - initialised udev monitor", __FUNCTION__); + + udev_monitor_enable_receiving(m_udevMon); + bool bUpdated(false); + ScanForDevices(); + while (!m_bStop) + { + bUpdated = WaitForUpdate(); + if (bUpdated && !m_bStop) + ScanForDevices(); + } + udev_monitor_unref(m_udevMon); + udev_unref(m_udev); +} + +void CPeripheralBusUSB::Clear(void) +{ + StopThread(false); + + CPeripheralBus::Clear(); +} + +bool CPeripheralBusUSB::WaitForUpdate() +{ + int udevFd = udev_monitor_get_fd(m_udevMon); + + if (udevFd < 0) + { + CLog::Log(LOGERROR, "{} - get udev monitor", __FUNCTION__); + return false; + } + + /* poll for udev changes */ + struct pollfd pollFd; + pollFd.fd = udevFd; + pollFd.events = POLLIN; + int iPollResult; + while (!m_bStop && ((iPollResult = poll(&pollFd, 1, 100)) <= 0)) + if (errno != EINTR && iPollResult != 0) + break; + + /* the thread is being stopped, so just return false */ + if (m_bStop) + return false; + + /* we have to read the message from the queue, even though we're not actually using it */ + struct udev_device *dev = udev_monitor_receive_device(m_udevMon); + if (dev) + udev_device_unref(dev); + else + { + CLog::Log(LOGERROR, "{} - failed to get device from udev_monitor_receive_device()", + __FUNCTION__); + Clear(); + return false; + } + + return true; +} diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h new file mode 100644 index 0000000..72cb437 --- /dev/null +++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "peripherals/bus/PeripheralBus.h" +#include "peripherals/devices/Peripheral.h" + +struct udev; +struct udev_monitor; + +namespace PERIPHERALS +{ + class CPeripherals; + + class CPeripheralBusUSB : public CPeripheralBus + { + public: + explicit CPeripheralBusUSB(CPeripherals& manager); + ~CPeripheralBusUSB(void) override; + + void Clear(void) override; + + /*! + * @see PeripheralBus::PerformDeviceScan() + */ + bool PerformDeviceScan(PeripheralScanResults &results) override; + + protected: + static PeripheralType GetType(int iDeviceClass); + + void Process(void) override; + bool WaitForUpdate(void); + + struct udev * m_udev; + struct udev_monitor *m_udevMon; + }; +} diff --git a/xbmc/platform/linux/powermanagement/CMakeLists.txt b/xbmc/platform/linux/powermanagement/CMakeLists.txt new file mode 100644 index 0000000..3694a79 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/CMakeLists.txt @@ -0,0 +1,17 @@ +set(SOURCES LinuxPowerSyscall.cpp) + +set(HEADERS FallbackPowerSyscall.h + LinuxPowerSyscall.h) + +if(DBUS_FOUND) + list(APPEND SOURCES ConsoleUPowerSyscall.cpp + LogindUPowerSyscall.cpp + UPowerSyscall.cpp) + list(APPEND HEADERS ConsoleUPowerSyscall.h + LogindUPowerSyscall.h + UPowerSyscall.h) +endif() + +if(SOURCES) + core_add_library(platform_linux_powermanagement) +endif() diff --git a/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp new file mode 100644 index 0000000..6f04dd7 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ConsoleUPowerSyscall.h" + +#include "utils/log.h" + +CConsoleUPowerSyscall::CConsoleUPowerSyscall() +{ + m_CanPowerdown = ConsoleKitMethodCall("CanStop"); + m_CanReboot = ConsoleKitMethodCall("CanRestart"); +} + +bool CConsoleUPowerSyscall::Powerdown() +{ + CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Stop"); + return message.SendSystem() != NULL; +} + +bool CConsoleUPowerSyscall::Reboot() +{ + CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Restart"); + return message.SendSystem() != NULL; +} + +bool CConsoleUPowerSyscall::HasConsoleKitAndUPower() +{ + return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "CanStop") + && HasUPower(); +} + +bool CConsoleUPowerSyscall::ConsoleKitMethodCall(const char *method) +{ + CDBusMessage message("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", method); + DBusMessage *reply = message.SendSystem(); + if (reply) + { + dbus_bool_t boolean = FALSE; + + if (dbus_message_get_args (reply, NULL, DBUS_TYPE_BOOLEAN, &boolean, DBUS_TYPE_INVALID)) + return boolean; + } + + return false; +} diff --git a/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h new file mode 100644 index 0000000..9fdd157 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/ConsoleUPowerSyscall.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusUtil.h" +#include "UPowerSyscall.h" + +class CConsoleUPowerSyscall : public CUPowerSyscall +{ +public: + CConsoleUPowerSyscall(); + bool Powerdown() override; + bool Reboot() override; + static bool HasConsoleKitAndUPower(); +private: + static bool ConsoleKitMethodCall(const char *method); +}; diff --git a/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h b/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h new file mode 100644 index 0000000..6988543 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/FallbackPowerSyscall.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "powermanagement/IPowerSyscall.h" + +class CFallbackPowerSyscall : public CPowerSyscallWithoutEvents +{ +public: + bool Powerdown() override {return true; } + bool Suspend() override {return false; } + bool Hibernate() override {return false; } + bool Reboot() override {return true; } + + bool CanPowerdown() override {return true; } + bool CanSuspend() override {return false; } + bool CanHibernate() override {return false; } + bool CanReboot() override {return true; } + int BatteryLevel() override {return 0; } +}; diff --git a/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp new file mode 100644 index 0000000..f3dbcdb --- /dev/null +++ b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LinuxPowerSyscall.h" +#include "FallbackPowerSyscall.h" +#if defined(HAS_DBUS) +#include "ConsoleUPowerSyscall.h" +#include "LogindUPowerSyscall.h" +#include "UPowerSyscall.h" +#endif // HAS_DBUS + +#include <functional> +#include <list> +#include <memory> +#include <utility> + +IPowerSyscall* CLinuxPowerSyscall::CreateInstance() +{ +#if defined(HAS_DBUS) + std::unique_ptr<IPowerSyscall> bestPowerManager; + std::unique_ptr<IPowerSyscall> currPowerManager; + int bestCount = -1; + int currCount = -1; + + std::list< std::pair< std::function<bool()>, + std::function<IPowerSyscall*()> > > powerManagers = + { + std::make_pair(CConsoleUPowerSyscall::HasConsoleKitAndUPower, + [] { return new CConsoleUPowerSyscall(); }), + std::make_pair(CLogindUPowerSyscall::HasLogind, + [] { return new CLogindUPowerSyscall(); }), + std::make_pair(CUPowerSyscall::HasUPower, + [] { return new CUPowerSyscall(); }) + }; + for(const auto& powerManager : powerManagers) + { + if (powerManager.first()) + { + currPowerManager.reset(powerManager.second()); + currCount = currPowerManager->CountPowerFeatures(); + if (currCount > bestCount) + { + bestCount = currCount; + bestPowerManager = std::move(currPowerManager); + } + if (bestCount == IPowerSyscall::MAX_COUNT_POWER_FEATURES) + break; + } + } + if (bestPowerManager) + return bestPowerManager.release(); + else +#endif // HAS_DBUS + return new CFallbackPowerSyscall(); +} + +void CLinuxPowerSyscall::Register() +{ + IPowerSyscall::RegisterPowerSyscall(CLinuxPowerSyscall::CreateInstance); +} diff --git a/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h new file mode 100644 index 0000000..b5cc218 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/LinuxPowerSyscall.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "powermanagement/IPowerSyscall.h" + +class CLinuxPowerSyscall +{ +public: + static IPowerSyscall* CreateInstance(); + static void Register(); +}; diff --git a/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp new file mode 100644 index 0000000..bd04197 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.cpp @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2012 Denis Yantarev + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LogindUPowerSyscall.h" + +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <string.h> + +#include <unistd.h> + +// logind DBus interface specification: +// http://www.freedesktop.org/wiki/Software/Logind/logind +// +// Inhibitor Locks documentation: +// http://www.freedesktop.org/wiki/Software/Logind/inhibit/ + +#define LOGIND_DEST "org.freedesktop.login1" +#define LOGIND_PATH "/org/freedesktop/login1" +#define LOGIND_IFACE "org.freedesktop.login1.Manager" + +CLogindUPowerSyscall::CLogindUPowerSyscall() +{ + m_lowBattery = false; + + CLog::Log(LOGINFO, "Selected Logind/UPower as PowerSyscall"); + + // Check if we have UPower. If not, we avoid any battery related operations. + CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices"); + m_hasUPower = message.SendSystem() != NULL; + + if (!m_hasUPower) + CLog::Log(LOGINFO, "LogindUPowerSyscall - UPower not found, battery information will not be available"); + + m_canPowerdown = LogindCheckCapability("CanPowerOff"); + m_canReboot = LogindCheckCapability("CanReboot"); + m_canHibernate = LogindCheckCapability("CanHibernate"); + m_canSuspend = LogindCheckCapability("CanSuspend"); + + InhibitDelayLockSleep(); + + m_batteryLevel = 0; + if (m_hasUPower) + UpdateBatteryLevel(); + + if (!m_connection.Connect(DBUS_BUS_SYSTEM, true)) + { + return; + } + + CDBusError error; + dbus_connection_set_exit_on_disconnect(m_connection, false); + dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'", error); + + if (!error && m_hasUPower) + dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UPower',member='DeviceChanged'", error); + + dbus_connection_flush(m_connection); + + if (error) + { + error.Log("UPowerSyscall: Failed to attach to signal"); + m_connection.Destroy(); + } +} + +CLogindUPowerSyscall::~CLogindUPowerSyscall() +{ + ReleaseDelayLockSleep(); + ReleaseDelayLockShutdown(); +} + +bool CLogindUPowerSyscall::Powerdown() +{ + // delay shutdown so that the app can close properly + InhibitDelayLockShutdown(); + return LogindSetPowerState("PowerOff"); +} + +bool CLogindUPowerSyscall::Reboot() +{ + return LogindSetPowerState("Reboot"); +} + +bool CLogindUPowerSyscall::Suspend() +{ + return LogindSetPowerState("Suspend"); +} + +bool CLogindUPowerSyscall::Hibernate() +{ + return LogindSetPowerState("Hibernate"); +} + +bool CLogindUPowerSyscall::CanPowerdown() +{ + return m_canPowerdown; +} + +bool CLogindUPowerSyscall::CanSuspend() +{ + return m_canSuspend; +} + +bool CLogindUPowerSyscall::CanHibernate() +{ + return m_canHibernate; +} + +bool CLogindUPowerSyscall::CanReboot() +{ + return m_canReboot; +} + +bool CLogindUPowerSyscall::HasLogind() +{ + // recommended method by systemd devs. The seats directory + // doesn't exist unless logind created it and therefore is running. + // see also https://mail.gnome.org/archives/desktop-devel-list/2013-March/msg00092.html + if (access("/run/systemd/seats/", F_OK) >= 0) + return true; + + // on some environments "/run/systemd/seats/" doesn't exist, e.g. on flatpak. Try DBUS instead. + CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, "ListSeats"); + DBusMessage *reply = message.SendSystem(); + if (!reply) + return false; + + DBusMessageIter arrIter; + if (dbus_message_iter_init(reply, &arrIter) && dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_ARRAY) + { + DBusMessageIter structIter; + dbus_message_iter_recurse(&arrIter, &structIter); + if (dbus_message_iter_get_arg_type(&structIter) == DBUS_TYPE_STRUCT) + { + DBusMessageIter strIter; + dbus_message_iter_recurse(&structIter, &strIter); + if (dbus_message_iter_get_arg_type(&strIter) == DBUS_TYPE_STRING) + { + char *seat; + dbus_message_iter_get_basic(&strIter, &seat); + if (StringUtils::StartsWith(seat, "seat")) + { + CLog::Log(LOGDEBUG, "LogindUPowerSyscall::HasLogind - found seat: {}", seat); + return true; + } + } + } + } + return false; +} + +bool CLogindUPowerSyscall::LogindSetPowerState(const char *state) +{ + CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, state); + // The user_interaction boolean parameters can be used to control + // whether PolicyKit should interactively ask the user for authentication + // credentials if it needs to. + message.AppendArgument(false); + return message.SendSystem() != NULL; +} + +bool CLogindUPowerSyscall::LogindCheckCapability(const char *capability) +{ + char *arg; + CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, capability); + DBusMessage *reply = message.SendSystem(); + if(reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID)) + { + // Returns one of "yes", "no" or "challenge". If "challenge" is + // returned the operation is available, but only after authorization. + return (strcmp(arg, "yes") == 0); + } + return false; +} + +int CLogindUPowerSyscall::BatteryLevel() +{ + return m_batteryLevel; +} + +void CLogindUPowerSyscall::UpdateBatteryLevel() +{ + char** source = NULL; + int length = 0; + double batteryLevelSum = 0; + int batteryCount = 0; + + CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices"); + DBusMessage *reply = message.SendSystem(); + + if (!reply) + return; + + if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &source, &length, DBUS_TYPE_INVALID)) + { + CLog::Log(LOGWARNING, "LogindUPowerSyscall: failed to enumerate devices"); + return; + } + + for (int i = 0; i < length; i++) + { + CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", source[i], "org.freedesktop.UPower.Device"); + bool isRechargeable = properties["IsRechargeable"].asBoolean(); + + if (isRechargeable) + { + batteryCount++; + batteryLevelSum += properties["Percentage"].asDouble(); + } + } + + dbus_free_string_array(source); + + if (batteryCount > 0) + { + m_batteryLevel = (int)(batteryLevelSum / (double)batteryCount); + m_lowBattery = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "OnLowBattery").asBoolean(); + } +} + +bool CLogindUPowerSyscall::PumpPowerEvents(IPowerEventsCallback *callback) +{ + bool result = false; + bool releaseLockSleep = false; + + if (m_connection) + { + dbus_connection_read_write(m_connection, 0); + DBusMessagePtr msg(dbus_connection_pop_message(m_connection)); + + if (msg) + { + if (dbus_message_is_signal(msg.get(), "org.freedesktop.login1.Manager", "PrepareForSleep")) + { + dbus_bool_t arg; + // the boolean argument defines whether we are going to sleep (true) or just woke up (false) + dbus_message_get_args(msg.get(), NULL, DBUS_TYPE_BOOLEAN, &arg, DBUS_TYPE_INVALID); + CLog::Log(LOGDEBUG, "LogindUPowerSyscall: Received PrepareForSleep with arg {}", (int)arg); + if (arg) + { + callback->OnSleep(); + releaseLockSleep = true; + } + else + { + callback->OnWake(); + InhibitDelayLockSleep(); + } + + result = true; + } + else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "DeviceChanged")) + { + bool lowBattery = m_lowBattery; + UpdateBatteryLevel(); + if (m_lowBattery && !lowBattery) + callback->OnLowBattery(); + + result = true; + } + else + CLog::Log(LOGDEBUG, "LogindUPowerSyscall - Received unknown signal {}", + dbus_message_get_member(msg.get())); + } + } + + if (releaseLockSleep) + ReleaseDelayLockSleep(); + + return result; +} + +void CLogindUPowerSyscall::InhibitDelayLockSleep() +{ + m_delayLockSleepFd = InhibitDelayLock("sleep"); +} + +void CLogindUPowerSyscall::InhibitDelayLockShutdown() +{ + m_delayLockShutdownFd = InhibitDelayLock("shutdown"); +} + +int CLogindUPowerSyscall::InhibitDelayLock(const char *what) +{ +#ifdef DBUS_TYPE_UNIX_FD + CDBusMessage message("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Inhibit"); + message.AppendArgument(what); // what to inhibit + message.AppendArgument("XBMC"); // who + message.AppendArgument(""); // reason + message.AppendArgument("delay"); // mode + + DBusMessage *reply = message.SendSystem(); + + if (!reply) + { + CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to inhibit {} delay lock", what); + return -1; + } + + int delayLockFd; + if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_UNIX_FD, &delayLockFd, DBUS_TYPE_INVALID)) + { + CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to get inhibit file descriptor"); + return -1; + } + + CLog::Log(LOGDEBUG, "LogindUPowerSyscall - inhibit lock taken, fd {}", delayLockFd); + return delayLockFd; +#else + CLog::Log(LOGWARNING, "LogindUPowerSyscall - inhibit lock support not compiled in"); + return -1; +#endif +} + +void CLogindUPowerSyscall::ReleaseDelayLockSleep() +{ + ReleaseDelayLock(m_delayLockSleepFd, "sleep"); + m_delayLockSleepFd = -1; +} + +void CLogindUPowerSyscall::ReleaseDelayLockShutdown() +{ + ReleaseDelayLock(m_delayLockShutdownFd, "shutdown"); + m_delayLockShutdownFd = -1; +} + +void CLogindUPowerSyscall::ReleaseDelayLock(int lockFd, const char *what) +{ + if (lockFd != -1) + { + close(lockFd); + CLog::Log(LOGDEBUG, "LogindUPowerSyscall - delay lock {} released", what); + } +} diff --git a/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h new file mode 100644 index 0000000..998e706 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/LogindUPowerSyscall.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Denis Yantarev + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusUtil.h" +#include "powermanagement/IPowerSyscall.h" + +class CLogindUPowerSyscall : public CAbstractPowerSyscall +{ +public: + CLogindUPowerSyscall(); + ~CLogindUPowerSyscall() override; + bool Powerdown() override; + bool Suspend() override; + bool Hibernate() override; + bool Reboot() override; + bool CanPowerdown() override; + bool CanSuspend() override; + bool CanHibernate() override; + bool CanReboot() override; + int BatteryLevel() override; + bool PumpPowerEvents(IPowerEventsCallback *callback) override; + // we don't require UPower because everything except the battery level works fine without it + static bool HasLogind(); +private: + CDBusConnection m_connection; + bool m_canPowerdown; + bool m_canSuspend; + bool m_canHibernate; + bool m_canReboot; + bool m_hasUPower; + bool m_lowBattery; + int m_batteryLevel; + int m_delayLockSleepFd = -1; // file descriptor for the logind sleep delay lock + int m_delayLockShutdownFd = -1; // file descriptor for the logind powerdown delay lock + void UpdateBatteryLevel(); + void InhibitDelayLockSleep(); + void InhibitDelayLockShutdown(); + int InhibitDelayLock(const char *what); + void ReleaseDelayLockSleep(); + void ReleaseDelayLockShutdown(); + void ReleaseDelayLock(int lockFd, const char *what); + static bool LogindSetPowerState(const char *state); + static bool LogindCheckCapability(const char *capability); +}; diff --git a/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp b/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp new file mode 100644 index 0000000..27ed2f4 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/UPowerSyscall.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "UPowerSyscall.h" + +#include "utils/log.h" + +CUPowerSource::CUPowerSource(const char *powerSource) +{ + if(powerSource == NULL) + m_powerSource = ""; + else + m_powerSource = powerSource; + + CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", m_powerSource.c_str(), "org.freedesktop.UPower.Device"); + m_isRechargeable = properties["IsRechargeable"].asBoolean(); + Update(); +} + +CUPowerSource::~CUPowerSource() = default; + +void CUPowerSource::Update() +{ + CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", m_powerSource.c_str(), "org.freedesktop.UPower.Device"); + m_batteryLevel = properties["Percentage"].asDouble(); +} + +bool CUPowerSource::IsRechargeable() +{ + return m_isRechargeable; +} + +double CUPowerSource::BatteryLevel() +{ + return m_batteryLevel; +} + +CUPowerSyscall::CUPowerSyscall() +{ + CLog::Log(LOGINFO, "Selected UPower as PowerSyscall"); + + m_lowBattery = false; + + //! @todo do not use dbus_connection_pop_message() that requires the use of a + //! private connection + if (m_connection.Connect(DBUS_BUS_SYSTEM, true)) + { + dbus_connection_set_exit_on_disconnect(m_connection, false); + + CDBusError error; + dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UPower'", error); + dbus_connection_flush(m_connection); + + if (error) + { + error.Log("UPower: Failed to attach to signal"); + m_connection.Destroy(); + } + } + + m_CanPowerdown = false; + m_CanReboot = false; + + UpdateCapabilities(); + + EnumeratePowerSources(); +} + +bool CUPowerSyscall::Powerdown() +{ + return false; +} + +bool CUPowerSyscall::Suspend() +{ + // UPower 0.9.1 does not signal sleeping unless you tell that its about to sleep... + CDBusMessage aboutToSleepMessage("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "AboutToSleep"); + aboutToSleepMessage.SendAsyncSystem(); + + CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "Suspend"); + return message.SendAsyncSystem(); +} + +bool CUPowerSyscall::Hibernate() +{ + // UPower 0.9.1 does not signal sleeping unless you tell that its about to sleep... + CDBusMessage aboutToSleepMessage("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "AboutToSleep"); + aboutToSleepMessage.SendAsyncSystem(); + + CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "Hibernate"); + return message.SendAsyncSystem(); +} + +bool CUPowerSyscall::Reboot() +{ + return false; +} + +bool CUPowerSyscall::CanPowerdown() +{ + return m_CanPowerdown; +} + +bool CUPowerSyscall::CanSuspend() +{ + return m_CanSuspend; +} + +bool CUPowerSyscall::CanHibernate() +{ + return m_CanHibernate; +} + +bool CUPowerSyscall::CanReboot() +{ + return m_CanReboot; +} + +int CUPowerSyscall::BatteryLevel() +{ + unsigned int nBatteryCount = 0; + double subCapacity = 0; + double batteryLevel = 0; + + for (auto& itr : m_powerSources) + { + itr.Update(); + if (itr.IsRechargeable()) + { + nBatteryCount++; + subCapacity += itr.BatteryLevel(); + } + } + + if(nBatteryCount) + batteryLevel = subCapacity / (double)nBatteryCount; + + return (int) batteryLevel; +} + +void CUPowerSyscall::EnumeratePowerSources() +{ + CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices"); + DBusMessage *reply = message.SendSystem(); + if (reply) + { + char** source = NULL; + int length = 0; + + if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &source, &length, DBUS_TYPE_INVALID)) + { + for (int i = 0; i < length; i++) + { + m_powerSources.emplace_back(source[i]); + } + + dbus_free_string_array(source); + } + } +} + +bool CUPowerSyscall::HasUPower() +{ + return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices"); +} + +bool CUPowerSyscall::PumpPowerEvents(IPowerEventsCallback *callback) +{ + bool result = false; + + if (m_connection) + { + dbus_connection_read_write(m_connection, 0); + DBusMessagePtr msg(dbus_connection_pop_message(m_connection)); + + if (msg) + { + result = true; + if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Sleeping")) + callback->OnSleep(); + else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Resuming")) + callback->OnWake(); + else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UPower", "Changed")) + { + bool lowBattery = m_lowBattery; + UpdateCapabilities(); + if (m_lowBattery && !lowBattery) + callback->OnLowBattery(); + } + else + CLog::Log(LOGDEBUG, "UPower: Received an unknown signal {}", + dbus_message_get_member(msg.get())); + } + } + return result; +} + +void CUPowerSyscall::UpdateCapabilities() +{ + m_CanSuspend = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "CanSuspend").asBoolean(false); + m_CanHibernate = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "CanHibernate").asBoolean(false); +} diff --git a/xbmc/platform/linux/powermanagement/UPowerSyscall.h b/xbmc/platform/linux/powermanagement/UPowerSyscall.h new file mode 100644 index 0000000..aed08d8 --- /dev/null +++ b/xbmc/platform/linux/powermanagement/UPowerSyscall.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusUtil.h" +#include "powermanagement/IPowerSyscall.h" + +#include <list> +#include <string> + +class CUPowerSource +{ +public: + CUPowerSource(const char *powerSource); + ~CUPowerSource(); + + void Update(); + bool IsRechargeable(); + double BatteryLevel(); + +private: + std::string m_powerSource; + bool m_isRechargeable; + double m_batteryLevel; +}; + +class CUPowerSyscall : public CAbstractPowerSyscall +{ +public: + CUPowerSyscall(); + bool Powerdown() override; + bool Suspend() override; + bool Hibernate() override; + bool Reboot() override; + bool CanPowerdown() override; + bool CanSuspend() override; + bool CanHibernate() override; + bool CanReboot() override; + int BatteryLevel() override; + bool PumpPowerEvents(IPowerEventsCallback *callback) override; + static bool HasUPower(); +protected: + bool m_CanPowerdown; + bool m_CanSuspend; + bool m_CanHibernate; + bool m_CanReboot; + + void UpdateCapabilities(); +private: + std::list<CUPowerSource> m_powerSources; + CDBusConnection m_connection; + + bool m_lowBattery; + void EnumeratePowerSources(); +}; diff --git a/xbmc/platform/linux/sse4/CMakeLists.txt b/xbmc/platform/linux/sse4/CMakeLists.txt new file mode 100644 index 0000000..6ec73f1 --- /dev/null +++ b/xbmc/platform/linux/sse4/CMakeLists.txt @@ -0,0 +1,8 @@ +if(HAVE_SSE4_1) + set(SOURCES CopyFrame.cpp) + set(HEADERS DllLibSSE4.h) + + core_add_shared_library(sse4) + set_target_properties(sse4 PROPERTIES FOLDER lib) + target_compile_options(sse4 PRIVATE -msse4.1) +endif() diff --git a/xbmc/platform/linux/sse4/CopyFrame.cpp b/xbmc/platform/linux/sse4/CopyFrame.cpp new file mode 100644 index 0000000..2147c3c --- /dev/null +++ b/xbmc/platform/linux/sse4/CopyFrame.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + */ + +#include <smmintrin.h> + +#define CACHED_BUFFER_SIZE 4096 + +extern "C" +{ + +/* + * http://software.intel.com/en-us/articles/copying-accelerated-video-decode-frame-buffers + * COPIES VIDEO FRAMES FROM USWC MEMORY TO WB SYSTEM MEMORY VIA CACHED BUFFER + * ASSUMES PITCH IS A MULTIPLE OF 64B CACHE LINE SIZE, WIDTH MAY NOT BE + */ +void copy_frame( void * pSrc, void * pDest, void * pCacheBlock, + unsigned int width, unsigned int height, unsigned int pitch ) +{ + __m128i x0, x1, x2, x3; + __m128i *pLoad; + __m128i *pStore; + __m128i *pCache; + unsigned int x, y, yLoad, yStore; + unsigned int rowsPerBlock; + unsigned int width64; + unsigned int extraPitch; + + + rowsPerBlock = CACHED_BUFFER_SIZE / pitch; + width64 = (width + 63) & ~0x03f; + extraPitch = (pitch - width64) / 16; + + pLoad = (__m128i *)pSrc; + pStore = (__m128i *)pDest; + + // COPY THROUGH 4KB CACHED BUFFER + for( y = 0; y < height; y += rowsPerBlock ) + { + // ROWS LEFT TO COPY AT END + if( y + rowsPerBlock > height ) + rowsPerBlock = height - y; + + pCache = (__m128i *)pCacheBlock; + + _mm_mfence(); + + // LOAD ROWS OF PITCH WIDTH INTO CACHED BLOCK + for( yLoad = 0; yLoad < rowsPerBlock; yLoad++ ) + { + // COPY A ROW, CACHE LINE AT A TIME + for( x = 0; x < pitch; x +=64 ) + { + x0 = _mm_stream_load_si128( pLoad +0 ); + x1 = _mm_stream_load_si128( pLoad +1 ); + x2 = _mm_stream_load_si128( pLoad +2 ); + x3 = _mm_stream_load_si128( pLoad +3 ); + + _mm_store_si128( pCache +0, x0 ); + _mm_store_si128( pCache +1, x1 ); + _mm_store_si128( pCache +2, x2 ); + _mm_store_si128( pCache +3, x3 ); + + pCache += 4; + pLoad += 4; + } + } + + _mm_mfence(); + + pCache = (__m128i *)pCacheBlock; + + // STORE ROWS OF FRAME WIDTH FROM CACHED BLOCK + for( yStore = 0; yStore < rowsPerBlock; yStore++ ) + { + // copy a row, cache line at a time + for( x = 0; x < width64; x +=64 ) + { + x0 = _mm_load_si128( pCache ); + x1 = _mm_load_si128( pCache +1 ); + x2 = _mm_load_si128( pCache +2 ); + x3 = _mm_load_si128( pCache +3 ); + + _mm_stream_si128( pStore, x0 ); + _mm_stream_si128( pStore +1, x1 ); + _mm_stream_si128( pStore +2, x2 ); + _mm_stream_si128( pStore +3, x3 ); + + pCache += 4; + pStore += 4; + } + + pCache += extraPitch; + pStore += extraPitch; + } + } +} +} diff --git a/xbmc/platform/linux/sse4/DllLibSSE4.h b/xbmc/platform/linux/sse4/DllLibSSE4.h new file mode 100644 index 0000000..a89f713 --- /dev/null +++ b/xbmc/platform/linux/sse4/DllLibSSE4.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DynamicDll.h" + +extern "C" { + +} + +class DllLibSSE4Interface +{ +public: + virtual ~DllLibSSE4Interface() = default; + virtual void copy_frame(void * pSrc, void * pDest, void * pCacheBlock, unsigned int width, unsigned int height, unsigned int pitch) = 0; +}; + +class DllLibSSE4 : public DllDynamic, DllLibSSE4Interface +{ + DECLARE_DLL_WRAPPER(DllLibSSE4, DLL_PATH_LIBSSE4) + DEFINE_METHOD6(void, copy_frame, (void *p1, void *p2, void *p3, unsigned int p4, unsigned int p5, unsigned int p6)) + + BEGIN_METHOD_RESOLVE() + RESOLVE_METHOD(copy_frame) + END_METHOD_RESOLVE() +}; diff --git a/xbmc/platform/linux/storage/CMakeLists.txt b/xbmc/platform/linux/storage/CMakeLists.txt new file mode 100644 index 0000000..223655a --- /dev/null +++ b/xbmc/platform/linux/storage/CMakeLists.txt @@ -0,0 +1,19 @@ +set(SOURCES LinuxStorageProvider.cpp) + +set(HEADERS LinuxStorageProvider.h) + +if(DBUS_FOUND) + list(APPEND SOURCES UDisksProvider.cpp + UDisks2Provider.cpp) + list(APPEND HEADERS UDisksProvider.h + UDisks2Provider.h) +endif() + +if(UDEV_FOUND) + list(APPEND SOURCES UDevProvider.cpp) + list(APPEND HEADERS UDevProvider.h) +endif() + +if(SOURCES) + core_add_library(platform_linux_storage) +endif() diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.cpp b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp new file mode 100644 index 0000000..6206466 --- /dev/null +++ b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LinuxStorageProvider.h" +#include "guilib/LocalizeStrings.h" +#include "UDevProvider.h" +#ifdef HAS_DBUS +#include "UDisksProvider.h" +#include "UDisks2Provider.h" +#endif +#include "platform/posix/PosixMountProvider.h" + +std::unique_ptr<IStorageProvider> IStorageProvider::CreateInstance() +{ + return std::make_unique<CLinuxStorageProvider>(); +} + +CLinuxStorageProvider::CLinuxStorageProvider() +{ + m_instance = NULL; + +#ifdef HAS_DBUS + if (CUDisks2Provider::HasUDisks2()) + m_instance = new CUDisks2Provider(); + else if (CUDisksProvider::HasUDisks()) + m_instance = new CUDisksProvider(); +#endif +#ifdef HAVE_LIBUDEV + if (m_instance == NULL) + m_instance = new CUDevProvider(); +#endif + + if (m_instance == NULL) + m_instance = new CPosixMountProvider(); +} + +CLinuxStorageProvider::~CLinuxStorageProvider() +{ + delete m_instance; +} + +void CLinuxStorageProvider::Initialize() +{ + m_instance->Initialize(); +} + +void CLinuxStorageProvider::Stop() +{ + m_instance->Stop(); +} + +void CLinuxStorageProvider::GetLocalDrives(VECSOURCES &localDrives) +{ + // Home directory + CMediaSource share; + share.strPath = getenv("HOME"); + share.strName = g_localizeStrings.Get(21440); + share.m_ignore = true; + share.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; + localDrives.push_back(share); + share.strPath = "/"; + share.strName = g_localizeStrings.Get(21453); + localDrives.push_back(share); + + m_instance->GetLocalDrives(localDrives); +} + +void CLinuxStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) +{ + m_instance->GetRemovableDrives(removableDrives); +} + +bool CLinuxStorageProvider::Eject(const std::string& mountpath) +{ + return m_instance->Eject(mountpath); +} + +std::vector<std::string> CLinuxStorageProvider::GetDiskUsage() +{ + return m_instance->GetDiskUsage(); +} + +bool CLinuxStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) +{ + return m_instance->PumpDriveChangeEvents(callback); +} diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.h b/xbmc/platform/linux/storage/LinuxStorageProvider.h new file mode 100644 index 0000000..e1c575f --- /dev/null +++ b/xbmc/platform/linux/storage/LinuxStorageProvider.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "storage/IStorageProvider.h" + +#include <vector> + +class CLinuxStorageProvider : public IStorageProvider +{ +public: + CLinuxStorageProvider(); + ~CLinuxStorageProvider() override; + + void Initialize() override; + void Stop() override; + void GetLocalDrives(VECSOURCES &localDrives) override; + void GetRemovableDrives(VECSOURCES &removableDrives) override; + bool Eject(const std::string& mountpath) override; + std::vector<std::string> GetDiskUsage() override; + bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; + +private: + IStorageProvider *m_instance; +}; diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp new file mode 100644 index 0000000..7123e87 --- /dev/null +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "UDevProvider.h" + +#include "platform/posix/PosixMountProvider.h" +#include "utils/log.h" +#include "utils/URIUtils.h" + +extern "C" { +#include <libudev.h> +#include <poll.h> +} + +static const char *get_mountpoint(const char *devnode) +{ + static char buf[4096]; + const char *delim = " "; + const char *mountpoint = nullptr; + FILE *fp = fopen("/proc/mounts", "r"); + if (!fp) + return nullptr; + + while (fgets(buf, sizeof (buf), fp)) + { + const char *node = strtok(buf, delim); + if (strcmp(node, devnode) == 0) + { + mountpoint = strtok(nullptr, delim); + break; + } + } + + if (mountpoint) + { + // If mount point contain characters like space, it is converted to + // "\040". This situation should be handled. + char *c1, *c2; + for (c1 = c2 = const_cast<char*>(mountpoint); *c2; ++c1) + { + if (*c2 == '\\') + { + *c1 = (((c2[1] - '0') << 6) | ((c2[2] - '0') << 3) | (c2[3] - '0')); + c2 += 4; + continue; + } + if (c1 != c2) + *c1 = *c2; + ++c2; + } + *c1 = *c2; + } + + fclose(fp); + return mountpoint; +} + +CUDevProvider::CUDevProvider() +{ + m_udev = nullptr; + m_udevMon = nullptr; +} + +void CUDevProvider::Initialize() +{ + CLog::Log(LOGDEBUG, "Selected UDev as storage provider"); + + m_udev = udev_new(); + if (!m_udev) + { + CLog::Log(LOGERROR, "{} - failed to allocate udev context", __FUNCTION__); + return; + } + /* set up a devices monitor that listen for any device change */ + m_udevMon = udev_monitor_new_from_netlink(m_udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "block", "disk"); + udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "block", "partition"); + udev_monitor_enable_receiving(m_udevMon); + + PumpDriveChangeEvents(nullptr); +} + +void CUDevProvider::Stop() +{ + udev_monitor_unref(m_udevMon); + udev_unref(m_udev); +} + +void CUDevProvider::GetDisks(VECSOURCES& disks, bool removable) +{ + // enumerate existing block devices + struct udev_enumerate *u_enum = udev_enumerate_new(m_udev); + if (!u_enum) + { + fprintf(stderr, "Error: udev_enumerate_new(udev)\n"); + return; + } + + udev_enumerate_add_match_subsystem(u_enum, "block"); + udev_enumerate_add_match_property(u_enum, "DEVTYPE", "disk"); + udev_enumerate_add_match_property(u_enum, "DEVTYPE", "partition"); + udev_enumerate_scan_devices(u_enum); + + struct udev_list_entry *u_list_ent; + struct udev_list_entry *u_first_list_ent; + u_first_list_ent = udev_enumerate_get_list_entry(u_enum); + udev_list_entry_foreach(u_list_ent, u_first_list_ent) + { + const char *name = udev_list_entry_get_name(u_list_ent); + struct udev *context = udev_enumerate_get_udev(u_enum); + struct udev_device *device = udev_device_new_from_syspath(context, name); + if (!device) + continue; + + // filter out devices without devnode + const char* devnode = udev_device_get_devnode(device); + if (!devnode) + { + udev_device_unref(device); + continue; + } + + // filter out devices that are not mounted + const char* mountpoint = get_mountpoint(devnode); + if (!mountpoint) + { + udev_device_unref(device); + continue; + } + + // filter out root partition + if (strcmp(mountpoint, "/") == 0) + { + udev_device_unref(device); + continue; + } + + // filter out things mounted on /tmp + if (strstr(mountpoint, "/tmp")) + { + udev_device_unref(device); + continue; + } + + // look for devices on the usb bus, or mounted on */media/ (sdcards), or optical devices + const char *bus = udev_device_get_property_value(device, "ID_BUS"); + const char *optical = udev_device_get_property_value(device, "ID_CDROM"); // matches also DVD, Blu-ray + bool isRemovable = ((bus && strstr(bus, "usb")) || + (optical && strstr(optical,"1")) || + (mountpoint && strstr(mountpoint, "/media/"))); + + // filter according to requested device type + if (removable != isRemovable) + { + udev_device_unref(device); + continue; + } + + const char *udev_label = udev_device_get_property_value(device, "ID_FS_LABEL"); + std::string label; + if (udev_label) + label = udev_label; + else + label = URIUtils::GetFileName(mountpoint); + + CMediaSource share; + share.strName = label; + share.strPath = mountpoint; + share.m_ignore = true; + if (isRemovable) + { + if (optical) + share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + else + share.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE; + } + else + share.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; + + disks.push_back(share); + udev_device_unref(device); + } + udev_enumerate_unref(u_enum); +} + +void CUDevProvider::GetLocalDrives(VECSOURCES &localDrives) +{ + GetDisks(localDrives, false); +} + +void CUDevProvider::GetRemovableDrives(VECSOURCES &removableDrives) +{ + GetDisks(removableDrives, true); +} + +bool CUDevProvider::Eject(const std::string& mountpath) +{ + // just go ahead and try to umount the disk + // if it does umount, life is good, if not, no loss. + std::string cmd = "umount \"" + mountpath + "\""; + int status = system(cmd.c_str()); + + if (status == 0) + return true; + + return false; +} + +std::vector<std::string> CUDevProvider::GetDiskUsage() +{ + CPosixMountProvider legacy; + return legacy.GetDiskUsage(); +} + +bool CUDevProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) +{ + bool changed = false; + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(udev_monitor_get_fd(m_udevMon), &readfds); + + // non-blocking, check the file descriptor for received data + struct timeval tv = {}; + int count = select(udev_monitor_get_fd(m_udevMon) + 1, &readfds, nullptr, nullptr, &tv); + if (count < 0) + return false; + + if (FD_ISSET(udev_monitor_get_fd(m_udevMon), &readfds)) + { + struct udev_device* dev = udev_monitor_receive_device(m_udevMon); + if (!dev) + return false; + + const char* action = udev_device_get_action(dev); + const char* devnode = udev_device_get_devnode(dev); + if (action && devnode) + { + MEDIA_DETECT::STORAGE::StorageDevice storageDevice; + const char *udev_label = udev_device_get_property_value(dev, "ID_FS_LABEL"); + const char* mountpoint = get_mountpoint(devnode); + if (udev_label) + storageDevice.label = udev_label; + else if (mountpoint) + { + storageDevice.label = URIUtils::GetFileName(mountpoint); + storageDevice.path = mountpoint; + } + + const char *fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE"); + if (mountpoint && strcmp(action, "add") == 0 && (fs_usage && strcmp(fs_usage, "filesystem") == 0)) + { + CLog::Log(LOGINFO, "UDev: Added {}", mountpoint); + if (callback) + callback->OnStorageAdded(storageDevice); + changed = true; + } + if (strcmp(action, "remove") == 0 && (fs_usage && strcmp(fs_usage, "filesystem") == 0)) + { + if (callback) + callback->OnStorageSafelyRemoved(storageDevice); + changed = true; + } + // browse disk dialog is not wanted for blu-rays + const char *bd = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_BD"); + if (strcmp(action, "change") == 0 && !(bd && strcmp(bd, "1") == 0)) + { + const char *optical = udev_device_get_property_value(dev, "ID_CDROM"); + bool isOptical = optical && (strcmp(optical, "1") != 0); + storageDevice.type = + isOptical ? MEDIA_DETECT::STORAGE::Type::OPTICAL : MEDIA_DETECT::STORAGE::Type::UNKNOWN; + + if (mountpoint && !isOptical) + { + CLog::Log(LOGINFO, "UDev: Changed / Added {}", mountpoint); + if (callback) + callback->OnStorageAdded(storageDevice); + changed = true; + } + const char *eject_request = udev_device_get_property_value(dev, "DISK_EJECT_REQUEST"); + if (eject_request && strcmp(eject_request, "1") == 0) + { + if (callback) + callback->OnStorageSafelyRemoved(storageDevice); + changed = true; + } + } + } + udev_device_unref(dev); + } + + return changed; +} diff --git a/xbmc/platform/linux/storage/UDevProvider.h b/xbmc/platform/linux/storage/UDevProvider.h new file mode 100644 index 0000000..a4c83b3 --- /dev/null +++ b/xbmc/platform/linux/storage/UDevProvider.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "storage/IStorageProvider.h" + +#include <string> +#include <vector> + +struct udev; +struct udev_monitor; + +class CUDevProvider : public IStorageProvider +{ +public: + CUDevProvider(); + ~CUDevProvider() override = default; + + void Initialize() override; + void Stop() override; + + void GetLocalDrives(VECSOURCES &localDrives) override; + void GetRemovableDrives(VECSOURCES &removableDrives) override; + + bool Eject(const std::string& mountpath) override; + + std::vector<std::string> GetDiskUsage() override; + + bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; + +private: + void GetDisks(VECSOURCES& devices, bool removable); + + struct udev *m_udev; + struct udev_monitor *m_udevMon; +}; diff --git a/xbmc/platform/linux/storage/UDisks2Provider.cpp b/xbmc/platform/linux/storage/UDisks2Provider.cpp new file mode 100644 index 0000000..b3431c7 --- /dev/null +++ b/xbmc/platform/linux/storage/UDisks2Provider.cpp @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "UDisks2Provider.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include "platform/posix/PosixMountProvider.h" + +#include <algorithm> +#include <functional> + +#define BOOL2SZ(b) ((b) ? "true" : "false") + +#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" + +#define UDISKS2_SERVICE_UDISKS2 "org.freedesktop.UDisks2" + +#define UDISKS2_PATH_MANAGER "/org/freedesktop/UDisks2/Manager" +#define UDISKS2_PATH_UDISKS2 "/org/freedesktop/UDisks2" + +#define UDISKS2_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block" +#define UDISKS2_INTERFACE_DRIVE "org.freedesktop.UDisks2.Drive" +#define UDISKS2_INTERFACE_FILESYSTEM "org.freedesktop.UDisks2.Filesystem" +#define UDISKS2_INTERFACE_MANAGER "org.freedesktop.UDisks2.Manager" + +#define UDISKS2_MATCH_RULE "type='signal',sender='" UDISKS2_SERVICE_UDISKS2 "',path_namespace='" UDISKS2_PATH_UDISKS2 "'" + +CUDisks2Provider::Drive::Drive(const char *object) : m_object(object) +{ +} + +bool CUDisks2Provider::Drive::IsOptical() const +{ + return std::any_of(m_mediaCompatibility.begin(), m_mediaCompatibility.end(), + [](const std::string& kind) { return kind.compare(0, 7, "optical") == 0; }); +} + +std::string CUDisks2Provider::Drive::ToString() const +{ + return StringUtils::Format("Drive {}: IsRemovable {} IsOptical {}", m_object, + BOOL2SZ(m_isRemovable), BOOL2SZ(IsOptical())); +} + +CUDisks2Provider::Block::Block(const char *object) : m_object(object) +{ +} + +bool CUDisks2Provider::Block::IsReady() +{ + return m_drive != nullptr; +} + +std::string CUDisks2Provider::Block::ToString() const +{ + return StringUtils::Format("Block device {}: Device {} Label {} IsSystem: {} Drive {}", m_object, + m_device, m_label.empty() ? "none" : m_label, BOOL2SZ(m_isSystem), + m_driveobject.empty() ? "none" : m_driveobject); +} + +CUDisks2Provider::Filesystem::Filesystem(const char *object) : m_object(object) +{ +} + +std::string CUDisks2Provider::Filesystem::ToString() const +{ + return StringUtils::Format("Filesystem {}: IsMounted {} MountPoint {}", m_object, + BOOL2SZ(m_isMounted), m_mountPoint.empty() ? "none" : m_mountPoint); +} + +MEDIA_DETECT::STORAGE::StorageDevice CUDisks2Provider::Filesystem::ToStorageDevice() const +{ + MEDIA_DETECT::STORAGE::StorageDevice device; + device.label = GetDisplayName(); + device.path = GetMountPoint(); + device.type = GetStorageType(); + return device; +} + +bool CUDisks2Provider::Filesystem::IsReady() const +{ + return m_block != nullptr && m_block->IsReady(); +} + +bool CUDisks2Provider::Filesystem::IsOptical() const +{ + return m_block->m_drive->IsOptical(); +} + +MEDIA_DETECT::STORAGE::Type CUDisks2Provider::Filesystem::GetStorageType() const +{ + if (m_block == nullptr || !m_block->IsReady()) + return MEDIA_DETECT::STORAGE::Type::UNKNOWN; + + if (IsOptical()) + return MEDIA_DETECT::STORAGE::Type::OPTICAL; + + return MEDIA_DETECT::STORAGE::Type::UNKNOWN; +} + +std::string CUDisks2Provider::Filesystem::GetMountPoint() const +{ + return m_mountPoint; +} + +std::string CUDisks2Provider::Filesystem::GetObject() const +{ + return m_object; +} + +void CUDisks2Provider::Filesystem::ResetMountPoint() +{ + m_mountPoint.clear(); + m_isMounted = false; +} + +void CUDisks2Provider::Filesystem::SetMountPoint(const std::string& mountPoint) +{ + m_mountPoint = mountPoint; + m_isMounted = true; +} + +bool CUDisks2Provider::Filesystem::IsMounted() const +{ + return m_isMounted; +} + +bool CUDisks2Provider::Filesystem::Mount() +{ + if (m_block->m_isSystem) { + CLog::Log(LOGDEBUG, "UDisks2: Skip mount of system device {}", ToString()); + return false; + } + else if (m_isMounted) + { + CLog::Log(LOGDEBUG, "UDisks2: Skip mount of already mounted device {}", ToString()); + return false; + } + else + { + CLog::Log(LOGDEBUG, "UDisks2: Mounting {}", m_block->m_device); + CDBusMessage message(UDISKS2_SERVICE_UDISKS2, m_object, UDISKS2_INTERFACE_FILESYSTEM, "Mount"); + AppendEmptyOptions(message.GetArgumentIter()); + DBusMessage *reply = message.SendSystem(); + return (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR); + } +} + +bool CUDisks2Provider::Filesystem::Unmount() +{ + if (m_block->m_isSystem) { + CLog::Log(LOGDEBUG, "UDisks2: Skip unmount of system device {}", ToString()); + return false; + } + else if (!m_isMounted) + { + CLog::Log(LOGDEBUG, "UDisks2: Skip unmount of not mounted device {}", ToString()); + return false; + } + else + { + CLog::Log(LOGDEBUG, "UDisks2: Unmounting {}", m_block->m_device); + CDBusMessage message(UDISKS2_SERVICE_UDISKS2, m_object, UDISKS2_INTERFACE_FILESYSTEM, "Unmount"); + AppendEmptyOptions(message.GetArgumentIter()); + DBusMessage *reply = message.SendSystem(); + return (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR); + } +} + +std::string CUDisks2Provider::Filesystem::GetDisplayName() const +{ + if (m_block->m_label.empty()) + { + std::string strSize = StringUtils::SizeToString(m_block->m_size); + return StringUtils::Format("{} {}", strSize, g_localizeStrings.Get(155)); + } + else + return m_block->m_label; +} + +CMediaSource CUDisks2Provider::Filesystem::ToMediaShare() const +{ + CMediaSource source; + source.strPath = m_mountPoint; + source.strName = GetDisplayName(); + if (IsOptical()) + source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + else if (m_block->m_isSystem) + source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; + else + source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE; + source.m_ignore = true; + return source; +} + +bool CUDisks2Provider::Filesystem::IsApproved() const +{ + return IsReady() && + (m_isMounted && m_mountPoint != "/" && m_mountPoint != "/boot" && m_mountPoint.compare(0, 6, "/boot/") != 0) /*|| + m_block->m_drive->m_isOptical*/; +} + +CUDisks2Provider::CUDisks2Provider() +{ + if (!m_connection.Connect(DBUS_BUS_SYSTEM, true)) + { + return; + } + + dbus_connection_set_exit_on_disconnect(m_connection, static_cast<dbus_bool_t>(false)); + + CDBusError error; + dbus_bus_add_match(m_connection, UDISKS2_MATCH_RULE, error); + dbus_connection_flush(m_connection); + + if (error) + { + error.Log("UDisks2: Failed to attach to signal"); + m_connection.Destroy(); + } +} + +CUDisks2Provider::~CUDisks2Provider() +{ + for (auto &elt : m_filesystems) + { + delete elt.second; + } + m_filesystems.clear(); + + for (auto &elt : m_blocks) + { + delete elt.second; + } + m_blocks.clear(); + + for (auto &elt : m_drives) + { + delete elt.second; + } + m_drives.clear(); +} + +void CUDisks2Provider::Initialize() +{ + CLog::Log(LOGDEBUG, "Selected UDisks2 as storage provider"); + m_daemonVersion = CDBusUtil::GetVariant(UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_MANAGER, UDISKS2_INTERFACE_MANAGER, + "Version").asString(); + CLog::Log(LOGDEBUG, "UDisks2: Daemon version {}", m_daemonVersion); + + CLog::Log(LOGDEBUG, "UDisks2: Querying available devices"); + CDBusMessage message(UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_UDISKS2, DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects"); + DBusMessage *reply = message.SendSystem(); + + if (reply && dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR) + { + HandleManagedObjects(reply); + } +} + +bool CUDisks2Provider::PumpDriveChangeEvents(IStorageEventsCallback *callback) +{ + if (m_connection) + { + dbus_connection_read_write(m_connection, 0); + DBusMessagePtr msg(dbus_connection_pop_message(m_connection)); + + if (msg) + { + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Message received (interface: {}, member: {})", + dbus_message_get_interface(msg.get()), dbus_message_get_member(msg.get())); + + if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded")) + { + HandleInterfacesAdded(msg.get()); + return false; + } + else if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved")) + { + return HandleInterfacesRemoved(msg.get(), callback); + } + else if (dbus_message_is_signal(msg.get(), DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) + { + return HandlePropertiesChanged(msg.get(), callback); + } + } + } + return false; +} + +bool CUDisks2Provider::HasUDisks2() +{ + return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, UDISKS2_SERVICE_UDISKS2, UDISKS2_PATH_UDISKS2, DBUS_INTERFACE_PEER, + "Ping"); +} + +bool CUDisks2Provider::Eject(const std::string &mountpath) +{ + std::string path(mountpath); + URIUtils::RemoveSlashAtEnd(path); + + for (const auto &elt: m_filesystems) + { + Filesystem *fs = elt.second; + if (fs->GetMountPoint() == path) + { + return fs->Unmount(); + } + } + + return false; +} + +std::vector<std::string> CUDisks2Provider::GetDiskUsage() +{ + CPosixMountProvider legacy; + return legacy.GetDiskUsage(); +} + +void CUDisks2Provider::GetDisks(VECSOURCES &devices, bool enumerateRemovable) +{ + for (const auto &elt: m_filesystems) + { + Filesystem *fs = elt.second; + if (fs->IsApproved() && fs->m_block->m_isSystem != enumerateRemovable) + devices.push_back(fs->ToMediaShare()); + } +} + +void CUDisks2Provider::DriveAdded(Drive *drive) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Drive added - {}", drive->ToString()); + + if (m_drives[drive->m_object]) + { + CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! DriveAdded on an indexed drive"); + delete m_drives[drive->m_object]; + } + + m_drives[drive->m_object] = drive; + + for (auto &elt: m_blocks) + { + auto block = elt.second; + if (block->m_driveobject == drive->m_object) + { + block->m_drive = drive; + BlockAdded(block, false); + } + } +} + +bool CUDisks2Provider::DriveRemoved(const std::string& object) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Drive removed ({})", object); + + if (m_drives.count(object) > 0) + { + delete m_drives[object]; + m_drives.erase(object); + } + + for (auto &elt: m_blocks) + { + auto block = elt.second; + if (block->m_driveobject == object) + { + block->m_drive = nullptr; + } + } + + return false; +} + +void CUDisks2Provider::BlockAdded(Block *block, bool isNew) +{ + if (isNew) + { + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Block added - {}", block->ToString()); + + if (m_drives.count(block->m_driveobject) > 0) + block->m_drive = m_drives[block->m_driveobject]; + + if (m_blocks[block->m_object]) + { + CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! BlockAdded on an indexed block device"); + delete m_blocks[block->m_object]; + } + + m_blocks[block->m_object] = block; + } + + + if (m_filesystems.count(block->m_object) > 0) + { + auto fs = m_filesystems[block->m_object]; + fs->m_block = block; + FilesystemAdded(fs, false); + } +} + +bool CUDisks2Provider::BlockRemoved(const std::string& object) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Block removed ({})", object); + + if (m_blocks.count(object) > 0) + { + delete m_blocks[object]; + m_blocks.erase(object); + } + + if (m_filesystems.count(object) > 0) + { + m_filesystems[object]->m_block = nullptr; + } + + return false; +} + +void CUDisks2Provider::FilesystemAdded(Filesystem *fs, bool isNew) +{ + if (isNew) + { + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Filesystem added - {}", fs->ToString()); + + if (m_blocks.count(fs->GetObject()) > 0) + fs->m_block = m_blocks[fs->GetObject()]; + + if (m_filesystems[fs->GetObject()]) + { + CLog::Log(LOGWARNING, "UDisks2: Inconsistency found! FilesystemAdded on an indexed filesystem"); + delete m_filesystems[fs->GetObject()]; + } + + m_filesystems[fs->GetObject()] = fs; + } + + if (fs->IsReady() && !fs->IsMounted()) + { + // optical drives should be always mounted by default unless explicitly disabled by the user + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_handleMounting || + (fs->IsOptical() && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_autoMountOpticalMedia)) + { + fs->Mount(); + } + } +} + +bool CUDisks2Provider::FilesystemRemoved(const char *object, IStorageEventsCallback *callback) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Filesystem removed ({})", object); + bool result = false; + if (m_filesystems.count(object) > 0) + { + auto fs = m_filesystems[object]; + if (fs->IsMounted()) + { + callback->OnStorageUnsafelyRemoved(fs->ToStorageDevice()); + result = true; + } + delete m_filesystems[object]; + m_filesystems.erase(object); + } + return result; +} + +void CUDisks2Provider::HandleManagedObjects(DBusMessage *msg) +{ + DBusMessageIter msgIter, dictIter; + dbus_message_iter_init(msg, &msgIter); + dbus_message_iter_recurse(&msgIter, &dictIter); + while (dbus_message_iter_get_arg_type(&dictIter) == DBUS_TYPE_DICT_ENTRY) + { + DBusMessageIter objIter; + dbus_message_iter_recurse(&dictIter, &objIter); + ParseInterfaces(&objIter); + dbus_message_iter_next(&dictIter); + } +} + +void CUDisks2Provider::HandleInterfacesAdded(DBusMessage *msg) +{ + DBusMessageIter msgIter; + dbus_message_iter_init(msg, &msgIter); + ParseInterfaces(&msgIter); +} + +bool CUDisks2Provider::HandleInterfacesRemoved(DBusMessage *msg, IStorageEventsCallback *callback) +{ + DBusMessageIter msgIter, ifaceIter; + const char *path, *iface; + bool result = false; + dbus_message_iter_init(msg, &msgIter); + dbus_message_iter_get_basic(&msgIter, &path); + dbus_message_iter_next(&msgIter); + dbus_message_iter_recurse(&msgIter, &ifaceIter); + while (dbus_message_iter_get_arg_type(&ifaceIter) == DBUS_TYPE_STRING) + { + dbus_message_iter_get_basic(&ifaceIter, &iface); + result |= RemoveInterface(path, iface, callback); + dbus_message_iter_next(&ifaceIter); + } + return result; +} + +bool CUDisks2Provider::HandlePropertiesChanged(DBusMessage *msg, IStorageEventsCallback *callback) +{ + DBusMessageIter msgIter, propsIter; + const char *object = dbus_message_get_path(msg); + const char *iface; + + dbus_message_iter_init(msg, &msgIter); + dbus_message_iter_get_basic(&msgIter, &iface); + dbus_message_iter_next(&msgIter); + dbus_message_iter_recurse(&msgIter, &propsIter); + + if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0) + { + return DrivePropertiesChanged(object, &propsIter); + } + else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0) + { + return BlockPropertiesChanged(object, &propsIter); + } + else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0) + { + return FilesystemPropertiesChanged(object, &propsIter, callback); + } + + return false; +} + +bool CUDisks2Provider::DrivePropertiesChanged(const char *object, DBusMessageIter *propsIter) +{ + if (m_drives.count(object) > 0) + { + auto drive = m_drives[object]; + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", drive->ToString()); + auto ParseDriveProperty = std::bind(&CUDisks2Provider::ParseDriveProperty, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(drive, propsIter, ParseDriveProperty); + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", drive->ToString()); + } + return false; +} + +bool CUDisks2Provider::BlockPropertiesChanged(const char *object, DBusMessageIter *propsIter) +{ + if (m_blocks.count(object) > 0) + { + auto block = m_blocks[object]; + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", block->ToString()); + auto ParseBlockProperty = std::bind(&CUDisks2Provider::ParseBlockProperty, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(block, propsIter, ParseBlockProperty); + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", block->ToString()); + } + return false; +} + +bool CUDisks2Provider::FilesystemPropertiesChanged(const char *object, DBusMessageIter *propsIter, IStorageEventsCallback *callback) +{ + if (m_filesystems.count(object) > 0) + { + auto fs = m_filesystems[object]; + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: Before update: {}", fs->ToString()); + bool wasMounted = fs->IsMounted(); + auto ParseFilesystemProperty = std::bind(&CUDisks2Provider::ParseFilesystemProperty, this, + std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(fs, propsIter, ParseFilesystemProperty); + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks2: After update: {}", fs->ToString()); + + if (!wasMounted && fs->IsMounted() && fs->IsApproved()) + { + CLog::Log(LOGINFO, "UDisks2: Added {}", fs->GetMountPoint()); + if (callback) + callback->OnStorageAdded(fs->ToStorageDevice()); + return true; + } + else if (wasMounted && !fs->IsMounted()) + { + CLog::Log(LOGINFO, "UDisks2: Removed {}", fs->m_block->m_device); + if (callback) + callback->OnStorageSafelyRemoved(fs->ToStorageDevice()); + return true; + } + } + return false; +} + +bool CUDisks2Provider::RemoveInterface(const char *path, const char *iface, IStorageEventsCallback *callback) +{ + if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0) + { + return DriveRemoved(path); + } + else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0) + { + return BlockRemoved(path); + } + else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0) + { + return FilesystemRemoved(path, callback); + } + else + { + return false; + } +} + + +void CUDisks2Provider::ParseInterfaces(DBusMessageIter *objIter) +{ + DBusMessageIter dictIter; + const char *object; + dbus_message_iter_get_basic(objIter, &object); + dbus_message_iter_next(objIter); + dbus_message_iter_recurse(objIter, &dictIter); + while (dbus_message_iter_get_arg_type(&dictIter) == DBUS_TYPE_DICT_ENTRY) + { + DBusMessageIter ifaceIter, propsIter; + const char *iface; + dbus_message_iter_recurse(&dictIter, &ifaceIter); + dbus_message_iter_get_basic(&ifaceIter, &iface); + dbus_message_iter_next(&ifaceIter); + dbus_message_iter_recurse(&ifaceIter, &propsIter); + ParseInterface(object, iface, &propsIter/*, &discovery*/); + dbus_message_iter_next(&dictIter); + } +} + +void CUDisks2Provider::ParseInterface(const char *object, const char *iface, DBusMessageIter *propsIter) +{ + if (strcmp(iface, UDISKS2_INTERFACE_DRIVE) == 0) + { + auto *drive = new Drive(object); + auto f = std::bind(&CUDisks2Provider::ParseDriveProperty, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(drive, propsIter, f); + DriveAdded(drive); + } + else if (strcmp(iface, UDISKS2_INTERFACE_BLOCK) == 0) + { + auto *block = new Block(object); + auto f = std::bind(&CUDisks2Provider::ParseBlockProperty, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(block, propsIter, f); + BlockAdded(block); + } + else if (strcmp(iface, UDISKS2_INTERFACE_FILESYSTEM) == 0) + { + auto *fs = new Filesystem(object); + auto f = std::bind(&CUDisks2Provider::ParseFilesystemProperty, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + ParseProperties(fs, propsIter, f); + FilesystemAdded(fs); + } +} + + +template<class Object, class Function> +void CUDisks2Provider::ParseProperties(Object *ref, DBusMessageIter *propsIter, Function f) +{ + while (dbus_message_iter_get_arg_type(propsIter) == DBUS_TYPE_DICT_ENTRY) + { + DBusMessageIter propIter, varIter; + const char *key; + + dbus_message_iter_recurse(propsIter, &propIter); + + dbus_message_iter_get_basic(&propIter, &key); + dbus_message_iter_next(&propIter); + + dbus_message_iter_recurse(&propIter, &varIter); + + f(ref, key, &varIter); + + dbus_message_iter_next(propsIter); + } + +} + +void CUDisks2Provider::ParseDriveProperty(Drive *drive, const char *key, DBusMessageIter *varIter) +{ + switch (dbus_message_iter_get_arg_type(varIter)) + { + case DBUS_TYPE_BOOLEAN: + { + dbus_bool_t value; + + if (strcmp(key, "Removable") == 0) + { + dbus_message_iter_get_basic(varIter, &value); + drive->m_isRemovable = static_cast<bool>(value); + } + + break; + } + case DBUS_TYPE_ARRAY: + { + DBusMessageIter arrIter; + + if (strcmp(key, "MediaCompatibility") == 0) + { + dbus_message_iter_recurse(varIter, &arrIter); + while (dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_STRING) + { + const char *compatibility; + dbus_message_iter_get_basic(&arrIter, &compatibility); + drive->m_mediaCompatibility.emplace_back(compatibility); + dbus_message_iter_next(&arrIter); + } + } + + break; + } + default: + break; + } +} + + +void CUDisks2Provider::ParseBlockProperty(Block *block, const char *key, DBusMessageIter *varIter) +{ + switch (dbus_message_iter_get_arg_type(varIter)) + { + case DBUS_TYPE_OBJECT_PATH: + { + const char *value; + + if (strcmp(key, "Drive") == 0) + { + dbus_message_iter_get_basic(varIter, &value); + block->m_driveobject.assign(value); + } + + break; + } + case DBUS_TYPE_STRING: + { + const char *value; + + if (strcmp(key, "IdLabel") == 0) + { + dbus_message_iter_get_basic(varIter, &value); + block->m_label.assign(value); + } + + break; + } + case DBUS_TYPE_BOOLEAN: + { + dbus_bool_t value; + + if (strcmp(key, "HintSystem") == 0) + { + dbus_message_iter_get_basic(varIter, &value); + block->m_isSystem = static_cast<bool>(value); + } + + break; + } + case DBUS_TYPE_UINT64: + { + dbus_uint64_t value; + + if (strcmp(key, "Size") == 0) + { + dbus_message_iter_get_basic(varIter, &value); + block->m_size = value; + } + + break; + } + case DBUS_TYPE_ARRAY: + { + DBusMessageIter arrIter; + + if (strcmp(key, "PreferredDevice") == 0) + { + dbus_message_iter_recurse(varIter, &arrIter); + block->m_device.assign(ParseByteArray(&arrIter)); + } + + break; + } + default: + break; + } +} + +void CUDisks2Provider::ParseFilesystemProperty(Filesystem *fs, const char *key, DBusMessageIter *varIter) +{ + switch (dbus_message_iter_get_arg_type(varIter)) + { + case DBUS_TYPE_ARRAY: + { + DBusMessageIter arrIter; + + if (strcmp(key, "MountPoints") == 0) + { + dbus_message_iter_recurse(varIter, &arrIter); + + if (dbus_message_iter_get_arg_type(&arrIter) == DBUS_TYPE_ARRAY) + { + DBusMessageIter valIter; + + dbus_message_iter_recurse(&arrIter, &valIter); + fs->SetMountPoint(ParseByteArray(&valIter)); + + dbus_message_iter_next(&arrIter); + } + else + { + fs->ResetMountPoint(); + } + } + + break; + } + default: + break; + } +} + +std::string CUDisks2Provider::ParseByteArray(DBusMessageIter *arrIter) +{ + std::ostringstream strStream; + + while (dbus_message_iter_get_arg_type(arrIter) == DBUS_TYPE_BYTE) + { + dbus_int16_t value = 0; + dbus_message_iter_get_basic(arrIter, &value); + if (value == 0) + break; + strStream << static_cast<char>(value); + dbus_message_iter_next(arrIter); + } + + return strStream.str(); +} + +void CUDisks2Provider::AppendEmptyOptions(DBusMessageIter *argsIter) +{ + DBusMessageIter dictIter; + dbus_message_iter_open_container(argsIter, DBUS_TYPE_ARRAY, "{sv}", &dictIter); + dbus_message_iter_close_container(argsIter, &dictIter); +} diff --git a/xbmc/platform/linux/storage/UDisks2Provider.h b/xbmc/platform/linux/storage/UDisks2Provider.h new file mode 100644 index 0000000..b4c85ee --- /dev/null +++ b/xbmc/platform/linux/storage/UDisks2Provider.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusUtil.h" +#include "storage/IStorageProvider.h" + +#include <string> +#include <vector> + +class CUDisks2Provider : public IStorageProvider +{ + class Drive + { + public: + std::string m_object; + bool m_isRemovable = false; + std::vector<std::string> m_mediaCompatibility; + + explicit Drive(const char *object); + ~Drive() = default; + + /*! \brief Check if the drive is optical + * @return true if the drive is optical, false otherwise + */ + bool IsOptical() const; + + /*! \brief Get a representation of the drive as a readable string + * @return drive as a string + */ + std::string ToString() const; + }; + + class Block + { + public: + Drive *m_drive = nullptr; + std::string m_object; + std::string m_driveobject; + std::string m_label; + std::string m_device; + bool m_isSystem = false; + u_int64_t m_size = 0; + + explicit Block(const char *object); + ~Block() = default; + + bool IsReady(); + + /*! \brief Get a representation of the block as a readable string + * @return block as a string + */ + std::string ToString() const; + }; + + class Filesystem + { + public: + Block *m_block = nullptr; + + explicit Filesystem(const char *object); + ~Filesystem() = default; + + bool Mount(); + bool Unmount(); + + /*! \brief Get the device display name/label + * @return the device display name/label + */ + std::string GetDisplayName() const; + + /*! \brief Check if the device is approved/whitelisted + * @return true if the device is approved/whitelisted, false otherwise + */ + bool IsApproved() const; + + /*! \brief Check if the device is mounted + * @return true if the device is mounted, false otherwise + */ + bool IsMounted() const; + + /*! \brief Check if the device is ready + * @return true if the device is ready, false otherwise + */ + bool IsReady() const; + + /*! \brief Check if the device is optical + * @return true if the device is optical, false otherwise + */ + bool IsOptical() const; + + /*! \brief Get the storage type of this device + * @return the storage type (e.g. OPTICAL) or UNKNOWN if + * the type couldn't be detected + */ + MEDIA_DETECT::STORAGE::Type GetStorageType() const; + + /*! \brief Get the device mount point + * @return the device mount point + */ + std::string GetMountPoint() const; + + /*! \brief Reset the device mount point + */ + void ResetMountPoint(); + + /*! \brief Set the device mount point + * @param mountPoint the device mount point + */ + void SetMountPoint(const std::string& mountPoint); + + /*! \brief Get the device dbus object + * @return the device dbus object + */ + std::string GetObject() const; + + /*! \brief Get a representation of the device as a readable string + * @return device as a string + */ + std::string ToString() const; + + /*! \brief Get a representation of the device as a media share + * @return the media share + */ + CMediaSource ToMediaShare() const; + + /*! \brief Get a representation of the device as a storage device abstraction + * @return the storage device abstraction of the device + */ + MEDIA_DETECT::STORAGE::StorageDevice ToStorageDevice() const; + + private: + bool m_isMounted = false; + std::string m_object; + std::string m_mountPoint; + }; + + typedef std::map<std::string, Drive *> DriveMap; + typedef std::map<std::string, Block *> BlockMap; + typedef std::map<std::string, Filesystem *> FilesystemMap; + +public: + CUDisks2Provider(); + ~CUDisks2Provider() override; + + void Initialize() override; + + bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; + + static bool HasUDisks2(); + + bool Eject(const std::string &mountpath) override; + + std::vector<std::string> GetDiskUsage() override; + + void GetLocalDrives(VECSOURCES &localDrives) override + { GetDisks(localDrives, false); } + + void GetRemovableDrives(VECSOURCES &removableDrives) override + { GetDisks(removableDrives, true); } + + void Stop() override + {} + +private: + CDBusConnection m_connection; + + DriveMap m_drives; + BlockMap m_blocks; + FilesystemMap m_filesystems; + + std::string m_daemonVersion; + + void GetDisks(VECSOURCES &devices, bool enumerateRemovable); + + void DriveAdded(Drive *drive); + bool DriveRemoved(const std::string& object); + void BlockAdded(Block *block, bool isNew = true); + bool BlockRemoved(const std::string& object); + void FilesystemAdded(Filesystem *fs, bool isNew = true); + bool FilesystemRemoved(const char *object, IStorageEventsCallback *callback); + + bool HandleInterfacesRemoved(DBusMessage *msg, IStorageEventsCallback *callback); + void HandleInterfacesAdded(DBusMessage *msg); + bool HandlePropertiesChanged(DBusMessage *msg, IStorageEventsCallback *callback); + + bool DrivePropertiesChanged(const char *object, DBusMessageIter *propsIter); + bool BlockPropertiesChanged(const char *object, DBusMessageIter *propsIter); + bool FilesystemPropertiesChanged(const char *object, DBusMessageIter *propsIter, IStorageEventsCallback *callback); + + bool RemoveInterface(const char *path, const char *iface, IStorageEventsCallback *callback); + + template<class Object, class Function> + void ParseProperties(Object *ref, DBusMessageIter *dictIter, Function f); + void ParseInterfaces(DBusMessageIter *dictIter); + void ParseDriveProperty(Drive *drive, const char *key, DBusMessageIter *varIter); + void ParseBlockProperty(Block *block, const char *key, DBusMessageIter *varIter); + void ParseFilesystemProperty(Filesystem *fs, const char *key, DBusMessageIter *varIter); + std::string ParseByteArray(DBusMessageIter *arrIter); + void HandleManagedObjects(DBusMessage *msg); + void ParseInterface(const char *object, const char *iface, DBusMessageIter *propsIter); + + static void AppendEmptyOptions(DBusMessageIter *argsIter); +}; diff --git a/xbmc/platform/linux/storage/UDisksProvider.cpp b/xbmc/platform/linux/storage/UDisksProvider.cpp new file mode 100644 index 0000000..7a2a034 --- /dev/null +++ b/xbmc/platform/linux/storage/UDisksProvider.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "UDisksProvider.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include "platform/posix/PosixMountProvider.h" + +CUDiskDevice::CUDiskDevice(const char *DeviceKitUDI): + m_DeviceKitUDI(DeviceKitUDI) +{ + m_isMounted = false; + m_isMountedByUs = false; + m_isRemovable = false; + m_isPartition = false; + m_isFileSystem = false; + m_isSystemInternal = false; + m_isOptical = false; + m_PartitionSize = 0; + Update(); +} + +void CUDiskDevice::Update() +{ + CVariant properties = CDBusUtil::GetAll("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device"); + + m_isFileSystem = properties["IdUsage"].asString() == "filesystem"; + if (m_isFileSystem) + { + m_UDI = properties["IdUuid"].asString(); + m_Label = properties["IdLabel"].asString(); + m_FileSystem = properties["IdType"].asString(); + } + else + { + m_UDI.clear(); + m_Label.clear(); + m_FileSystem.clear(); + } + + m_isMounted = properties["DeviceIsMounted"].asBoolean(); + if (m_isMounted && !properties["DeviceMountPaths"].empty()) + m_MountPath = properties["DeviceMountPaths"][0].asString(); + else + m_MountPath.clear(); + + m_PartitionSize = properties["PartitionSize"].asUnsignedInteger(); + m_isPartition = properties["DeviceIsPartition"].asBoolean(); + m_isSystemInternal = properties["DeviceIsSystemInternal"].asBoolean(); + m_isOptical = properties["DeviceIsOpticalDisc"].asBoolean(); + if (m_isPartition) + { + CVariant isRemovable = CDBusUtil::GetVariant("org.freedesktop.UDisks", properties["PartitionSlave"].asString().c_str(), "org.freedesktop.UDisks.Device", "DeviceIsRemovable"); + + if ( !isRemovable.isNull() ) + m_isRemovable = isRemovable.asBoolean(); + else + m_isRemovable = false; + } + else + m_isRemovable = properties["DeviceIsRemovable"].asBoolean(); +} + +bool CUDiskDevice::Mount() +{ + if (!m_isMounted && !m_isSystemInternal && m_isFileSystem) + { + CLog::Log(LOGDEBUG, "UDisks: Mounting {}", m_DeviceKitUDI); + CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemMount"); + message.AppendArgument(""); + const char *array[] = {}; + message.AppendArgument(array, 0); + + DBusMessage *reply = message.SendSystem(); + if (reply) + { + char *mountPoint; + if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &mountPoint, DBUS_TYPE_INVALID)) + { + m_MountPath = mountPoint; + CLog::Log(LOGDEBUG, "UDisks: Successfully mounted {} on {}", m_DeviceKitUDI, mountPoint); + m_isMountedByUs = m_isMounted = true; + } + } + + return m_isMounted; + } + else + CLog::Log(LOGDEBUG, "UDisks: Is not able to mount {}", ToString()); + + return false; +} + +bool CUDiskDevice::UnMount() +{ + if (m_isMounted && !m_isSystemInternal && m_isFileSystem) + { + CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemUnmount"); + + const char *array[1]; + message.AppendArgument(array, 0); + + DBusMessage *reply = message.SendSystem(); + if (reply) + m_isMountedByUs = m_isMounted = false; + + return !m_isMounted; + } + else + CLog::Log(LOGDEBUG, "UDisks: Is not able to unmount {}", ToString()); + + return false; +} + +CMediaSource CUDiskDevice::ToMediaShare() const +{ + CMediaSource source; + source.strPath = m_MountPath; + if (m_Label.empty()) + { + std::string strSize = StringUtils::SizeToString(m_PartitionSize); + source.strName = StringUtils::Format("{} {}", strSize, g_localizeStrings.Get(155)); + } + else + source.strName = m_Label; + if (m_isOptical) + source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + else if (m_isSystemInternal) + source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; + else + source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE; + source.m_ignore = true; + return source; +} + +bool CUDiskDevice::IsApproved() const +{ + return (m_isFileSystem && m_isMounted && m_UDI.length() > 0 && (m_FileSystem.length() > 0 && m_FileSystem != "swap") + && m_MountPath != "/" && m_MountPath != "/boot") || m_isOptical; +} + +bool CUDiskDevice::IsOptical() const +{ + return m_isOptical; +} + +MEDIA_DETECT::STORAGE::Type CUDiskDevice::GetStorageType() const +{ + if (IsOptical()) + return MEDIA_DETECT::STORAGE::Type::OPTICAL; + + return MEDIA_DETECT::STORAGE::Type::UNKNOWN; +} + +bool CUDiskDevice::IsMounted() const +{ + return m_isMounted; +} + +std::string CUDiskDevice::GetDisplayName() const +{ + return m_Label; +} + +std::string CUDiskDevice::GetMountPoint() const +{ + return m_MountPath; +} + +bool CUDiskDevice::IsSystemInternal() const +{ + return m_isSystemInternal; +} + +MEDIA_DETECT::STORAGE::StorageDevice CUDiskDevice::ToStorageDevice() const +{ + MEDIA_DETECT::STORAGE::StorageDevice device; + device.label = GetDisplayName(); + device.path = GetMountPoint(); + device.type = GetStorageType(); + return device; +} + +#define BOOL2SZ(b) ((b) ? "true" : "false") + +std::string CUDiskDevice::ToString() const +{ + return StringUtils::Format("DeviceUDI {}: IsFileSystem {} HasFileSystem {} " + "IsSystemInternal {} IsMounted {} IsRemovable {} IsPartition {} " + "IsOptical {}", + m_DeviceKitUDI, BOOL2SZ(m_isFileSystem), m_FileSystem, + BOOL2SZ(m_isSystemInternal), BOOL2SZ(m_isMounted), + BOOL2SZ(m_isRemovable), BOOL2SZ(m_isPartition), BOOL2SZ(m_isOptical)); +} + +CUDisksProvider::CUDisksProvider() +{ + //! @todo do not use dbus_connection_pop_message() that requires the use of a + //! private connection + if (!m_connection.Connect(DBUS_BUS_SYSTEM, true)) + { + return; + } + + dbus_connection_set_exit_on_disconnect(m_connection, false); + + CDBusError error; + dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UDisks'", error); + dbus_connection_flush(m_connection); + + if (error) + { + error.Log("UDisks: Failed to attach to signal"); + m_connection.Destroy(); + } +} + +CUDisksProvider::~CUDisksProvider() +{ + for (auto& itr : m_AvailableDevices) + delete itr.second; + + m_AvailableDevices.clear(); +} + +void CUDisksProvider::Initialize() +{ + CLog::Log(LOGDEBUG, "Selected UDisks as storage provider"); + m_DaemonVersion = atoi(CDBusUtil::GetVariant("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "DaemonVersion").asString().c_str()); + CLog::Log(LOGDEBUG, "UDisks: DaemonVersion {}", m_DaemonVersion); + + CLog::Log(LOGDEBUG, "UDisks: Querying available devices"); + std::vector<std::string> devices = EnumerateDisks(); + for (unsigned int i = 0; i < devices.size(); i++) + DeviceAdded(devices[i].c_str(), NULL); +} + +bool CUDisksProvider::Eject(const std::string& mountpath) +{ + std::string path(mountpath); + URIUtils::RemoveSlashAtEnd(path); + + for (auto& itr : m_AvailableDevices) + { + CUDiskDevice* device = itr.second; + if (device->GetMountPoint() == path) + return device->UnMount(); + } + + return false; +} + +std::vector<std::string> CUDisksProvider::GetDiskUsage() +{ + CPosixMountProvider legacy; + return legacy.GetDiskUsage(); +} + +bool CUDisksProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) +{ + bool result = false; + if (m_connection) + { + dbus_connection_read_write(m_connection, 0); + DBusMessagePtr msg(dbus_connection_pop_message(m_connection)); + + if (msg) + { + char *object; + if (dbus_message_get_args (msg.get(), NULL, DBUS_TYPE_OBJECT_PATH, &object, DBUS_TYPE_INVALID)) + { + result = true; + if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceAdded")) + DeviceAdded(object, callback); + else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceRemoved")) + DeviceRemoved(object, callback); + else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceChanged")) + DeviceChanged(object, callback); + } + } + } + return result; +} + +bool CUDisksProvider::HasUDisks() +{ + return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices"); +} + +void CUDisksProvider::DeviceAdded(const char *object, IStorageEventsCallback *callback) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded ({})", object); + + if (m_AvailableDevices[object]) + { + CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceAdded on an indexed disk"); + delete m_AvailableDevices[object]; + } + + CUDiskDevice *device = NULL; + device = new CUDiskDevice(object); + m_AvailableDevices[object] = device; + + // optical drives should be always mounted by default unless explicitly disabled by the user + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_handleMounting || + (device->IsOptical() && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_autoMountOpticalMedia)) + { + device->Mount(); + } + + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded - {}", device->ToString()); + + if (device->IsMounted() && device->IsApproved()) + { + CLog::Log(LOGINFO, "UDisks: Added {}", device->GetMountPoint()); + if (callback) + callback->OnStorageAdded(device->ToStorageDevice()); + } +} + +void CUDisksProvider::DeviceRemoved(const char *object, IStorageEventsCallback *callback) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceRemoved ({})", object); + + CUDiskDevice *device = m_AvailableDevices[object]; + if (device) + { + if (device->IsMounted() && callback) + callback->OnStorageUnsafelyRemoved(device->ToStorageDevice()); + + delete m_AvailableDevices[object]; + m_AvailableDevices.erase(object); + } +} + +void CUDisksProvider::DeviceChanged(const char *object, IStorageEventsCallback *callback) +{ + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged ({})", object); + + CUDiskDevice *device = m_AvailableDevices[object]; + if (device == NULL) + { + CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceChanged on an unindexed disk"); + DeviceAdded(object, callback); + } + else + { + bool mounted = device->IsMounted(); + // make sure to not silently remount ejected usb thumb drives that user wants to eject + // optical drives should be always mounted by default unless explicitly disabled by the user + const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if (!mounted && device->IsOptical()) + { + if (advancedSettings->m_handleMounting || advancedSettings->m_autoMountOpticalMedia) + { + device->Mount(); + } + } + + device->Update(); + if (!mounted && device->IsMounted() && callback) + callback->OnStorageAdded(device->ToStorageDevice()); + else if (mounted && !device->IsMounted() && callback) + callback->OnStorageSafelyRemoved(device->ToStorageDevice()); + + CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged - {}", device->ToString()); + } +} + +std::vector<std::string> CUDisksProvider::EnumerateDisks() +{ + std::vector<std::string> devices; + CDBusMessage message("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices"); + DBusMessage *reply = message.SendSystem(); + if (reply) + { + char** disks = NULL; + int length = 0; + + if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &disks, &length, DBUS_TYPE_INVALID)) + { + for (int i = 0; i < length; i++) + devices.emplace_back(disks[i]); + + dbus_free_string_array(disks); + } + } + + return devices; +} + +void CUDisksProvider::GetDisks(VECSOURCES& devices, bool EnumerateRemovable) +{ + for (auto& itr : m_AvailableDevices) + { + CUDiskDevice* device = itr.second; + if (device && device->IsApproved() && device->IsSystemInternal() != EnumerateRemovable) + devices.push_back(device->ToMediaShare()); + } +} diff --git a/xbmc/platform/linux/storage/UDisksProvider.h b/xbmc/platform/linux/storage/UDisksProvider.h new file mode 100644 index 0000000..c5372f8 --- /dev/null +++ b/xbmc/platform/linux/storage/UDisksProvider.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DBusUtil.h" +#include "storage/IStorageProvider.h" + +#include <string> +#include <vector> + +class CUDiskDevice +{ +public: + CUDiskDevice(const char *DeviceKitUDI); + ~CUDiskDevice() = default; + + void Update(); + + bool Mount(); + bool UnMount(); + + /*! \brief Check if the device is approved/whitelisted + * @return true if the device is approved/whitelisted, false otherwise + */ + bool IsApproved() const; + + /*! \brief Get the storage type of this device + * @return the storage type (e.g. OPTICAL) or UNKNOWN if + * the type couldn't be detected + */ + MEDIA_DETECT::STORAGE::Type GetStorageType() const; + + /*! \brief Check if the device is optical + * @return true if the device is optical, false otherwise + */ + bool IsOptical() const; + + /*! \brief Check if the device is mounted + * @return true if the device is mounted, false otherwise + */ + bool IsMounted() const; + + /*! \brief Check if the device is internal to the system + * @return true if the device is internal to the system, false otherwise + */ + bool IsSystemInternal() const; + + /*! \brief Get the device display name/label + * @return the device display name/label + */ + std::string GetDisplayName() const; + + /*! \brief Get the device mount point + * @return the device mount point + */ + std::string GetMountPoint() const; + + /*! \brief Get a representation of the device as a readable string + * @return device as a string + */ + std::string ToString() const; + + /*! \brief Get a representation of the device as a media share + * @return the media share + */ + CMediaSource ToMediaShare() const; + + /*! \brief Get a representation of the device as a storage device abstraction + * @return the storage device abstraction of the device + */ + MEDIA_DETECT::STORAGE::StorageDevice ToStorageDevice() const; + +private: + std::string m_UDI; + std::string m_DeviceKitUDI; + std::string m_MountPath; + std::string m_FileSystem; + std::string m_Label; + bool m_isMounted; + bool m_isMountedByUs; + bool m_isRemovable; + bool m_isPartition; + bool m_isFileSystem; + bool m_isSystemInternal; + bool m_isOptical; + int64_t m_PartitionSize; +}; + +class CUDisksProvider : public IStorageProvider +{ +public: + CUDisksProvider(); + ~CUDisksProvider() override; + + void Initialize() override; + void Stop() override { } + + void GetLocalDrives(VECSOURCES &localDrives) override { GetDisks(localDrives, false); } + void GetRemovableDrives(VECSOURCES &removableDrives) override { GetDisks(removableDrives, true); } + + bool Eject(const std::string& mountpath) override; + + std::vector<std::string> GetDiskUsage() override; + + bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; + + static bool HasUDisks(); +private: + typedef std::map<std::string, CUDiskDevice *> DeviceMap; + typedef std::pair<std::string, CUDiskDevice *> DevicePair; + + void DeviceAdded(const char *object, IStorageEventsCallback *callback); + void DeviceRemoved(const char *object, IStorageEventsCallback *callback); + void DeviceChanged(const char *object, IStorageEventsCallback *callback); + + std::vector<std::string> EnumerateDisks(); + + void GetDisks(VECSOURCES& devices, bool EnumerateRemovable); + + int m_DaemonVersion; + + DeviceMap m_AvailableDevices; + + CDBusConnection m_connection; +}; diff --git a/xbmc/platform/linux/test/CMakeLists.txt b/xbmc/platform/linux/test/CMakeLists.txt new file mode 100644 index 0000000..1fb0281 --- /dev/null +++ b/xbmc/platform/linux/test/CMakeLists.txt @@ -0,0 +1,3 @@ +list(APPEND SOURCES TestSysfsPath.cpp) + +core_add_test_library(linux_test) diff --git a/xbmc/platform/linux/test/TestSysfsPath.cpp b/xbmc/platform/linux/test/TestSysfsPath.cpp new file mode 100644 index 0000000..91e7763 --- /dev/null +++ b/xbmc/platform/linux/test/TestSysfsPath.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "platform/linux/SysfsPath.h" +#include "utils/StringUtils.h" + +#include <fstream> +#include <sstream> +#include <string> + +#include <gtest/gtest.h> + +struct TestSysfsPath : public ::testing::Test +{ + std::string GetTestFilePath() + { + std::string tmpdir{"/tmp"}; + const char *test_tmpdir = getenv("TMPDIR"); + + if (test_tmpdir && test_tmpdir[0] != '\0') { + tmpdir.assign(test_tmpdir); + } + + return tmpdir + "/kodi-test-" + StringUtils::CreateUUID(); + } +}; + +TEST_F(TestSysfsPath, SysfsPathTestInt) +{ + std::string filepath = GetTestFilePath(); + std::ofstream m_output(filepath); + + int temp{1234}; + m_output << temp; + m_output.close(); + + CSysfsPath path(filepath); + ASSERT_TRUE(path.Exists()); + EXPECT_EQ(path.Get<int>(), 1234); + EXPECT_EQ(path.Get<float>(), 1234); + EXPECT_EQ(path.Get<double>(), 1234); + EXPECT_EQ(path.Get<uint64_t>(), 1234); + EXPECT_EQ(path.Get<uint16_t>(), 1234); + EXPECT_EQ(path.Get<unsigned int>(), 1234); + EXPECT_EQ(path.Get<unsigned long int>(), 1234); + + std::remove(filepath.c_str()); +} + +TEST_F(TestSysfsPath, SysfsPathTestString) +{ + std::string filepath = GetTestFilePath(); + std::ofstream m_output{filepath}; + + std::string temp{"test"}; + m_output << temp; + m_output.close(); + + CSysfsPath path(filepath); + ASSERT_TRUE(path.Exists()); + EXPECT_EQ(path.Get<std::string>(), "test"); + + std::remove(filepath.c_str()); +} + +TEST_F(TestSysfsPath, SysfsPathTestLongString) +{ + std::string filepath = GetTestFilePath().append("-long"); + std::ofstream m_output{filepath}; + + std::string temp{"test with spaces"}; + m_output << temp; + m_output.close(); + + CSysfsPath path(filepath); + ASSERT_TRUE(path.Exists()); + EXPECT_EQ(path.Get<std::string>(), "test with spaces"); + + std::remove(filepath.c_str()); +} + +TEST_F(TestSysfsPath, SysfsPathTestPathDoesNotExist) +{ + CSysfsPath otherPath{"/thispathdoesnotexist"}; + ASSERT_FALSE(otherPath.Exists()); +} diff --git a/xbmc/platform/linux/threads/CMakeLists.txt b/xbmc/platform/linux/threads/CMakeLists.txt new file mode 100644 index 0000000..b90d55e --- /dev/null +++ b/xbmc/platform/linux/threads/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCES ThreadImplLinux.cpp) +set(HEADERS ThreadImplLinux.h) + +core_add_library(platform_linux_threads) diff --git a/xbmc/platform/linux/threads/ThreadImplLinux.cpp b/xbmc/platform/linux/threads/ThreadImplLinux.cpp new file mode 100644 index 0000000..f4810cf --- /dev/null +++ b/xbmc/platform/linux/threads/ThreadImplLinux.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ThreadImplLinux.h" + +#include "utils/Map.h" +#include "utils/log.h" + +#include <algorithm> +#include <array> +#include <limits.h> + +#include <sys/resource.h> +#include <unistd.h> + +#if !defined(TARGET_ANDROID) && (defined(__GLIBC__) || defined(__UCLIBC__)) +#if defined(__UCLIBC__) || !__GLIBC_PREREQ(2, 30) +#include <sys/syscall.h> +#endif +#endif + +namespace +{ + +constexpr auto nativeThreadPriorityMap = make_map<ThreadPriority, int>({ + {ThreadPriority::LOWEST, -1}, + {ThreadPriority::BELOW_NORMAL, -1}, + {ThreadPriority::NORMAL, 0}, + {ThreadPriority::ABOVE_NORMAL, 1}, + {ThreadPriority::HIGHEST, 1}, +}); + +static_assert(static_cast<size_t>(ThreadPriority::PRIORITY_COUNT) == nativeThreadPriorityMap.size(), + "nativeThreadPriorityMap doesn't match the size of ThreadPriority, did you forget to " + "add/remove a mapping?"); + +constexpr int ThreadPriorityToNativePriority(const ThreadPriority& priority) +{ + const auto it = nativeThreadPriorityMap.find(priority); + if (it != nativeThreadPriorityMap.cend()) + { + return it->second; + } + else + { + throw std::range_error("Priority not found"); + } +} + +#if !defined(TARGET_ANDROID) && (defined(__GLIBC__) || defined(__UCLIBC__)) +#if defined(__UCLIBC__) || !__GLIBC_PREREQ(2, 30) +static pid_t gettid() +{ + return syscall(__NR_gettid); +} +#endif +#endif + +} // namespace + +static int s_maxPriority; +static bool s_maxPriorityIsSet{false}; + +// We need to return what the best number than can be passed +// to SetPriority is. It will basically be relative to the +// the main thread's nice level, inverted (since "higher" priority +// nice levels are actually lower numbers). +static int GetUserMaxPriority(int maxPriority) +{ + if (s_maxPriorityIsSet) + return s_maxPriority; + + // if we're root, then we can do anything. So we'll allow + // max priority. + if (geteuid() == 0) + return maxPriority; + + // get user max prio + struct rlimit limit; + if (getrlimit(RLIMIT_NICE, &limit) != 0) + { + // If we fail getting the limit for nice we just assume we can't raise the priority + return 0; + } + + const int appNice = getpriority(PRIO_PROCESS, getpid()); + const int rlimVal = limit.rlim_cur; + + // according to the docs, limit.rlim_cur shouldn't be zero, yet, here we are. + // if a user has no entry in limits.conf rlim_cur is zero. In this case the best + // nice value we can hope to achieve is '0' as a regular user + const int userBestNiceValue = (rlimVal == 0) ? 0 : (20 - rlimVal); + + // running the app with nice -n 10 -> + // e.g. +10 10 - 0 // default non-root user. + // e.g. +30 10 - -20 // if root with rlimits set. + // running the app default -> + // e.g. 0 0 - 0 // default non-root user. + // e.g. +20 0 - -20 // if root with rlimits set. + const int bestUserSetPriority = appNice - userBestNiceValue; // nice is inverted from prio. + + // static because we only need to check this once. + // we shouldn't expect a user to change RLIMIT_NICE while running + // and it won't work anyway for threads that already set their priority. + s_maxPriority = std::min(maxPriority, bestUserSetPriority); + s_maxPriorityIsSet = true; + + return s_maxPriority; +} + +std::unique_ptr<IThreadImpl> IThreadImpl::CreateThreadImpl(std::thread::native_handle_type handle) +{ + return std::make_unique<CThreadImplLinux>(handle); +} + +CThreadImplLinux::CThreadImplLinux(std::thread::native_handle_type handle) + : IThreadImpl(handle), m_threadID(gettid()) +{ +} + +void CThreadImplLinux::SetThreadInfo(const std::string& name) +{ +#if defined(__GLIBC__) + pthread_setname_np(m_handle, name.c_str()); +#endif + + // get user max prio + const int maxPrio = ThreadPriorityToNativePriority(ThreadPriority::HIGHEST); + const int userMaxPrio = GetUserMaxPriority(maxPrio); + + // if the user does not have an entry in limits.conf the following + // call will fail + if (userMaxPrio > 0) + { + // start thread with nice level of application + const int appNice = getpriority(PRIO_PROCESS, getpid()); + if (setpriority(PRIO_PROCESS, m_threadID, appNice) != 0) + CLog::Log(LOGERROR, "[threads] failed to set priority: {}", strerror(errno)); + } +} + +bool CThreadImplLinux::SetPriority(const ThreadPriority& priority) +{ + // keep priority in bounds + const int prio = ThreadPriorityToNativePriority(priority); + const int maxPrio = ThreadPriorityToNativePriority(ThreadPriority::HIGHEST); + const int minPrio = ThreadPriorityToNativePriority(ThreadPriority::LOWEST); + + // get user max prio given max prio (will take the min) + const int userMaxPrio = GetUserMaxPriority(maxPrio); + + // clamp to min and max priorities + const int adjustedPrio = std::clamp(prio, minPrio, userMaxPrio); + + // nice level of application + const int appNice = getpriority(PRIO_PROCESS, getpid()); + const int newNice = appNice - adjustedPrio; + + if (setpriority(PRIO_PROCESS, m_threadID, newNice) != 0) + { + CLog::Log(LOGERROR, "[threads] failed to set priority: {}", strerror(errno)); + return false; + } + + return true; +} diff --git a/xbmc/platform/linux/threads/ThreadImplLinux.h b/xbmc/platform/linux/threads/ThreadImplLinux.h new file mode 100644 index 0000000..986ffe5 --- /dev/null +++ b/xbmc/platform/linux/threads/ThreadImplLinux.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/IThreadImpl.h" + +class CThreadImplLinux : public IThreadImpl +{ +public: + CThreadImplLinux(std::thread::native_handle_type handle); + + ~CThreadImplLinux() override = default; + + void SetThreadInfo(const std::string& name) override; + + bool SetPriority(const ThreadPriority& priority) override; + +private: + pid_t m_threadID; +}; diff --git a/xbmc/platform/posix/CMakeLists.txt b/xbmc/platform/posix/CMakeLists.txt new file mode 100644 index 0000000..2d8d4db --- /dev/null +++ b/xbmc/platform/posix/CMakeLists.txt @@ -0,0 +1,21 @@ +set(SOURCES ConvUtils.cpp + CPUInfoPosix.cpp + Filesystem.cpp + MessagePrinter.cpp + PlatformPosix.cpp + PosixMountProvider.cpp + PosixResourceCounter.cpp + PosixTimezone.cpp + XHandle.cpp + XTimeUtils.cpp) + +set(HEADERS ConvUtils.h + CPUInfoPosix.h + PlatformDefs.h + PlatformPosix.h + PosixMountProvider.h + PosixResourceCounter.h + PosixTimezone.h + XHandle.h) + +core_add_library(platform_posix) diff --git a/xbmc/platform/posix/CPUInfoPosix.cpp b/xbmc/platform/posix/CPUInfoPosix.cpp new file mode 100644 index 0000000..9d158d0 --- /dev/null +++ b/xbmc/platform/posix/CPUInfoPosix.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CPUInfoPosix.h" + +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +bool CCPUInfoPosix::GetTemperature(CTemperature& temperature) +{ + return CheckUserTemperatureCommand(temperature); +} + +bool CCPUInfoPosix::CheckUserTemperatureCommand(CTemperature& temperature) +{ + temperature.SetValid(false); + + auto settingComponent = CServiceBroker::GetSettingsComponent(); + if (!settingComponent) + return false; + + auto advancedSettings = settingComponent->GetAdvancedSettings(); + if (!advancedSettings) + return false; + + std::string cmd = advancedSettings->m_cpuTempCmd; + + if (cmd.empty()) + return false; + + int value = {}; + char scale = {}; + + auto p = popen(cmd.c_str(), "r"); + if (p) + { + int ret = fscanf(p, "%d %c", &value, &scale); + pclose(p); + + if (ret < 2) + return false; + } + + if (scale == 'C' || scale == 'c') + temperature = CTemperature::CreateFromCelsius(value); + else if (scale == 'F' || scale == 'f') + temperature = CTemperature::CreateFromFahrenheit(value); + else + return false; + + temperature.SetValid(true); + + return true; +} diff --git a/xbmc/platform/posix/CPUInfoPosix.h b/xbmc/platform/posix/CPUInfoPosix.h new file mode 100644 index 0000000..af94b1b --- /dev/null +++ b/xbmc/platform/posix/CPUInfoPosix.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/CPUInfo.h" + +class CCPUInfoPosix : public CCPUInfo +{ +public: + virtual bool GetTemperature(CTemperature& temperature) override; + +protected: + CCPUInfoPosix() = default; + virtual ~CCPUInfoPosix() = default; + + bool CheckUserTemperatureCommand(CTemperature& temperature); +}; diff --git a/xbmc/platform/posix/ConvUtils.cpp b/xbmc/platform/posix/ConvUtils.cpp new file mode 100644 index 0000000..6ad3da2 --- /dev/null +++ b/xbmc/platform/posix/ConvUtils.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +#include "PlatformDefs.h" + +DWORD GetLastError() +{ + return errno; +} + +void SetLastError(DWORD dwErrCode) +{ + errno = dwErrCode; +} diff --git a/xbmc/platform/posix/ConvUtils.h b/xbmc/platform/posix/ConvUtils.h new file mode 100644 index 0000000..2e7c16a --- /dev/null +++ b/xbmc/platform/posix/ConvUtils.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "PlatformDefs.h" // DWORD ... + +DWORD GetLastError(); +void SetLastError(DWORD dwErrCode); + diff --git a/xbmc/platform/posix/Filesystem.cpp b/xbmc/platform/posix/Filesystem.cpp new file mode 100644 index 0000000..0db3af5 --- /dev/null +++ b/xbmc/platform/posix/Filesystem.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "platform/Filesystem.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/URIUtils.h" + +#if defined(TARGET_LINUX) +#include <sys/statvfs.h> +#elif defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) +#include <sys/param.h> +#include <sys/mount.h> +#elif defined(TARGET_ANDROID) +#include <sys/statfs.h> +#endif + +#include <cstdint> +#include <cstdlib> +#include <limits.h> +#include <string.h> + +#include <unistd.h> + +namespace KODI +{ +namespace PLATFORM +{ +namespace FILESYSTEM +{ + +space_info space(const std::string& path, std::error_code& ec) +{ + ec.clear(); + space_info sp; +#if defined(TARGET_LINUX) + struct statvfs64 fsInfo; + auto result = statvfs64(CSpecialProtocol::TranslatePath(path).c_str(), &fsInfo); +#else + struct statfs fsInfo; + // is 64-bit on android and darwin (10.6SDK + any iOS) + auto result = statfs(CSpecialProtocol::TranslatePath(path).c_str(), &fsInfo); +#endif + + if (result != 0) + { + ec.assign(result, std::system_category()); + sp.available = static_cast<uintmax_t>(-1); + sp.capacity = static_cast<uintmax_t>(-1); + sp.free = static_cast<uintmax_t>(-1); + return sp; + } + sp.available = static_cast<uintmax_t>(fsInfo.f_bavail * fsInfo.f_bsize); + sp.capacity = static_cast<uintmax_t>(fsInfo.f_blocks * fsInfo.f_bsize); + sp.free = static_cast<uintmax_t>(fsInfo.f_bfree * fsInfo.f_bsize); + + return sp; +} + +std::string temp_directory_path(std::error_code &ec) +{ + ec.clear(); + + auto result = getenv("TMPDIR"); + if (result) + return URIUtils::AppendSlash(result); + + return "/tmp/"; +} + +std::string create_temp_directory(std::error_code &ec) +{ + char buf[PATH_MAX]; + + auto path = temp_directory_path(ec); + + strncpy(buf, (path + "xbmctempXXXXXX").c_str(), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + auto tmp = mkdtemp(buf); + if (!tmp) + { + ec.assign(errno, std::system_category()); + return std::string(); + } + + ec.clear(); + return std::string(tmp); +} + +std::string temp_file_path(const std::string& suffix, std::error_code& ec) +{ + char tmp[PATH_MAX]; + + auto tempPath = create_temp_directory(ec); + if (ec) + return std::string(); + + tempPath = URIUtils::AddFileToFolder(tempPath, "xbmctempfileXXXXXX" + suffix); + if (tempPath.length() >= PATH_MAX) + { + ec.assign(EOVERFLOW, std::system_category()); + return std::string(); + } + + strncpy(tmp, tempPath.c_str(), sizeof(tmp) - 1); + tmp[sizeof(tmp) - 1] = '\0'; + + auto fd = mkstemps(tmp, suffix.length()); + if (fd < 0) + { + ec.assign(errno, std::system_category()); + return std::string(); + } + + close(fd); + + ec.clear(); + return std::string(tmp); +} + +} +} +} diff --git a/xbmc/platform/posix/MessagePrinter.cpp b/xbmc/platform/posix/MessagePrinter.cpp new file mode 100644 index 0000000..6621740 --- /dev/null +++ b/xbmc/platform/posix/MessagePrinter.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "platform/MessagePrinter.h" + +#include "CompileInfo.h" + +#include <stdio.h> + +void CMessagePrinter::DisplayMessage(const std::string& message) +{ + fprintf(stdout, "%s\n", message.c_str()); +} + +void CMessagePrinter::DisplayWarning(const std::string& warning) +{ + fprintf(stderr, "%s\n", warning.c_str()); +} + +void CMessagePrinter::DisplayError(const std::string& error) +{ + fprintf(stderr,"%s\n", error.c_str()); +} + +void CMessagePrinter::DisplayHelpMessage(const std::vector<std::pair<std::string, std::string>>& help) +{ + //very crude implementation, pretty it up when possible + std::string message; + for (const auto& line : help) + { + message.append(line.first + "\t" + line.second + "\n"); + } + + fprintf(stdout, "%s\n", message.c_str()); +} diff --git a/xbmc/platform/posix/PlatformDefs.h b/xbmc/platform/posix/PlatformDefs.h new file mode 100644 index 0000000..e6e59fe --- /dev/null +++ b/xbmc/platform/posix/PlatformDefs.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <pthread.h> +#include <string.h> +#if defined(TARGET_DARWIN) +#include <stdio.h> +#include <sched.h> +#include <AvailabilityMacros.h> +#ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS +#endif +#include <sys/sysctl.h> +#include <mach/mach.h> +#if defined(TARGET_DARWIN_OSX) +#include <libkern/OSTypes.h> +#endif + +#elif defined(TARGET_FREEBSD) +#include <stdio.h> +#include <sys/sysctl.h> +#else +#include <sys/sysinfo.h> +#endif + +#include <sys/time.h> +#include <time.h> + +#if defined(__ppc__) || defined(__powerpc__) +#define PIXEL_ASHIFT 0 +#define PIXEL_RSHIFT 8 +#define PIXEL_GSHIFT 16 +#define PIXEL_BSHIFT 24 +#else +#define PIXEL_ASHIFT 24 +#define PIXEL_RSHIFT 16 +#define PIXEL_GSHIFT 8 +#define PIXEL_BSHIFT 0 +#endif + +#include <stdint.h> + +#define _fdopen fdopen +#define _vsnprintf vsnprintf + +#define __stdcall +#define __cdecl +#define WINAPI __stdcall +#undef APIENTRY +struct CXHandle; // forward declaration +typedef CXHandle* HANDLE; + +typedef void* HINSTANCE; +typedef void* HMODULE; + +typedef unsigned int DWORD; +#define INVALID_HANDLE_VALUE ((HANDLE)~0U) + +#define MAXWORD 0xffff + +typedef union _LARGE_INTEGER +{ + struct { + DWORD LowPart; + int32_t HighPart; + } u; + long long QuadPart; +} LARGE_INTEGER, *PLARGE_INTEGER; + + typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + } u; + unsigned long long QuadPart; +} ULARGE_INTEGER; + +// Network +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (~0) +#define closesocket(s) close(s) +#define ioctlsocket(s, f, v) ioctl(s, f, v) +#define WSAGetLastError() (errno) +#define WSAECONNRESET ECONNRESET + +typedef int SOCKET; + +// Thread +typedef int (*LPTHREAD_START_ROUTINE)(void *); + +// File +#define O_BINARY 0 +#define _O_TRUNC O_TRUNC +#define _O_RDONLY O_RDONLY +#define _O_WRONLY O_WRONLY + +#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) + #define stat64 stat + #define __stat64 stat + #define fstat64 fstat + typedef int64_t off64_t; + #if defined(TARGET_FREEBSD) + #define statfs64 statfs + #endif +#else + #define __stat64 stat64 +#endif + +struct _stati64 { + dev_t st_dev; + ino_t st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + dev_t st_rdev; + long long st_size; + time_t _st_atime; + time_t _st_mtime; + time_t _st_ctime; +}; + +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 + +#define _S_IFREG S_IFREG +#define _S_IFDIR S_IFDIR +#define MAX_PATH PATH_MAX + +// CreateFile defines +#define FILE_FLAG_NO_BUFFERING 0x20000000 +#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000 + +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define TRUNCATE_EXISTING 5 + +#define FILE_READ_DATA ( 0x0001 ) +#define FILE_WRITE_DATA ( 0x0002 ) diff --git a/xbmc/platform/posix/PlatformPosix.cpp b/xbmc/platform/posix/PlatformPosix.cpp new file mode 100644 index 0000000..b1caa26 --- /dev/null +++ b/xbmc/platform/posix/PlatformPosix.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlatformPosix.h" + +#include "filesystem/SpecialProtocol.h" + +#include <cstdlib> +#include <time.h> + +#ifdef HAS_DBUS +#include <dbus/dbus.h> +#endif + +std::atomic_flag CPlatformPosix::ms_signalFlag; + +bool CPlatformPosix::InitStageOne() +{ + + if (!CPlatform::InitStageOne()) + return false; + + // Initialize to "set" state + ms_signalFlag.test_and_set(); + + // Initialize timezone information variables + tzset(); + + // set special://envhome + if (getenv("HOME")) + { + CSpecialProtocol::SetEnvHomePath(getenv("HOME")); + } + else + { + fprintf(stderr, "The HOME environment variable is not set!\n"); + return false; + } + +#ifdef HAS_DBUS + // call 'dbus_threads_init_default' before any other dbus calls in order to + // avoid race conditions with other threads using dbus connections + dbus_threads_init_default(); +#endif + + return true; +} + +bool CPlatformPosix::TestQuitFlag() +{ + // Keep set, return true when it was cleared before + return !ms_signalFlag.test_and_set(); +} + +void CPlatformPosix::RequestQuit() +{ + ms_signalFlag.clear(); +} diff --git a/xbmc/platform/posix/PlatformPosix.h b/xbmc/platform/posix/PlatformPosix.h new file mode 100644 index 0000000..08513b9 --- /dev/null +++ b/xbmc/platform/posix/PlatformPosix.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/Platform.h" + +#include <atomic> + +class CPlatformPosix : public CPlatform +{ +public: + bool InitStageOne() override; + + static bool TestQuitFlag(); + static void RequestQuit(); + +private: + static std::atomic_flag ms_signalFlag; +}; diff --git a/xbmc/platform/posix/PosixMountProvider.cpp b/xbmc/platform/posix/PosixMountProvider.cpp new file mode 100644 index 0000000..d139be1 --- /dev/null +++ b/xbmc/platform/posix/PosixMountProvider.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PosixMountProvider.h" + +#include "utils/RegExp.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <cstdlib> + +CPosixMountProvider::CPosixMountProvider() +{ + m_removableLength = 0; + PumpDriveChangeEvents(NULL); +} + +void CPosixMountProvider::Initialize() +{ + CLog::Log(LOGDEBUG, "Selected Posix mount as storage provider"); +} + +void CPosixMountProvider::GetDrives(VECSOURCES &drives) +{ + std::vector<std::string> result; + + CRegExp reMount; +#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) + reMount.RegComp("on (.+) \\(([^,]+)"); +#else + reMount.RegComp("on (.+) type ([^ ]+)"); +#endif + char line[1024]; + + FILE* pipe = popen("mount", "r"); + + if (pipe) + { + while (fgets(line, sizeof(line) - 1, pipe)) + { + if (reMount.RegFind(line) != -1) + { + bool accepted = false; + std::string mountStr = reMount.GetReplaceString("\\1"); + std::string fsStr = reMount.GetReplaceString("\\2"); + const char* mount = mountStr.c_str(); + const char* fs = fsStr.c_str(); + + // Here we choose which filesystems are approved + if (strcmp(fs, "fuseblk") == 0 || strcmp(fs, "vfat") == 0 + || strcmp(fs, "ext2") == 0 || strcmp(fs, "ext3") == 0 + || strcmp(fs, "reiserfs") == 0 || strcmp(fs, "xfs") == 0 + || strcmp(fs, "ntfs-3g") == 0 || strcmp(fs, "iso9660") == 0 + || strcmp(fs, "exfat") == 0 + || strcmp(fs, "fusefs") == 0 || strcmp(fs, "hfs") == 0) + accepted = true; + + // Ignore root + if (strcmp(mount, "/") == 0) + accepted = false; + + if(accepted) + result.emplace_back(mount); + } + } + pclose(pipe); + } + + for (unsigned int i = 0; i < result.size(); i++) + { + CMediaSource share; + share.strPath = result[i]; + share.strName = URIUtils::GetFileName(result[i]); + share.m_ignore = true; + drives.push_back(share); + } +} + +std::vector<std::string> CPosixMountProvider::GetDiskUsage() +{ + std::vector<std::string> result; + char line[1024]; + +#if defined(TARGET_DARWIN) + FILE* pipe = popen("df -hT ufs,cd9660,hfs,udf", "r"); +#elif defined(TARGET_FREEBSD) + FILE* pipe = popen("df -h -t ufs,cd9660,hfs,udf,zfs", "r"); +#else + FILE* pipe = popen("df -h", "r"); +#endif + + static const char* excludes[] = {"rootfs","devtmpfs","tmpfs","none","/dev/loop", "udev", NULL}; + + if (pipe) + { + while (fgets(line, sizeof(line) - 1, pipe)) + { + bool ok=true; + for (int i=0;excludes[i];++i) + { + if (strstr(line,excludes[i])) + { + ok=false; + break; + } + } + if (ok) + result.emplace_back(line); + } + pclose(pipe); + } + + return result; +} + +bool CPosixMountProvider::Eject(const std::string& mountpath) +{ + +#if !defined(TARGET_DARWIN_EMBEDDED) + // just go ahead and try to umount the disk + // if it does umount, life is good, if not, no loss. + std::string cmd = "umount \"" + mountpath + "\""; + int status = system(cmd.c_str()); + + if (status == 0) + return true; +#endif + + return false; +} + +bool CPosixMountProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) +{ + VECSOURCES drives; + GetRemovableDrives(drives); + bool changed = drives.size() != m_removableLength; + m_removableLength = drives.size(); + return changed; +} diff --git a/xbmc/platform/posix/PosixMountProvider.h b/xbmc/platform/posix/PosixMountProvider.h new file mode 100644 index 0000000..170c644 --- /dev/null +++ b/xbmc/platform/posix/PosixMountProvider.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "storage/IStorageProvider.h" + +#include <string> +#include <vector> + +class CPosixMountProvider : public IStorageProvider +{ +public: + CPosixMountProvider(); + ~CPosixMountProvider() override = default; + + void Initialize() override; + void Stop() override { } + + void GetLocalDrives(VECSOURCES &localDrives) override { GetDrives(localDrives); } + void GetRemovableDrives(VECSOURCES &removableDrives) override { /*GetDrives(removableDrives);*/ } + + std::vector<std::string> GetDiskUsage() override; + + bool Eject(const std::string& mountpath) override; + + bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; +private: + void GetDrives(VECSOURCES &drives); + + unsigned int m_removableLength; +}; diff --git a/xbmc/platform/posix/PosixResourceCounter.cpp b/xbmc/platform/posix/PosixResourceCounter.cpp new file mode 100644 index 0000000..d82ef72 --- /dev/null +++ b/xbmc/platform/posix/PosixResourceCounter.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PosixResourceCounter.h" + +#include "utils/log.h" + +#include <errno.h> + +#include "PlatformDefs.h" + +CPosixResourceCounter::CPosixResourceCounter() +{ + Reset(); +} + +CPosixResourceCounter::~CPosixResourceCounter() = default; + +double CPosixResourceCounter::GetCPUUsage() +{ + struct timeval tmNow; + if (gettimeofday(&tmNow, NULL) == -1) + CLog::Log(LOGERROR, "error {} in gettimeofday", errno); + else + { + double dElapsed = ( ((double)tmNow.tv_sec + (double)tmNow.tv_usec / 1000000.0) - + ((double)m_tmLastCheck.tv_sec + (double)m_tmLastCheck.tv_usec / 1000000.0) ); + + if (dElapsed >= 3.0) + { + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == -1) + CLog::Log(LOGERROR, "error {} in getrusage", errno); + else + { + double dUser = ( ((double)usage.ru_utime.tv_sec + (double)usage.ru_utime.tv_usec / 1000000.0) - + ((double)m_usage.ru_utime.tv_sec + (double)m_usage.ru_utime.tv_usec / 1000000.0) ); + double dSys = ( ((double)usage.ru_stime.tv_sec + (double)usage.ru_stime.tv_usec / 1000000.0) - + ((double)m_usage.ru_stime.tv_sec + (double)m_usage.ru_stime.tv_usec / 1000000.0) ); + + m_tmLastCheck = tmNow; + m_usage = usage; + m_dLastUsage = ((dUser+dSys) / dElapsed) * 100.0; + return m_dLastUsage; + } + } + } + + return m_dLastUsage; +} + +void CPosixResourceCounter::Reset() +{ + if (gettimeofday(&m_tmLastCheck, NULL) == -1) + CLog::Log(LOGERROR, "error {} in gettimeofday", errno); + + if (getrusage(RUSAGE_SELF, &m_usage) == -1) + CLog::Log(LOGERROR, "error {} in getrusage", errno); + + m_dLastUsage = 0.0; +} + + + + + diff --git a/xbmc/platform/posix/PosixResourceCounter.h b/xbmc/platform/posix/PosixResourceCounter.h new file mode 100644 index 0000000..ef61b15 --- /dev/null +++ b/xbmc/platform/posix/PosixResourceCounter.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <time.h> + +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/times.h> + +class CPosixResourceCounter +{ +public: + CPosixResourceCounter(); + virtual ~CPosixResourceCounter(); + + double GetCPUUsage(); + void Reset(); + +protected: + struct rusage m_usage; + struct timeval m_tmLastCheck; + double m_dLastUsage; +}; + diff --git a/xbmc/platform/posix/PosixTimezone.cpp b/xbmc/platform/posix/PosixTimezone.cpp new file mode 100644 index 0000000..6f276a3 --- /dev/null +++ b/xbmc/platform/posix/PosixTimezone.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <time.h> +#ifdef TARGET_ANDROID +#include "platform/android/bionic_supplement/bionic_supplement.h" +#endif +#include "PlatformDefs.h" +#include "PosixTimezone.h" +#include "utils/SystemInfo.h" + +#include "ServiceBroker.h" +#include "utils/StringUtils.h" +#include "XBDateTime.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include <stdlib.h> + +#include <algorithm> + +CPosixTimezone::CPosixTimezone() +{ + char* line = NULL; + size_t linelen = 0; + int nameonfourthfield = 0; + std::string s; + std::vector<std::string> tokens; + + // Load timezones + FILE* fp = fopen("/usr/share/zoneinfo/zone.tab", "r"); + if (fp) + { + std::string countryCode; + std::string timezoneName; + + while (getdelim(&line, &linelen, '\n', fp) > 0) + { + tokens.clear(); + s = line; + StringUtils::Trim(s); + + if (s.length() == 0) + continue; + + if (s[0] == '#') + continue; + + StringUtils::Tokenize(s, tokens, " \t"); + if (tokens.size() < 3) + continue; + + countryCode = tokens[0]; + timezoneName = tokens[2]; + + if (m_timezonesByCountryCode.count(countryCode) == 0) + { + std::vector<std::string> timezones; + timezones.push_back(timezoneName); + m_timezonesByCountryCode[countryCode] = timezones; + } + else + { + std::vector<std::string>& timezones = m_timezonesByCountryCode[countryCode]; + timezones.push_back(timezoneName); + } + + m_countriesByTimezoneName[timezoneName] = countryCode; + } + fclose(fp); + } + + if (line) + { + free(line); + line = NULL; + linelen = 0; + } + + // Load countries + fp = fopen("/usr/share/zoneinfo/iso3166.tab", "r"); + if (!fp) + { + fp = fopen("/usr/share/misc/iso3166", "r"); + nameonfourthfield = 1; + } + if (fp) + { + std::string countryCode; + std::string countryName; + + while (getdelim(&line, &linelen, '\n', fp) > 0) + { + s = line; + StringUtils::Trim(s); + + //! @todo STRING_CLEANUP + if (s.length() == 0) + continue; + + if (s[0] == '#') + continue; + + // Search for the first non space from the 2nd character and on + int i = 2; + while (s[i] == ' ' || s[i] == '\t') i++; + + if (nameonfourthfield) + { + // skip three letter + while (s[i] != ' ' && s[i] != '\t') i++; + while (s[i] == ' ' || s[i] == '\t') i++; + // skip number + while (s[i] != ' ' && s[i] != '\t') i++; + while (s[i] == ' ' || s[i] == '\t') i++; + } + + countryCode = s.substr(0, 2); + countryName = s.substr(i); + + m_counties.push_back(countryName); + m_countryByCode[countryCode] = countryName; + m_countryByName[countryName] = countryCode; + } + sort(m_counties.begin(), m_counties.end(), sortstringbyname()); + fclose(fp); + } + free(line); +} + +void CPosixTimezone::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_LOCALE_TIMEZONE) + { + SetTimezone(std::static_pointer_cast<const CSettingString>(setting)->GetValue()); + + CDateTime::ResetTimezoneBias(); + } + else if (settingId == CSettings::SETTING_LOCALE_TIMEZONECOUNTRY) + { + // nothing to do here. Changing locale.timezonecountry will trigger an + // update of locale.timezone and automatically adjust its value + // and execute OnSettingChanged() for it as well (see above) + } +} + +void CPosixTimezone::OnSettingsLoaded() +{ + SetTimezone(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_TIMEZONE)); + CDateTime::ResetTimezoneBias(); +} + +std::vector<std::string> CPosixTimezone::GetCounties() +{ + return m_counties; +} + +std::vector<std::string> CPosixTimezone::GetTimezonesByCountry(const std::string& country) +{ + return m_timezonesByCountryCode[m_countryByName[country]]; +} + +std::string CPosixTimezone::GetCountryByTimezone(const std::string& timezone) +{ +#if defined(TARGET_DARWIN) + return "?"; +#else + return m_countryByCode[m_countriesByTimezoneName[timezone]]; +#endif +} + +void CPosixTimezone::SetTimezone(const std::string& timezoneName) +{ +#if !defined(TARGET_DARWIN) + bool use_timezone = true; +#else + bool use_timezone = false; +#endif + + if (use_timezone) + { + static char env_var[255]; + sprintf(env_var, "TZ=:%s", timezoneName.c_str()); + putenv(env_var); + tzset(); + } +} + +std::string CPosixTimezone::GetOSConfiguredTimezone() +{ + char timezoneName[255]; + + // try Slackware approach first + ssize_t rlrc = readlink("/etc/localtime-copied-from" + , timezoneName, sizeof(timezoneName)-1); + + // RHEL and maybe other distros make /etc/localtime a symlink + if (rlrc == -1) + rlrc = readlink("/etc/localtime", timezoneName, sizeof(timezoneName)-1); + + if (rlrc != -1) + { + timezoneName[rlrc] = '\0'; + + char* p = strrchr(timezoneName,'/'); + if (p) + { // we want the previous '/' + char* q = p; + *q = 0; + p = strrchr(timezoneName,'/'); + *q = '/'; + if (p) + p++; + } + return p; + } + + // now try Debian approach + timezoneName[0] = 0; + FILE* fp = fopen("/etc/timezone", "r"); + if (fp) + { + if (fgets(timezoneName, sizeof(timezoneName), fp)) + timezoneName[strlen(timezoneName)-1] = '\0'; + fclose(fp); + } + + return timezoneName; +} + +void CPosixTimezone::SettingOptionsTimezoneCountriesFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + std::vector<std::string> countries = g_timezone.GetCounties(); + for (unsigned int i = 0; i < countries.size(); i++) + list.emplace_back(countries[i], countries[i]); +} + +void CPosixTimezone::SettingOptionsTimezonesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + current = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + bool found = false; + std::vector<std::string> timezones = g_timezone.GetTimezonesByCountry(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_TIMEZONECOUNTRY)); + for (unsigned int i = 0; i < timezones.size(); i++) + { + if (!found && StringUtils::EqualsNoCase(timezones[i], current)) + found = true; + + list.emplace_back(timezones[i], timezones[i]); + } + + if (!found && !timezones.empty()) + current = timezones[0]; +} + +CPosixTimezone g_timezone; diff --git a/xbmc/platform/posix/PosixTimezone.h b/xbmc/platform/posix/PosixTimezone.h new file mode 100644 index 0000000..076e87f --- /dev/null +++ b/xbmc/platform/posix/PosixTimezone.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" + +#include <map> +#include <string> +#include <vector> + +class CSetting; +struct StringSettingOption; + +class CPosixTimezone : public ISettingCallback, public ISettingsHandler +{ +public: + CPosixTimezone(); + + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + void OnSettingsLoaded() override; + + std::string GetOSConfiguredTimezone(); + + std::vector<std::string> GetCounties(); + std::vector<std::string> GetTimezonesByCountry(const std::string& country); + std::string GetCountryByTimezone(const std::string& timezone); + + void SetTimezone(const std::string& timezone); + int m_IsDST = 0; + + static void SettingOptionsTimezoneCountriesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + static void SettingOptionsTimezonesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + +private: + std::vector<std::string> m_counties; + std::map<std::string, std::string> m_countryByCode; + std::map<std::string, std::string> m_countryByName; + + std::map<std::string, std::vector<std::string> > m_timezonesByCountryCode; + std::map<std::string, std::string> m_countriesByTimezoneName; +}; + +extern CPosixTimezone g_timezone; + diff --git a/xbmc/platform/posix/XHandle.cpp b/xbmc/platform/posix/XHandle.cpp new file mode 100644 index 0000000..be7a78b --- /dev/null +++ b/xbmc/platform/posix/XHandle.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XHandle.h" + +#include "utils/log.h" + +#include <cassert> +#include <mutex> + +int CXHandle::m_objectTracker[10] = {}; + +HANDLE WINAPI GetCurrentProcess(void) { + return (HANDLE)-1; // -1 a special value - pseudo handle +} + +CXHandle::CXHandle() +{ + Init(); + m_objectTracker[m_type]++; +} + +CXHandle::CXHandle(HandleType nType) +{ + Init(); + m_type=nType; + m_objectTracker[m_type]++; +} + +CXHandle::CXHandle(const CXHandle &src) +{ + // we shouldn't get here EVER. however, if we do - try to make the best. copy what we can + // and most importantly - not share any pointer. + CLog::Log(LOGWARNING, "{}, copy handle.", __FUNCTION__); + + Init(); + + if (src.m_hMutex) + m_hMutex = new CCriticalSection(); + + fd = src.fd; + m_bManualEvent = src.m_bManualEvent; + m_tmCreation = time(NULL); + m_FindFileResults = src.m_FindFileResults; + m_nFindFileIterator = src.m_nFindFileIterator; + m_FindFileDir = src.m_FindFileDir; + m_iOffset = src.m_iOffset; + m_bCDROM = src.m_bCDROM; + m_objectTracker[m_type]++; +} + +CXHandle::~CXHandle() +{ + + m_objectTracker[m_type]--; + + if (RecursionCount > 0) { + CLog::Log(LOGERROR, "{}, destroying handle with recursion count {}", __FUNCTION__, + RecursionCount); + assert(false); + } + + if (m_nRefCount > 1) { + CLog::Log(LOGERROR, "{}, destroying handle with ref count {}", __FUNCTION__, m_nRefCount); + assert(false); + } + + if (m_hMutex) { + delete m_hMutex; + } + + if (m_internalLock) { + delete m_internalLock; + } + + if (m_hCond) { + delete m_hCond; + } + + if ( fd != 0 ) { + close(fd); + } + +} + +void CXHandle::Init() +{ + fd=0; + m_hMutex=NULL; + m_hCond=NULL; + m_type = HND_NULL; + RecursionCount=0; + m_bManualEvent=false; + m_bEventSet=false; + m_nFindFileIterator=0 ; + m_nRefCount=1; + m_tmCreation = time(NULL); + m_internalLock = new CCriticalSection(); +} + +void CXHandle::ChangeType(HandleType newType) { + m_objectTracker[m_type]--; + m_type = newType; + m_objectTracker[m_type]++; +} + +void CXHandle::DumpObjectTracker() { + for (int i=0; i< 10; i++) { + CLog::Log(LOGDEBUG, "object {} --> {} instances", i, m_objectTracker[i]); + } +} + +bool CloseHandle(HANDLE hObject) { + if (!hObject) + return false; + + if (hObject == INVALID_HANDLE_VALUE || hObject == (HANDLE)-1) + return true; + + bool bDelete = false; + { + std::unique_lock<CCriticalSection> lock((*hObject->m_internalLock)); + if (--hObject->m_nRefCount == 0) + bDelete = true; + } + + if (bDelete) + delete hObject; + + return true; +} + + diff --git a/xbmc/platform/posix/XHandle.h b/xbmc/platform/posix/XHandle.h new file mode 100644 index 0000000..158817e --- /dev/null +++ b/xbmc/platform/posix/XHandle.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "XHandlePublic.h" +#include "threads/Condition.h" +#include "threads/CriticalSection.h" + +#include <list> +#include <string> +#include <vector> + +#include "PlatformDefs.h" + +struct CXHandle { + +public: + typedef enum { HND_NULL = 0, HND_FILE, HND_EVENT, HND_MUTEX, HND_FIND_FILE } HandleType; + + CXHandle(); + explicit CXHandle(HandleType nType); + CXHandle(const CXHandle &src); + + virtual ~CXHandle(); + void Init(); + inline HandleType GetType() { return m_type; } + void ChangeType(HandleType newType); + + XbmcThreads::ConditionVariable *m_hCond; + std::list<CXHandle*> m_hParents; + + // simulate mutex and critical section + CCriticalSection *m_hMutex; + int RecursionCount; // for mutex - for compatibility with TARGET_WINDOWS critical section + int fd; + bool m_bManualEvent; + time_t m_tmCreation; + std::vector<std::string> m_FindFileResults; + int m_nFindFileIterator; + std::string m_FindFileDir; + off64_t m_iOffset; + bool m_bCDROM; + bool m_bEventSet; + int m_nRefCount; + CCriticalSection *m_internalLock; + + static void DumpObjectTracker(); + +protected: + HandleType m_type; + static int m_objectTracker[10]; + +}; diff --git a/xbmc/platform/posix/XHandlePublic.h b/xbmc/platform/posix/XHandlePublic.h new file mode 100644 index 0000000..6d1f840 --- /dev/null +++ b/xbmc/platform/posix/XHandlePublic.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +struct CXHandle; +typedef CXHandle* HANDLE; +typedef HANDLE* LPHANDLE; + +bool CloseHandle(HANDLE hObject); + +#define DUPLICATE_CLOSE_SOURCE 0x00000001 +#define DUPLICATE_SAME_ACCESS 0x00000002 + diff --git a/xbmc/platform/posix/XTimeUtils.cpp b/xbmc/platform/posix/XTimeUtils.cpp new file mode 100644 index 0000000..e78e5cf --- /dev/null +++ b/xbmc/platform/posix/XTimeUtils.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "utils/XTimeUtils.h" + +#include "PosixTimezone.h" + +#include <errno.h> +#include <mutex> +#include <time.h> + +#include <sys/times.h> + +#if defined(TARGET_ANDROID) && !defined(__LP64__) +#include <time64.h> +#endif + +#define WIN32_TIME_OFFSET ((unsigned long long)(369 * 365 + 89) * 24 * 3600 * 10000000) + +namespace KODI +{ +namespace TIME +{ + +/* + * A Leap year is any year that is divisible by four, but not by 100 unless also + * divisible by 400 + */ +#define IsLeapYear(y) ((!(y % 4)) ? (((!(y % 400)) && (y % 100)) ? 1 : 0) : 0) + +uint32_t GetTimeZoneInformation(TimeZoneInformation* timeZoneInformation) +{ + if (!timeZoneInformation) + return KODI_TIME_ZONE_ID_INVALID; + + struct tm t; + time_t tt = time(NULL); + if (localtime_r(&tt, &t)) + timeZoneInformation->bias = -t.tm_gmtoff / 60; + + timeZoneInformation->standardName = tzname[0]; + timeZoneInformation->daylightName = tzname[1]; + + return KODI_TIME_ZONE_ID_UNKNOWN; +} + +void GetLocalTime(SystemTime* systemTime) +{ + const time_t t = time(NULL); + struct tm now; + + localtime_r(&t, &now); + systemTime->year = now.tm_year + 1900; + systemTime->month = now.tm_mon + 1; + systemTime->dayOfWeek = now.tm_wday; + systemTime->day = now.tm_mday; + systemTime->hour = now.tm_hour; + systemTime->minute = now.tm_min; + systemTime->second = now.tm_sec; + systemTime->milliseconds = 0; + // NOTE: localtime_r() is not required to set this, but we Assume that it's set here. + g_timezone.m_IsDST = now.tm_isdst; +} + +int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime) +{ + ULARGE_INTEGER l; + l.u.LowPart = fileTime->lowDateTime; + l.u.HighPart = fileTime->highDateTime; + + time_t ft; + struct tm tm_ft; + FileTimeToTimeT(fileTime, &ft); + localtime_r(&ft, &tm_ft); + + l.QuadPart += static_cast<unsigned long long>(tm_ft.tm_gmtoff) * 10000000; + + localFileTime->lowDateTime = l.u.LowPart; + localFileTime->highDateTime = l.u.HighPart; + return 1; +} + +int SystemTimeToFileTime(const SystemTime* systemTime, FileTime* fileTime) +{ + static const int dayoffset[12] = {0, 31, 59, 90, 120, 151, 182, 212, 243, 273, 304, 334}; +#if defined(TARGET_DARWIN) + static std::mutex timegm_lock; +#endif + + struct tm sysTime = {}; + sysTime.tm_year = systemTime->year - 1900; + sysTime.tm_mon = systemTime->month - 1; + sysTime.tm_wday = systemTime->dayOfWeek; + sysTime.tm_mday = systemTime->day; + sysTime.tm_hour = systemTime->hour; + sysTime.tm_min = systemTime->minute; + sysTime.tm_sec = systemTime->second; + sysTime.tm_yday = dayoffset[sysTime.tm_mon] + (sysTime.tm_mday - 1); + sysTime.tm_isdst = g_timezone.m_IsDST; + + // If this is a leap year, and we're past the 28th of Feb, increment tm_yday. + if (IsLeapYear(systemTime->year) && (sysTime.tm_yday > 58)) + sysTime.tm_yday++; + +#if defined(TARGET_DARWIN) + std::lock_guard<std::mutex> lock(timegm_lock); +#endif + +#if defined(TARGET_ANDROID) && !defined(__LP64__) + time64_t t = timegm64(&sysTime); +#else + time_t t = timegm(&sysTime); +#endif + + LARGE_INTEGER result; + result.QuadPart = (long long)t * 10000000 + (long long)systemTime->milliseconds * 10000; + result.QuadPart += WIN32_TIME_OFFSET; + + fileTime->lowDateTime = result.u.LowPart; + fileTime->highDateTime = result.u.HighPart; + + return 1; +} + +long CompareFileTime(const FileTime* fileTime1, const FileTime* fileTime2) +{ + ULARGE_INTEGER t1; + t1.u.LowPart = fileTime1->lowDateTime; + t1.u.HighPart = fileTime1->highDateTime; + + ULARGE_INTEGER t2; + t2.u.LowPart = fileTime2->lowDateTime; + t2.u.HighPart = fileTime2->highDateTime; + + if (t1.QuadPart == t2.QuadPart) + return 0; + else if (t1.QuadPart < t2.QuadPart) + return -1; + else + return 1; +} + +int FileTimeToSystemTime(const FileTime* fileTime, SystemTime* systemTime) +{ + LARGE_INTEGER file; + file.u.LowPart = fileTime->lowDateTime; + file.u.HighPart = fileTime->highDateTime; + + file.QuadPart -= WIN32_TIME_OFFSET; + file.QuadPart /= 10000; /* to milliseconds */ + systemTime->milliseconds = file.QuadPart % 1000; + file.QuadPart /= 1000; /* to seconds */ + + time_t ft = file.QuadPart; + + struct tm tm_ft; + gmtime_r(&ft, &tm_ft); + + systemTime->year = tm_ft.tm_year + 1900; + systemTime->month = tm_ft.tm_mon + 1; + systemTime->dayOfWeek = tm_ft.tm_wday; + systemTime->day = tm_ft.tm_mday; + systemTime->hour = tm_ft.tm_hour; + systemTime->minute = tm_ft.tm_min; + systemTime->second = tm_ft.tm_sec; + + return 1; +} + +int LocalFileTimeToFileTime(const FileTime* localFileTime, FileTime* fileTime) +{ + ULARGE_INTEGER l; + l.u.LowPart = localFileTime->lowDateTime; + l.u.HighPart = localFileTime->highDateTime; + + l.QuadPart += (unsigned long long) timezone * 10000000; + + fileTime->lowDateTime = l.u.LowPart; + fileTime->highDateTime = l.u.HighPart; + + return 1; +} + + +int FileTimeToTimeT(const FileTime* localFileTime, time_t* pTimeT) +{ + if (!localFileTime || !pTimeT) + return false; + + ULARGE_INTEGER fileTime; + fileTime.u.LowPart = localFileTime->lowDateTime; + fileTime.u.HighPart = localFileTime->highDateTime; + + fileTime.QuadPart -= WIN32_TIME_OFFSET; + fileTime.QuadPart /= 10000; /* to milliseconds */ + fileTime.QuadPart /= 1000; /* to seconds */ + + time_t ft = fileTime.QuadPart; + + struct tm tm_ft; + localtime_r(&ft, &tm_ft); + + *pTimeT = mktime(&tm_ft); + return 1; +} + +int TimeTToFileTime(time_t timeT, FileTime* localFileTime) +{ + if (!localFileTime) + return false; + + ULARGE_INTEGER result; + result.QuadPart = (unsigned long long) timeT * 10000000; + result.QuadPart += WIN32_TIME_OFFSET; + + localFileTime->lowDateTime = result.u.LowPart; + localFileTime->highDateTime = result.u.HighPart; + + return 1; +} + +} // namespace TIME +} // namespace KODI diff --git a/xbmc/platform/posix/filesystem/CMakeLists.txt b/xbmc/platform/posix/filesystem/CMakeLists.txt new file mode 100644 index 0000000..badb1a8 --- /dev/null +++ b/xbmc/platform/posix/filesystem/CMakeLists.txt @@ -0,0 +1,18 @@ +set(SOURCES PosixDirectory.cpp + PosixFile.cpp) + +set(HEADERS PosixDirectory.h + PosixFile.h) + +if(SMBCLIENT_FOUND) + list(APPEND SOURCES SMBDirectory.cpp + SMBFile.cpp + SMBWSDiscovery.cpp + SMBWSDiscoveryListener.cpp) + list(APPEND HEADERS SMBDirectory.h + SMBFile.h + SMBWSDiscovery.h + SMBWSDiscoveryListener.h) +endif() + +core_add_library(platform_posix_filesystem) diff --git a/xbmc/platform/posix/filesystem/PosixDirectory.cpp b/xbmc/platform/posix/filesystem/PosixDirectory.cpp new file mode 100644 index 0000000..6685c0e --- /dev/null +++ b/xbmc/platform/posix/filesystem/PosixDirectory.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PosixDirectory.h" + +#include "FileItem.h" +#include "URL.h" +#include "utils/AliasShortcutUtils.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XTimeUtils.h" + +#include <dirent.h> +#include <sys/stat.h> + +using namespace XFILE; + +CPosixDirectory::CPosixDirectory(void) = default; + +CPosixDirectory::~CPosixDirectory(void) = default; + +bool CPosixDirectory::GetDirectory(const CURL& url, CFileItemList &items) +{ + std::string root = url.Get(); + + if (IsAliasShortcut(root, true)) + TranslateAliasShortcut(root); + + DIR *dir = opendir(root.c_str()); + if (!dir) + return false; + + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) + { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + std::string itemLabel(entry->d_name); + CCharsetConverter::unknownToUTF8(itemLabel); + CFileItemPtr pItem(new CFileItem(itemLabel)); + std::string itemPath(URIUtils::AddFileToFolder(root, entry->d_name)); + + bool bStat = false; + struct stat buffer; + + // Unix-based readdir implementations may return an incorrect dirent.d_ino value that + // is not equal to the (correct) stat() obtained one. In this case the file type + // could not be determined and the value of dirent.d_type is set to DT_UNKNOWN. + // In order to get a correct value we have to incur the cost of calling stat. + if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) + { + if (stat(itemPath.c_str(), &buffer) == 0) + bStat = true; + } + + if (entry->d_type == DT_DIR || (bStat && S_ISDIR(buffer.st_mode))) + { + pItem->m_bIsFolder = true; + URIUtils::AddSlashAtEnd(itemPath); + } + else + { + pItem->m_bIsFolder = false; + } + + if (StringUtils::StartsWith(entry->d_name, ".")) + pItem->SetProperty("file:hidden", true); + + pItem->SetPath(itemPath); + + if (!(m_flags & DIR_FLAG_NO_FILE_INFO)) + { + if (bStat || stat(pItem->GetPath().c_str(), &buffer) == 0) + { + KODI::TIME::FileTime fileTime, localTime; + KODI::TIME::TimeTToFileTime(buffer.st_mtime, &fileTime); + KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime); + pItem->m_dateTime = localTime; + + if (!pItem->m_bIsFolder) + pItem->m_dwSize = buffer.st_size; + } + } + items.Add(pItem); + } + closedir(dir); + return true; +} + +bool CPosixDirectory::Create(const CURL& url) +{ + if (!Create(url.Get())) + return Exists(url); + + return true; +} + +bool CPosixDirectory::Create(const std::string& path) +{ + if (mkdir(path.c_str(), 0755) != 0) + { + if (errno == ENOENT) + { + auto sep = path.rfind('/'); + if (sep == std::string::npos) + return false; + + if (Create(path.substr(0, sep))) + return Create(path); + } + + return false; + } + return true; +} + +bool CPosixDirectory::Remove(const CURL& url) +{ + if (rmdir(url.Get().c_str()) == 0) + return true; + + return !Exists(url); +} + +bool CPosixDirectory::RemoveRecursive(const CURL& url) +{ + std::string root = url.Get(); + + if (IsAliasShortcut(root, true)) + TranslateAliasShortcut(root); + + DIR *dir = opendir(root.c_str()); + if (!dir) + return false; + + bool success(true); + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) + { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + std::string itemLabel(entry->d_name); + CCharsetConverter::unknownToUTF8(itemLabel); + std::string itemPath(URIUtils::AddFileToFolder(root, entry->d_name)); + + bool bStat = false; + struct stat buffer; + + // Unix-based readdir implementations may return an incorrect dirent.d_ino value that + // is not equal to the (correct) stat() obtained one. In this case the file type + // could not be determined and the value of dirent.d_type is set to DT_UNKNOWN. + // In order to get a correct value we have to incur the cost of calling stat. + if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) + { + if (stat(itemPath.c_str(), &buffer) == 0) + bStat = true; + } + + if (entry->d_type == DT_DIR || (bStat && S_ISDIR(buffer.st_mode))) + { + if (!RemoveRecursive(CURL{ itemPath })) + { + success = false; + break; + } + } + else + { + if (unlink(itemPath.c_str()) != 0) + { + success = false; + break; + } + } + } + + closedir(dir); + + if (success) + { + if (rmdir(root.c_str()) != 0) + success = false; + } + + return success; +} + +bool CPosixDirectory::Exists(const CURL& url) +{ + std::string path = url.Get(); + + if (IsAliasShortcut(path, true)) + TranslateAliasShortcut(path); + + struct stat buffer; + if (stat(path.c_str(), &buffer) != 0) + return false; + return S_ISDIR(buffer.st_mode) ? true : false; +} diff --git a/xbmc/platform/posix/filesystem/PosixDirectory.h b/xbmc/platform/posix/filesystem/PosixDirectory.h new file mode 100644 index 0000000..eb323b6 --- /dev/null +++ b/xbmc/platform/posix/filesystem/PosixDirectory.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/IDirectory.h" + +namespace XFILE +{ + +class CPosixDirectory : public IDirectory +{ +public: + CPosixDirectory(void); + ~CPosixDirectory(void) override; + bool GetDirectory(const CURL& url, CFileItemList &items) override; + bool Create(const CURL& url) override; + bool Exists(const CURL& url) override; + bool Remove(const CURL& url) override; + bool RemoveRecursive(const CURL& url) override; +private: + bool Create(const std::string& path); +}; +} diff --git a/xbmc/platform/posix/filesystem/PosixFile.cpp b/xbmc/platform/posix/filesystem/PosixFile.cpp new file mode 100644 index 0000000..3010136 --- /dev/null +++ b/xbmc/platform/posix/filesystem/PosixFile.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PosixFile.h" + +#include "URL.h" +#include "filesystem/File.h" +#include "utils/AliasShortcutUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <string> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +#if defined(HAVE_STATX) // use statx if available to get file birth date +#include <sys/sysmacros.h> +#endif +#include <unistd.h> + +using namespace XFILE; + +CPosixFile::~CPosixFile() +{ + if (m_fd >= 0) + close(m_fd); +} + +// local helper +static std::string getFilename(const CURL& url) +{ + std::string filename(url.GetFileName()); + if (IsAliasShortcut(filename, false)) + TranslateAliasShortcut(filename); + + return filename; +} + + +bool CPosixFile::Open(const CURL& url) +{ + if (m_fd >= 0) + return false; + + const std::string filename(getFilename(url)); + if (filename.empty()) + return false; + + m_fd = open(filename.c_str(), O_RDONLY, S_IRUSR | S_IRGRP | S_IROTH); + m_filePos = 0; + + return m_fd != -1; +} + +bool CPosixFile::OpenForWrite(const CURL& url, bool bOverWrite /* = false*/ ) +{ + if (m_fd >= 0) + return false; + + const std::string filename(getFilename(url)); + if (filename.empty()) + return false; + + m_fd = open(filename.c_str(), O_RDWR | O_CREAT | (bOverWrite ? O_TRUNC : 0), S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (m_fd < 0) + return false; + + m_filePos = 0; + m_allowWrite = true; + + return true; +} + +void CPosixFile::Close() +{ + if (m_fd >= 0) + { + close(m_fd); + m_fd = -1; + m_filePos = -1; + m_lastDropPos = -1; + m_allowWrite = false; + } +} + + +ssize_t CPosixFile::Read(void* lpBuf, size_t uiBufSize) +{ + if (m_fd < 0) + return -1; + + assert(lpBuf != NULL || uiBufSize == 0); + if (lpBuf == NULL && uiBufSize != 0) + return -1; + + if (uiBufSize > SSIZE_MAX) + uiBufSize = SSIZE_MAX; + + const ssize_t res = read(m_fd, lpBuf, uiBufSize); + if (res < 0) + { + Seek(0, SEEK_CUR); // force update file position + return -1; + } + + if (m_filePos >= 0) + { + m_filePos += res; // if m_filePos was known - update it +#if defined(HAVE_POSIX_FADVISE) + // Drop the cache between then last drop and 16 MB behind where we + // are now, to make sure the file doesn't displace everything else. + // However, never throw out the first 16 MB of the file, as it might + // be the header etc., and never ask the OS to drop in chunks of + // less than 1 MB. + const int64_t end_drop = m_filePos - 16 * 1024 * 1024; + if (end_drop >= 17 * 1024 * 1024) + { + const int64_t start_drop = std::max<int64_t>(m_lastDropPos, 16 * 1024 * 1024); + if (end_drop - start_drop >= 1 * 1024 * 1024 && + posix_fadvise(m_fd, start_drop, end_drop - start_drop, POSIX_FADV_DONTNEED) == 0) + m_lastDropPos = end_drop; + } +#endif + } + + return res; +} + +ssize_t CPosixFile::Write(const void* lpBuf, size_t uiBufSize) +{ + if (m_fd < 0) + return -1; + + assert(lpBuf != NULL || uiBufSize == 0); + if ((lpBuf == NULL && uiBufSize != 0) || !m_allowWrite) + return -1; + + if (uiBufSize > SSIZE_MAX) + uiBufSize = SSIZE_MAX; + + const ssize_t res = write(m_fd, lpBuf, uiBufSize); + if (res < 0) + { + Seek(0, SEEK_CUR); // force update file position + return -1; + } + + if (m_filePos >= 0) + m_filePos += res; // if m_filePos was known - update it + + return res; +} + +int64_t CPosixFile::Seek(int64_t iFilePosition, int iWhence /* = SEEK_SET*/) +{ + if (m_fd < 0) + return -1; + +#ifdef TARGET_ANDROID + //! @todo properly support with detection in configure + //! Android special case: Android doesn't substitute off64_t for off_t and similar functions + m_filePos = lseek64(m_fd, (off64_t)iFilePosition, iWhence); +#else // !TARGET_ANDROID + const off_t filePosOffT = (off_t) iFilePosition; + // check for parameter overflow + if (sizeof(int64_t) != sizeof(off_t) && iFilePosition != filePosOffT) + return -1; + + m_filePos = lseek(m_fd, filePosOffT, iWhence); +#endif // !TARGET_ANDROID + + return m_filePos; +} + +int CPosixFile::Truncate(int64_t size) +{ + if (m_fd < 0) + return -1; + + const off_t sizeOffT = (off_t) size; + // check for parameter overflow + if (sizeof(int64_t) != sizeof(off_t) && size != sizeOffT) + return -1; + + return ftruncate(m_fd, sizeOffT); +} + +int64_t CPosixFile::GetPosition() +{ + if (m_fd < 0) + return -1; + + if (m_filePos < 0) + m_filePos = lseek(m_fd, 0, SEEK_CUR); + + return m_filePos; +} + +int64_t CPosixFile::GetLength() +{ + if (m_fd < 0) + return -1; + + struct stat64 st; + if (fstat64(m_fd, &st) != 0) + return -1; + + return st.st_size; +} + +void CPosixFile::Flush() +{ + if (m_fd >= 0) + fsync(m_fd); +} + +int CPosixFile::IoControl(EIoControl request, void* param) +{ + if (m_fd < 0) + return -1; + + if (request == IOCTRL_NATIVE) + { + if(!param) + return -1; + return ioctl(m_fd, ((SNativeIoControl*)param)->request, ((SNativeIoControl*)param)->param); + } + else if (request == IOCTRL_SEEK_POSSIBLE) + { + if (GetPosition() < 0) + return -1; // current position is unknown, can't test seeking + else if (m_filePos > 0) + { + const int64_t orgPos = m_filePos; + // try to seek one byte back + const bool seekPossible = (Seek(orgPos - 1, SEEK_SET) == (orgPos - 1)); + // restore file position + if (Seek(orgPos, SEEK_SET) != orgPos) + return 0; // seeking is not possible + + return seekPossible ? 1 : 0; + } + else + { // m_filePos == 0 + // try to seek one byte forward + const bool seekPossible = (Seek(1, SEEK_SET) == 1); + // restore file position + if (Seek(0, SEEK_SET) != 0) + return 0; // seeking is not possible + + if (seekPossible) + return 1; + + if (GetLength() <= 0) + return -1; // size of file is zero or can be zero, can't test seeking + else + return 0; // size of file is 1 byte or more and seeking not possible + } + } + + return -1; +} + + +bool CPosixFile::Delete(const CURL& url) +{ + const std::string filename(getFilename(url)); + if (filename.empty()) + return false; + + if (unlink(filename.c_str()) == 0) + return true; + + if (errno == EACCES || errno == EPERM) + CLog::LogF(LOGWARNING, "Can't access file \"{}\"", filename); + + return false; +} + +bool CPosixFile::Rename(const CURL& url, const CURL& urlnew) +{ + const std::string name(getFilename(url)), newName(getFilename(urlnew)); + if (name.empty() || newName.empty()) + return false; + + if (name == newName) + return true; + + if (rename(name.c_str(), newName.c_str()) == 0) + return true; + + if (errno == EACCES || errno == EPERM) + CLog::LogF(LOGWARNING, "Can't access file \"{}\" for rename to \"{}\"", name, newName); + + // rename across mount points - need to copy/delete + if (errno == EXDEV) + { + CLog::LogF(LOGDEBUG, + "Source file \"{}\" and target file \"{}\" are located on different filesystems, " + "copy&delete will be used instead of rename", + name, newName); + if (XFILE::CFile::Copy(name, newName)) + { + if (XFILE::CFile::Delete(name)) + return true; + else + XFILE::CFile::Delete(newName); + } + } + + return false; +} + +bool CPosixFile::Exists(const CURL& url) +{ + const std::string filename(getFilename(url)); + if (filename.empty()) + return false; + + struct stat64 st; + return stat64(filename.c_str(), &st) == 0 && !S_ISDIR(st.st_mode); +} + +int CPosixFile::Stat(const CURL& url, struct __stat64* buffer) +{ + assert(buffer != NULL); + const std::string filename(getFilename(url)); + if (filename.empty() || !buffer) + return -1; + +// Use statx to get file creation date (btime) which isn't available with just stat. This fills the +// buffer with the same data as the Windows implementation. Useful for the music library so that +// tags can be updated without changing the date they were added to the library (as m/ctime does) + +#if defined(HAVE_STATX) + int dirfd = AT_FDCWD; + int flags = AT_STATX_SYNC_AS_STAT; + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + struct statx stxbuf = {}; + long ret = 0; + ret = statx(dirfd, filename.c_str(), flags, mask, &stxbuf); + if (ret == 0) + { + *buffer = {}; + buffer->st_mtime = stxbuf.stx_mtime.tv_sec; // modification time + if (stxbuf.stx_btime.tv_sec != 0) + buffer->st_ctime = stxbuf.stx_btime.tv_sec; // birth (creation) time + else + buffer->st_ctime = stxbuf.stx_ctime.tv_sec; // change time (of metadata or file) + // fill everything else we might need (statx buffer is slightly different to stat buffer so + // can't just return the statx buffer) Note we might not need all this but lets fill it for + // completeness + buffer->st_atime = stxbuf.stx_atime.tv_sec; + buffer->st_size = stxbuf.stx_size; + buffer->st_blksize = stxbuf.stx_blksize; + buffer->st_blocks = stxbuf.stx_blocks; + buffer->st_ino = stxbuf.stx_ino; + buffer->st_nlink = stxbuf.stx_nlink; + buffer->st_uid = stxbuf.stx_uid; + buffer->st_gid = stxbuf.stx_gid; + buffer->st_mode = stxbuf.stx_mode; + buffer->st_rdev = makedev(stxbuf.stx_rdev_major, stxbuf.stx_rdev_minor); + buffer->st_dev = makedev(stxbuf.stx_dev_major, stxbuf.stx_dev_minor); + } + return ret; +#else + return stat64(filename.c_str(), buffer); +#endif +} + +int CPosixFile::Stat(struct __stat64* buffer) +{ + assert(buffer != NULL); + if (m_fd < 0 || !buffer) + return -1; + + return fstat64(m_fd, buffer); +} diff --git a/xbmc/platform/posix/filesystem/PosixFile.h b/xbmc/platform/posix/filesystem/PosixFile.h new file mode 100644 index 0000000..aa5e0a0 --- /dev/null +++ b/xbmc/platform/posix/filesystem/PosixFile.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/IFile.h" + +namespace XFILE +{ + + class CPosixFile : public IFile + { + public: + ~CPosixFile() override; + + bool Open(const CURL& url) override; + bool OpenForWrite(const CURL& url, bool bOverWrite = false) override; + void Close() override; + + ssize_t Read(void* lpBuf, size_t uiBufSize) override; + ssize_t Write(const void* lpBuf, size_t uiBufSize) override; + int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override; + int Truncate(int64_t size) override; + int64_t GetPosition() override; + int64_t GetLength() override; + void Flush() override; + int IoControl(EIoControl request, void* param) override; + + bool Delete(const CURL& url) override; + bool Rename(const CURL& url, const CURL& urlnew) override; + bool Exists(const CURL& url) override; + int Stat(const CURL& url, struct __stat64* buffer) override; + int Stat(struct __stat64* buffer) override; + + protected: + int m_fd = -1; + int64_t m_filePos = -1; + int64_t m_lastDropPos = -1; + bool m_allowWrite = false; + }; + +} diff --git a/xbmc/platform/posix/filesystem/SMBDirectory.cpp b/xbmc/platform/posix/filesystem/SMBDirectory.cpp new file mode 100644 index 0000000..e2305af --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBDirectory.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +/* +* know bugs: +* - when opening a server for the first time with ip address and the second time +* with server name, access to the server is denied. +* - when browsing entire network, user can't go back one step +* share = smb://, user selects a workgroup, user selects a server. +* doing ".." will go back to smb:// (entire network) and not to workgroup list. +* +* debugging is set to a max of 10 for release builds (see local.h) +*/ + +#include "SMBDirectory.h" + +#include "FileItem.h" +#include "PasswordManager.h" +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include "platform/posix/filesystem/SMBWSDiscovery.h" + +#include <mutex> + +#include <libsmbclient.h> + +struct CachedDirEntry +{ + unsigned int type; + std::string name; +}; + +using namespace XFILE; + +CSMBDirectory::CSMBDirectory(void) +{ + smb.AddActiveConnection(); +} + +CSMBDirectory::~CSMBDirectory(void) +{ + smb.AddIdleConnection(); +} + +bool CSMBDirectory::GetDirectory(const CURL& url, CFileItemList &items) +{ + // We accept smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]] + + /* samba isn't thread safe with old interface, always lock */ + std::unique_lock<CCriticalSection> lock(smb); + + smb.Init(); + + //Separate roots for the authentication and the containing items to allow browsing to work correctly + std::string strRoot = url.Get(); + std::string strAuth; + + lock.unlock(); // OpenDir is locked + + // if url provided does not having anything except smb protocol + // Do a WS-Discovery search to find possible smb servers to mimic smbv1 behaviour + if (strRoot == "smb://") + { + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return false; + + auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (!settings) + return false; + + // Check WS-Discovery daemon enabled, if not return as smb:// cant be handled further + if (settings->GetBool(CSettings::SETTING_SERVICES_WSDISCOVERY)) + { + WSDiscovery::CWSDiscoveryPosix& WSInstance = + dynamic_cast<WSDiscovery::CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery()); + return WSInstance.GetServerList(items); + } + else + { + return false; + } + } + + int fd = OpenDir(url, strAuth); + if (fd < 0) + return false; + + URIUtils::AddSlashAtEnd(strRoot); + URIUtils::AddSlashAtEnd(strAuth); + + std::string strFile; + + // need to keep the samba lock for as short as possible. + // so we first cache all directory entries and then go over them again asking for stat + // "stat" is locked each time. that way the lock is freed between stat requests + std::vector<CachedDirEntry> vecEntries; + struct smbc_dirent* dirEnt; + + lock.lock(); + if (!smb.IsSmbValid()) + return false; + while ((dirEnt = smbc_readdir(fd))) + { + CachedDirEntry aDir; + aDir.type = dirEnt->smbc_type; + aDir.name = dirEnt->name; + vecEntries.push_back(aDir); + } + smbc_closedir(fd); + lock.unlock(); + + for (size_t i=0; i<vecEntries.size(); i++) + { + CachedDirEntry aDir = vecEntries[i]; + + // We use UTF-8 internally, as does SMB + strFile = aDir.name; + + if (!strFile.empty() && strFile != "." && strFile != ".." + && strFile != "lost+found" + && aDir.type != SMBC_PRINTER_SHARE && aDir.type != SMBC_IPC_SHARE) + { + int64_t iSize = 0; + bool bIsDir = true; + int64_t lTimeDate = 0; + bool hidden = false; + + if(StringUtils::EndsWith(strFile, "$") && aDir.type == SMBC_FILE_SHARE ) + continue; + + if (StringUtils::StartsWith(strFile, ".")) + hidden = true; + + // only stat files that can give proper responses + if ( aDir.type == SMBC_FILE || + aDir.type == SMBC_DIR ) + { + // set this here to if the stat should fail + bIsDir = (aDir.type == SMBC_DIR); + + struct stat info = {}; + if ((m_flags & DIR_FLAG_NO_FILE_INFO)==0 && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambastatfiles) + { + // make sure we use the authenticated path which contains any default username + const std::string strFullName = strAuth + smb.URLEncode(strFile); + + lock.lock(); + if (!smb.IsSmbValid()) + { + items.ClearItems(); + return false; + } + + if( smbc_stat(strFullName.c_str(), &info) == 0 ) + { + + char value[20]; + // We poll for extended attributes which symbolizes bits but split up into a string. Where 0x02 is hidden and 0x12 is hidden directory. + // According to the libsmbclient.h it returns 0 on success and -1 on error. + // But before Samba 4.17.5 it seems to return the length of the returned value + // (which is 4), see https://bugzilla.samba.org/show_bug.cgi?id=14808. + // Checking for >= 0 should work both for the old erroneous and the correct behaviour. + if (smbc_getxattr(strFullName.c_str(), "system.dos_attr.mode", value, sizeof(value)) >= 0) + { + long longvalue = strtol(value, NULL, 16); + if (longvalue & SMBC_DOS_MODE_HIDDEN) + hidden = true; + } + else + CLog::Log( + LOGERROR, + "Getting extended attributes for the share: '{}'\nunix_err:'{:x}' error: '{}'", + CURL::GetRedacted(strFullName), errno, strerror(errno)); + + bIsDir = S_ISDIR(info.st_mode); + lTimeDate = info.st_mtime; + if(lTimeDate == 0) // if modification date is missing, use create date + lTimeDate = info.st_ctime; + iSize = info.st_size; + } + else + CLog::Log(LOGERROR, "{} - Failed to stat file {}", __FUNCTION__, + CURL::GetRedacted(strFullName)); + + lock.unlock(); + } + } + + KODI::TIME::FileTime fileTime, localTime; + KODI::TIME::TimeTToFileTime(lTimeDate, &fileTime); + KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime); + + if (bIsDir) + { + CFileItemPtr pItem(new CFileItem(strFile)); + std::string path(strRoot); + + // needed for network / workgroup browsing + // skip if root if we are given a server + if (aDir.type == SMBC_SERVER) + { + /* create url with same options, user, pass.. but no filename or host*/ + CURL rooturl(strRoot); + rooturl.SetFileName(""); + rooturl.SetHostName(""); + path = smb.URLEncode(rooturl); + } + path = URIUtils::AddFileToFolder(path,aDir.name); + URIUtils::AddSlashAtEnd(path); + pItem->SetPath(path); + pItem->m_bIsFolder = true; + pItem->m_dateTime=localTime; + if (hidden) + pItem->SetProperty("file:hidden", true); + items.Add(pItem); + } + else + { + CFileItemPtr pItem(new CFileItem(strFile)); + pItem->SetPath(strRoot + aDir.name); + pItem->m_bIsFolder = false; + pItem->m_dwSize = iSize; + pItem->m_dateTime=localTime; + if (hidden) + pItem->SetProperty("file:hidden", true); + items.Add(pItem); + } + } + } + + return true; +} + +int CSMBDirectory::Open(const CURL &url) +{ + smb.Init(); + std::string strAuth; + return OpenDir(url, strAuth); +} + +/// \brief Checks authentication against SAMBA share and prompts for username and password if needed +/// \param strAuth The SMB style path +/// \return SMB file descriptor +int CSMBDirectory::OpenDir(const CURL& url, std::string& strAuth) +{ + int fd = -1; + + /* make a writeable copy */ + CURL urlIn = CSMB::GetResolvedUrl(url); + + CPasswordManager::GetInstance().AuthenticateURL(urlIn); + strAuth = smb.URLEncode(urlIn); + + // remove the / or \ at the end. the samba library does not strip them off + // don't do this for smb:// !! + std::string s = strAuth; + int len = s.length(); + if (len > 1 && s.at(len - 2) != '/' && + (s.at(len - 1) == '/' || s.at(len - 1) == '\\')) + { + s.erase(len - 1, 1); + } + + CLog::LogFC(LOGDEBUG, LOGSAMBA, "Using authentication url {}", CURL::GetRedacted(s)); + + { + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return -1; + fd = smbc_opendir(s.c_str()); + } + + while (fd < 0) /* only to avoid goto in following code */ + { + std::string cError; + + if (errno == EACCES) + { + if (m_flags & DIR_FLAG_ALLOW_PROMPT) + RequireAuthentication(urlIn); + break; + } + + if (errno == ENODEV || errno == ENOENT) + cError = StringUtils::Format(g_localizeStrings.Get(770), errno); + else + cError = strerror(errno); + + if (m_flags & DIR_FLAG_ALLOW_PROMPT) + SetErrorDialog(257, cError.c_str()); + break; + } + + if (fd < 0) + { + // write error to logfile + CLog::Log( + LOGERROR, + "SMBDirectory->GetDirectory: Unable to open directory : '{}'\nunix_err:'{:x}' error : '{}'", + CURL::GetRedacted(strAuth), errno, strerror(errno)); + } + + return fd; +} + +bool CSMBDirectory::Create(const CURL& url2) +{ + std::unique_lock<CCriticalSection> lock(smb); + smb.Init(); + + CURL url = CSMB::GetResolvedUrl(url2); + CPasswordManager::GetInstance().AuthenticateURL(url); + std::string strFileName = smb.URLEncode(url); + + int result = smbc_mkdir(strFileName.c_str(), 0); + bool success = (result == 0 || EEXIST == errno); + if(!success) + CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno)); + + return success; +} + +bool CSMBDirectory::Remove(const CURL& url2) +{ + std::unique_lock<CCriticalSection> lock(smb); + smb.Init(); + + CURL url = CSMB::GetResolvedUrl(url2); + CPasswordManager::GetInstance().AuthenticateURL(url); + std::string strFileName = smb.URLEncode(url); + + int result = smbc_rmdir(strFileName.c_str()); + + if(result != 0 && errno != ENOENT) + { + CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno)); + return false; + } + + return true; +} + +bool CSMBDirectory::Exists(const CURL& url2) +{ + std::unique_lock<CCriticalSection> lock(smb); + smb.Init(); + + CURL url = CSMB::GetResolvedUrl(url2); + CPasswordManager::GetInstance().AuthenticateURL(url); + std::string strFileName = smb.URLEncode(url); + + struct stat info; + if (smbc_stat(strFileName.c_str(), &info) != 0) + return false; + + return S_ISDIR(info.st_mode); +} + diff --git a/xbmc/platform/posix/filesystem/SMBDirectory.h b/xbmc/platform/posix/filesystem/SMBDirectory.h new file mode 100644 index 0000000..cef06d2 --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBDirectory.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "MediaSource.h" +#include "SMBFile.h" +#include "filesystem/IDirectory.h" + +namespace XFILE +{ +class CSMBDirectory : public IDirectory +{ +public: + CSMBDirectory(void); + ~CSMBDirectory(void) override; + bool GetDirectory(const CURL& url, CFileItemList &items) override; + DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; } + bool Create(const CURL& url) override; + bool Exists(const CURL& url) override; + bool Remove(const CURL& url) override; + + int Open(const CURL &url); + +private: + int OpenDir(const CURL &url, std::string& strAuth); +}; +} diff --git a/xbmc/platform/posix/filesystem/SMBFile.cpp b/xbmc/platform/posix/filesystem/SMBFile.cpp new file mode 100644 index 0000000..38a82c3 --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBFile.cpp @@ -0,0 +1,721 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +// SMBFile.cpp: implementation of the CSMBFile class. +// +////////////////////////////////////////////////////////////////////// + +#include "SMBFile.h" + +#include "PasswordManager.h" +#include "SMBDirectory.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "commons/Exception.h" +#include "filesystem/SpecialProtocol.h" +#include "network/DNSNameCache.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <inttypes.h> +#include <mutex> + +#include <libsmbclient.h> + +using namespace XFILE; + +void xb_smbc_log(const char* msg) +{ + CLog::Log(LOGINFO, "{}{}", "smb: ", msg); +} + +void xb_smbc_auth(const char *srv, const char *shr, char *wg, int wglen, + char *un, int unlen, char *pw, int pwlen) +{ +} + +// WTF is this ?, we get the original server cache only +// to set the server cache to this function which call the +// original one anyway. Seems quite silly. +smbc_get_cached_srv_fn orig_cache; +SMBCSRV* xb_smbc_cache(SMBCCTX* c, const char* server, const char* share, const char* workgroup, const char* username) +{ + return orig_cache(c, server, share, workgroup, username); +} + +bool CSMB::IsFirstInit = true; + +CSMB::CSMB() +{ + m_context = NULL; + m_OpenConnections = 0; + m_IdleTimeout = 0; +} + +CSMB::~CSMB() +{ + Deinit(); +} + +void CSMB::Deinit() +{ + std::unique_lock<CCriticalSection> lock(*this); + + /* samba goes loco if deinited while it has some files opened */ + if (m_context) + { + smbc_set_context(NULL); + smbc_free_context(m_context, 1); + m_context = NULL; + } +} + +void CSMB::Init() +{ + std::unique_lock<CCriticalSection> lock(*this); + + if (!m_context) + { + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + + // force libsmbclient to use our own smb.conf by overriding HOME + std::string truehome(getenv("HOME")); + setenv("HOME", CSpecialProtocol::TranslatePath("special://home").c_str(), 1); + + // Create ~/.kodi/.smb/smb.conf. This file is used by libsmbclient. + // http://us1.samba.org/samba/docs/man/manpages-3/libsmbclient.7.html + // http://us1.samba.org/samba/docs/man/manpages-3/smb.conf.5.html + std::string smb_conf; + std::string home(getenv("HOME")); + URIUtils::RemoveSlashAtEnd(home); + smb_conf = home + "/.smb"; + int result = mkdir(smb_conf.c_str(), 0755); + if (result == 0 || (errno == EEXIST && IsFirstInit)) + { + smb_conf += "/smb.conf"; + FILE* f = fopen(smb_conf.c_str(), "w"); + if (f != NULL) + { + fprintf(f, "[global]\n"); + + fprintf(f, "\tlock directory = %s/.smb/\n", home.c_str()); + + // set minimum smbclient protocol version + if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) > 0) + { + if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) == 1) + fprintf(f, "\tclient min protocol = NT1\n"); + else + fprintf(f, "\tclient min protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL)); + } + + // set maximum smbclient protocol version + if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) > 0) + { + if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1) + fprintf(f, "\tclient max protocol = NT1\n"); + else + fprintf(f, "\tclient max protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL)); + } + + // set legacy security options + if (settings->GetBool(CSettings::SETTING_SMB_LEGACYSECURITY) && (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1)) + { + fprintf(f, "\tclient NTLMv2 auth = no\n"); + fprintf(f, "\tclient use spnego = no\n"); + } + + // set wins server if there's one. name resolve order defaults to 'lmhosts host wins bcast'. + // if no WINS server has been specified the wins method will be ignored. + if (settings->GetString(CSettings::SETTING_SMB_WINSSERVER).length() > 0 && !StringUtils::EqualsNoCase(settings->GetString(CSettings::SETTING_SMB_WINSSERVER), "0.0.0.0") ) + { + fprintf(f, "\twins server = %s\n", settings->GetString(CSettings::SETTING_SMB_WINSSERVER).c_str()); + fprintf(f, "\tname resolve order = bcast wins host\n"); + } + else + fprintf(f, "\tname resolve order = bcast host\n"); + + // use user-configured charset. if no charset is specified, + // samba tries to use charset 850 but falls back to ASCII in case it is not available + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.length() > 0) + fprintf(f, "\tdos charset = %s\n", CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.c_str()); + + // include users configuration if available + fprintf(f, "\tinclude = %s/.smb/user.conf\n", home.c_str()); + + fclose(f); + } + } + + // reads smb.conf so this MUST be after we create smb.conf + // multiple smbc_init calls are ignored by libsmbclient. + // note: this is important as it initializes the smb old + // interface compatibility. Samba 3.4.0 or higher has the new interface. + // note: we leak the following here once, not sure why yet. + // 48 bytes -> smb_xmalloc_array + // 32 bytes -> set_param_opt + // 16 bytes -> set_param_opt + smbc_init(xb_smbc_auth, 0); + + // setup our context + m_context = smbc_new_context(); + + // restore HOME + setenv("HOME", truehome.c_str(), 1); + +#ifdef DEPRECATED_SMBC_INTERFACE + smbc_setDebug(m_context, CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0); + smbc_setFunctionAuthData(m_context, xb_smbc_auth); + orig_cache = smbc_getFunctionGetCachedServer(m_context); + smbc_setFunctionGetCachedServer(m_context, xb_smbc_cache); + smbc_setOptionOneSharePerServer(m_context, false); + smbc_setOptionBrowseMaxLmbCount(m_context, 0); + smbc_setTimeout(m_context, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000); + // we do not need to strdup these, smbc_setXXX below will make their own copies + if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0) + //! @bug libsmbclient < 4.9 isn't const correct + smbc_setWorkgroup(m_context, const_cast<char*>(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str())); + std::string guest = "guest"; + //! @bug libsmbclient < 4.8 isn't const correct + smbc_setUser(m_context, const_cast<char*>(guest.c_str())); +#else + m_context->debug = (CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0); + m_context->callbacks.auth_fn = xb_smbc_auth; + orig_cache = m_context->callbacks.get_cached_srv_fn; + m_context->callbacks.get_cached_srv_fn = xb_smbc_cache; + m_context->options.one_share_per_server = false; + m_context->options.browse_max_lmb_count = 0; + m_context->timeout = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000; + // we need to strdup these, they will get free'd on smbc_free_context + if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0) + m_context->workgroup = strdup(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str()); + m_context->user = strdup("guest"); +#endif + + // initialize samba and do some hacking into the settings + if (smbc_init_context(m_context)) + { + // setup context using the smb old interface compatibility + SMBCCTX *old_context = smbc_set_context(m_context); + // free previous context or we leak it, this comes from smbc_init above. + // there is a bug in smbclient (old interface), if we init/set a context + // then set(null)/free it in DeInit above, the next smbc_set_context + // return the already freed previous context, free again and bang, crash. + // so we setup a stic bool to track the first init so we can free the + // context associated with the initial smbc_init. + if (old_context && IsFirstInit) + { + smbc_free_context(old_context, 1); + IsFirstInit = false; + } + } + else + { + smbc_free_context(m_context, 1); + m_context = NULL; + } + } + m_IdleTimeout = 180; +} + +std::string CSMB::URLEncode(const CURL &url) +{ + /* due to smb wanting encoded urls we have to build it manually */ + + std::string flat = "smb://"; + + /* samba messes up of password is set but no username is set. don't know why yet */ + /* probably the url parser that goes crazy */ + if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */) + { + if(!url.GetDomain().empty()) + { + flat += URLEncode(url.GetDomain()); + flat += ";"; + } + flat += URLEncode(url.GetUserName()); + if(url.GetPassWord().length() > 0) + { + flat += ":"; + flat += URLEncode(url.GetPassWord()); + } + flat += "@"; + } + flat += URLEncode(url.GetHostName()); + + if (url.HasPort()) + { + flat += StringUtils::Format(":{}", url.GetPort()); + } + + /* okey sadly since a slash is an invalid name we have to tokenize */ + std::vector<std::string> parts; + StringUtils::Tokenize(url.GetFileName(), parts, "/"); + for (const std::string& it : parts) + { + flat += "/"; + flat += URLEncode((it)); + } + + /* okey options should go here, thou current samba doesn't support any */ + + return flat; +} + +std::string CSMB::URLEncode(const std::string &value) +{ + return CURL::Encode(value); +} + +/* This is called from CApplication::ProcessSlow() and is used to tell if smbclient have been idle for too long */ +void CSMB::CheckIfIdle() +{ +/* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as + worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if which will lead to another check, which is locked. */ + if (m_OpenConnections == 0) + { /* I've set the the maximum IDLE time to be 1 min and 30 sec. */ + std::unique_lock<CCriticalSection> lock(*this); + if (m_OpenConnections == 0 /* check again - when locked */ && m_context != NULL) + { + if (m_IdleTimeout > 0) + { + m_IdleTimeout--; + } + else + { + CLog::Log(LOGINFO, "Samba is idle. Closing the remaining connections"); + smb.Deinit(); + } + } + } +} + +void CSMB::SetActivityTime() +{ + /* Since we get called every 500ms from ProcessSlow we limit the tick count to 180 */ + /* That means we have 2 ticks per second which equals 180/2 == 90 seconds */ + m_IdleTimeout = 180; +} + +/* The following two function is used to keep track on how many Opened files/directories there are. + This makes the idle timer not count if a movie is paused for example */ +void CSMB::AddActiveConnection() +{ + std::unique_lock<CCriticalSection> lock(*this); + m_OpenConnections++; +} +void CSMB::AddIdleConnection() +{ + std::unique_lock<CCriticalSection> lock(*this); + m_OpenConnections--; + /* If we close a file we reset the idle timer so that we don't have any weird behaviours if a user + leaves the movie paused for a long while and then press stop */ + m_IdleTimeout = 180; +} + +CURL CSMB::GetResolvedUrl(const CURL& url) +{ + CURL tmpUrl(url); + std::string resolvedHostName; + + if (CDNSNameCache::Lookup(tmpUrl.GetHostName(), resolvedHostName)) + tmpUrl.SetHostName(resolvedHostName); + + return tmpUrl; +} + +CSMB smb; + +CSMBFile::CSMBFile() +{ + smb.Init(); + m_fd = -1; + smb.AddActiveConnection(); + m_allowRetry = true; +} + +CSMBFile::~CSMBFile() +{ + Close(); + smb.AddIdleConnection(); +} + +int64_t CSMBFile::GetPosition() +{ + if (m_fd == -1) + return -1; + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return -1; + return smbc_lseek(m_fd, 0, SEEK_CUR); +} + +int64_t CSMBFile::GetLength() +{ + if (m_fd == -1) + return -1; + return m_fileSize; +} + +bool CSMBFile::Open(const CURL& url) +{ + Close(); + + // we can't open files like smb://file.f or smb://server/file.f + // if a file matches the if below return false, it can't exist on a samba share. + if (!IsValidFile(url.GetFileName())) + { + CLog::Log(LOGINFO, "SMBFile->Open: Bad URL : '{}'", url.GetRedacted()); + return false; + } + m_url = url; + + // opening a file to another computer share will create a new session + // when opening smb://server xbms will try to find folder.jpg in all shares + // listed, which will create lot's of open sessions. + + std::string strFileName; + m_fd = OpenFile(url, strFileName); + + CLog::Log(LOGDEBUG, "CSMBFile::Open - opened {}, fd={}", url.GetRedacted(), m_fd); + if (m_fd == -1) + { + // write error to logfile + CLog::Log(LOGINFO, "SMBFile->Open: Unable to open file : '{}'\nunix_err:'{:x}' error : '{}'", + CURL::GetRedacted(strFileName), errno, strerror(errno)); + return false; + } + + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return false; + struct stat tmpBuffer; + if (smbc_stat(strFileName.c_str(), &tmpBuffer) < 0) + { + smbc_close(m_fd); + m_fd = -1; + return false; + } + + m_fileSize = tmpBuffer.st_size; + + int64_t ret = smbc_lseek(m_fd, 0, SEEK_SET); + if ( ret < 0 ) + { + smbc_close(m_fd); + m_fd = -1; + return false; + } + // We've successfully opened the file! + return true; +} + + +/// \brief Checks authentication against SAMBA share. Reads password cache created in CSMBDirectory::OpenDir(). +/// \param strAuth The SMB style path +/// \return SMB file descriptor +/* +int CSMBFile::OpenFile(std::string& strAuth) +{ + int fd = -1; + + std::string strPath = g_passwordManager.GetSMBAuthFilename(strAuth); + + fd = smbc_open(strPath.c_str(), O_RDONLY, 0); + //! @todo Run a loop here that prompts for our username/password as appropriate? + //! We have the ability to run a file (eg from a button action) without browsing to + //! the directory first. In the case of a password protected share that we do + //! not have the authentication information for, the above smbc_open() will have + //! returned negative, and the file will not be opened. While this is not a particular + //! likely scenario, we might want to implement prompting for the password in this case. + //! The code from SMBDirectory can be used for this. + if(fd >= 0) + strAuth = strPath; + + return fd; +} +*/ + +int CSMBFile::OpenFile(const CURL &url, std::string& strAuth) +{ + int fd = -1; + smb.Init(); + + strAuth = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + std::string strPath = strAuth; + + { + std::unique_lock<CCriticalSection> lock(smb); + if (smb.IsSmbValid()) + fd = smbc_open(strPath.c_str(), O_RDONLY, 0); + } + + if (fd >= 0) + strAuth = strPath; + + return fd; +} + +bool CSMBFile::Exists(const CURL& url) +{ + // we can't open files like smb://file.f or smb://server/file.f + // if a file matches the if below return false, it can't exist on a samba share. + if (!IsValidFile(url.GetFileName())) return false; + + smb.Init(); + std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + + struct stat info; + + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return false; + int iResult = smbc_stat(strFileName.c_str(), &info); + + if (iResult < 0) return false; + return true; +} + +int CSMBFile::Stat(struct __stat64* buffer) +{ + if (m_fd == -1) + return -1; + + struct stat tmpBuffer = {}; + + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return -1; + int iResult = smbc_fstat(m_fd, &tmpBuffer); + CUtil::StatToStat64(buffer, &tmpBuffer); + return iResult; +} + +int CSMBFile::Stat(const CURL& url, struct __stat64* buffer) +{ + smb.Init(); + std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + std::unique_lock<CCriticalSection> lock(smb); + + if (!smb.IsSmbValid()) + return -1; + struct stat tmpBuffer = {}; + int iResult = smbc_stat(strFileName.c_str(), &tmpBuffer); + CUtil::StatToStat64(buffer, &tmpBuffer); + return iResult; +} + +int CSMBFile::Truncate(int64_t size) +{ + if (m_fd == -1) return 0; + /* + * This would force us to be dependant on SMBv3.2 which is GPLv3 + * This is only used by the TagLib writers, which are not currently in use + * So log and warn until we implement TagLib writing & can re-implement this better. + std::unique_lock<CCriticalSection> lock(smb); // Init not called since it has to be "inited" by now + +#if defined(TARGET_ANDROID) + int iResult = 0; +#else + int iResult = smbc_ftruncate(m_fd, size); +#endif +*/ + CLog::Log(LOGWARNING, "{} - Warning(smbc_ftruncate called and not implemented)", __FUNCTION__); + return 0; +} + +ssize_t CSMBFile::Read(void *lpBuf, size_t uiBufSize) +{ + if (uiBufSize > SSIZE_MAX) + uiBufSize = SSIZE_MAX; + + if (m_fd == -1) + return -1; + + // Some external libs (libass) use test read with zero size and + // null buffer pointer to check whether file is readable, but + // libsmbclient always return "-1" if called with null buffer + // regardless of buffer size. + // To overcome this, force return "0" in that case. + if (uiBufSize == 0 && lpBuf == NULL) + return 0; + + std::unique_lock<CCriticalSection> lock( + smb); // Init not called since it has to be "inited" by now + if (!smb.IsSmbValid()) + return -1; + smb.SetActivityTime(); + + ssize_t bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize); + + if (m_allowRetry && bytesRead < 0 && errno == EINVAL ) + { + CLog::Log(LOGERROR, "{} - Error( {}, {}, {} ) - Retrying", __FUNCTION__, bytesRead, errno, + strerror(errno)); + bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize); + } + + if ( bytesRead < 0 ) + CLog::Log(LOGERROR, "{} - Error( {}, {}, {} )", __FUNCTION__, bytesRead, errno, + strerror(errno)); + + return bytesRead; +} + +int64_t CSMBFile::Seek(int64_t iFilePosition, int iWhence) +{ + if (m_fd == -1) return -1; + + std::unique_lock<CCriticalSection> lock( + smb); // Init not called since it has to be "inited" by now + if (!smb.IsSmbValid()) + return -1; + smb.SetActivityTime(); + int64_t pos = smbc_lseek(m_fd, iFilePosition, iWhence); + + if ( pos < 0 ) + { + CLog::Log(LOGERROR, "{} - Error( {}, {}, {} )", __FUNCTION__, pos, errno, strerror(errno)); + return -1; + } + + return pos; +} + +void CSMBFile::Close() +{ + if (m_fd != -1) + { + CLog::Log(LOGDEBUG, "CSMBFile::Close closing fd {}", m_fd); + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return; + smbc_close(m_fd); + } + m_fd = -1; +} + +ssize_t CSMBFile::Write(const void* lpBuf, size_t uiBufSize) +{ + if (m_fd == -1) return -1; + + // lpBuf can be safely casted to void* since xbmc_write will only read from it. + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return -1; + + return smbc_write(m_fd, lpBuf, uiBufSize); +} + +bool CSMBFile::Delete(const CURL& url) +{ + smb.Init(); + std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return false; + + int result = smbc_unlink(strFile.c_str()); + + if(result != 0) + CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno)); + + return (result == 0); +} + +bool CSMBFile::Rename(const CURL& url, const CURL& urlnew) +{ + smb.Init(); + std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + std::string strFileNew = GetAuthenticatedPath(CSMB::GetResolvedUrl(urlnew)); + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return false; + + int result = smbc_rename(strFile.c_str(), strFileNew.c_str()); + + if(result != 0) + CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__, strerror(errno)); + + return (result == 0); +} + +bool CSMBFile::OpenForWrite(const CURL& url, bool bOverWrite) +{ + m_fileSize = 0; + + Close(); + + // we can't open files like smb://file.f or smb://server/file.f + // if a file matches the if below return false, it can't exist on a samba share. + if (!IsValidFile(url.GetFileName())) return false; + + std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url)); + std::unique_lock<CCriticalSection> lock(smb); + if (!smb.IsSmbValid()) + return false; + + if (bOverWrite) + { + CLog::Log(LOGWARNING, "SMBFile::OpenForWrite() called with overwriting enabled! - {}", + CURL::GetRedacted(strFileName)); + m_fd = smbc_creat(strFileName.c_str(), 0); + } + else + { + m_fd = smbc_open(strFileName.c_str(), O_RDWR, 0); + } + + if (m_fd == -1) + { + // write error to logfile + CLog::Log(LOGERROR, "SMBFile->Open: Unable to open file : '{}'\nunix_err:'{:x}' error : '{}'", + CURL::GetRedacted(strFileName), errno, strerror(errno)); + return false; + } + + // We've successfully opened the file! + return true; +} + +bool CSMBFile::IsValidFile(const std::string& strFileName) +{ + if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */ + StringUtils::EndsWith(strFileName, "/.") || /* not current folder */ + StringUtils::EndsWith(strFileName, "/..")) /* not parent folder */ + return false; + return true; +} + +std::string CSMBFile::GetAuthenticatedPath(const CURL &url) +{ + CURL authURL(CSMB::GetResolvedUrl(url)); + CPasswordManager::GetInstance().AuthenticateURL(authURL); + return smb.URLEncode(authURL); +} + +int CSMBFile::IoControl(EIoControl request, void* param) +{ + if (request == IOCTRL_SEEK_POSSIBLE) + return 1; + + if (request == IOCTRL_SET_RETRY) + { + m_allowRetry = *(bool*) param; + return 0; + } + + return -1; +} + diff --git a/xbmc/platform/posix/filesystem/SMBFile.h b/xbmc/platform/posix/filesystem/SMBFile.h new file mode 100644 index 0000000..8cc960e --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBFile.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// SMBFile.h: interface for the CSMBFile class. + +// + +////////////////////////////////////////////////////////////////////// + + +#include "URL.h" +#include "filesystem/IFile.h" +#include "threads/CriticalSection.h" + +#define NT_STATUS_CONNECTION_REFUSED long(0xC0000000 | 0x0236) +#define NT_STATUS_INVALID_HANDLE long(0xC0000000 | 0x0008) +#define NT_STATUS_ACCESS_DENIED long(0xC0000000 | 0x0022) +#define NT_STATUS_OBJECT_NAME_NOT_FOUND long(0xC0000000 | 0x0034) +#define NT_STATUS_INVALID_COMPUTER_NAME long(0xC0000000 | 0x0122) + +struct _SMBCCTX; +typedef _SMBCCTX SMBCCTX; + +class CSMB : public CCriticalSection +{ +public: + CSMB(); + ~CSMB(); + void Init(); + void Deinit(); + /* Makes sense to be called after acquiring the lock */ + bool IsSmbValid() const { return m_context != nullptr; } + void CheckIfIdle(); + void SetActivityTime(); + void AddActiveConnection(); + void AddIdleConnection(); + std::string URLEncode(const std::string &value); + std::string URLEncode(const CURL &url); + + DWORD ConvertUnixToNT(int error); + static CURL GetResolvedUrl(const CURL& url); + +private: + SMBCCTX *m_context; + int m_OpenConnections; + unsigned int m_IdleTimeout; + static bool IsFirstInit; +}; + +extern CSMB smb; + +namespace XFILE +{ +class CSMBFile : public IFile +{ +public: + CSMBFile(); + int OpenFile(const CURL &url, std::string& strAuth); + ~CSMBFile() override; + void Close() override; + int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override; + ssize_t Read(void* lpBuf, size_t uiBufSize) override; + bool Open(const CURL& url) override; + bool Exists(const CURL& url) override; + int Stat(const CURL& url, struct __stat64* buffer) override; + int Stat(struct __stat64* buffer) override; + int Truncate(int64_t size) override; + int64_t GetLength() override; + int64_t GetPosition() override; + ssize_t Write(const void* lpBuf, size_t uiBufSize) override; + + bool OpenForWrite(const CURL& url, bool bOverWrite = false) override; + bool Delete(const CURL& url) override; + bool Rename(const CURL& url, const CURL& urlnew) override; + int GetChunkSize() override { return 64*1024; } + int IoControl(EIoControl request, void* param) override; + +protected: + CURL m_url; + bool IsValidFile(const std::string& strFileName); + std::string GetAuthenticatedPath(const CURL &url); + int64_t m_fileSize; + int m_fd; + bool m_allowRetry; +}; +} diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp new file mode 100644 index 0000000..8bab0aa --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021- Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SMBWSDiscovery.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "network/IWSDiscovery.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include "platform/posix/filesystem/SMBWSDiscoveryListener.h" + +#include <algorithm> +#include <chrono> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> + +using namespace std::chrono; +using namespace WSDiscovery; + +namespace WSDiscovery +{ +std::unique_ptr<IWSDiscovery> IWSDiscovery::GetInstance() +{ + return std::make_unique<WSDiscovery::CWSDiscoveryPosix>(); +} + +std::atomic<bool> CWSDiscoveryPosix::m_isInitialized{false}; + +CWSDiscoveryPosix::CWSDiscoveryPosix() +{ + // Set our wsd_instance ID to seconds since epoch + auto epochduration = system_clock::now().time_since_epoch(); + wsd_instance_id = epochduration.count() * system_clock::period::num / system_clock::period::den; + + m_WSDListenerUDP = std::make_unique<CWSDiscoveryListenerUDP>(); + m_isInitialized = true; +} + +CWSDiscoveryPosix::~CWSDiscoveryPosix() +{ + StopServices(); +} + +bool CWSDiscoveryPosix::StopServices() +{ + m_WSDListenerUDP->Stop(); + + return true; +} + +bool CWSDiscoveryPosix::StartServices() +{ + m_WSDListenerUDP->Start(); + + return true; +} + +bool CWSDiscoveryPosix::IsRunning() +{ + return m_WSDListenerUDP->IsRunning(); +} + +bool CWSDiscoveryPosix::GetServerList(CFileItemList& items) +{ + { + std::unique_lock<CCriticalSection> lock(m_critWSD); + + for (const auto& item : m_vecWSDInfo) + { + auto found = item.computer.find('/'); + std::string host; + if (found != std::string::npos) + host = item.computer.substr(0, found); + else + { + if (item.xaddrs_host.empty()) + continue; + host = item.xaddrs_host; + } + + CFileItemPtr pItem = std::make_shared<CFileItem>(host); + pItem->SetPath("smb://" + host + '/'); + pItem->m_bIsFolder = true; + items.Add(pItem); + } + } + return true; +} + +bool CWSDiscoveryPosix::GetCached(const std::string& strHostName, std::string& strIpAddress) +{ + const std::string match = strHostName + "/"; + + std::unique_lock<CCriticalSection> lock(m_critWSD); + for (const auto& item : m_vecWSDInfo) + { + if (!item.computer.empty() && StringUtils::StartsWithNoCase(item.computer, match)) + { + strIpAddress = item.xaddrs_host; + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryPosix::Lookup - {} -> {}", strHostName, + strIpAddress); + return true; + } + } + + return false; +} + +void CWSDiscoveryPosix::SetItems(std::vector<wsd_req_info> entries) +{ + { + std::unique_lock<CCriticalSection> lock(m_critWSD); + m_vecWSDInfo = std::move(entries); + } +} +} // namespace WSDiscovery diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscovery.h b/xbmc/platform/posix/filesystem/SMBWSDiscovery.h new file mode 100644 index 0000000..139ff11 --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBWSDiscovery.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021- Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "network/IWSDiscovery.h" +#include "threads/CriticalSection.h" +#include "threads/SingleLock.h" + +#include "platform/posix/filesystem/SMBWSDiscoveryListener.h" + +#include <memory> +#include <string> +#include <vector> + +class CFileItemList; + +namespace WSDiscovery +{ +class CWSDiscoveryListenerUDP; +} + +namespace WSDiscovery +{ +struct wsd_req_info +{ + std::string action; // ToDo: Action probably isnt required to be stored + std::string msgid; + std::string types; // ToDo: Types may not be needed. + std::string address; + std::string xaddrs; + std::string xaddrs_host; + std::string computer; + + bool operator==(const wsd_req_info& item) const + { + return ((item.xaddrs == xaddrs) && (item.address == address)); + } +}; + +class CWSDiscoveryPosix : public WSDiscovery::IWSDiscovery +{ +public: + CWSDiscoveryPosix(); + + // IWSDiscovery interface methods + ~CWSDiscoveryPosix() override; + bool StartServices() override; + bool StopServices() override; + bool IsRunning() override; + + /* + * Get List of smb servers found by WSD + * out (CFileItemList&) List of fileitems populated with smb addresses + * return (bool) true if >0 WSD addresses found + */ + bool GetServerList(CFileItemList& items); + + long long GetInstanceID() const { return wsd_instance_id; } + + /* + * Set List of WSD info request + * out (vector<wsd_req_info>) vector of WSD responses received + * return void + */ + void SetItems(std::vector<WSDiscovery::wsd_req_info> entries); + + /* + * Lookup host name in collected ws-discovery data + * in (const std::string&) Host name + * out (std::string&) IP address if found + * return (bool) true if found + */ + bool GetCached(const std::string& strHostName, std::string& strIpAddress); + + static bool IsInitialized() { return m_isInitialized; } + +private: + CCriticalSection m_critWSD; + + /* + * As per docs - Pg32 - http://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf + * MUST be incremented by >= 1 each time the service has gone down, lost state, + * and came back up again. SHOULD NOT be incremented otherwise. Means to set + * this value include, but are not limited to: + * • A counter that is incremented on each 'cold' boot + * • The boot time of the service, expressed as seconds elapsed since midnight + * January 1, 1970 + * + * Our implementation will only set this on creation of the class + * We arent providing services to clients, so this should be ok. + */ + long long wsd_instance_id; + + // WSD UDP daemon + std::unique_ptr<WSDiscovery::CWSDiscoveryListenerUDP> m_WSDListenerUDP; + + std::vector<WSDiscovery::wsd_req_info> m_vecWSDInfo; + + static std::atomic<bool> m_isInitialized; +}; +} // namespace WSDiscovery diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp new file mode 100644 index 0000000..d42c19b --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2021- Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SMBWSDiscoveryListener.h" + +#include "ServiceBroker.h" +#include "filesystem/CurlFile.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include "platform/posix/filesystem/SMBWSDiscovery.h" + +#include <array> +#include <chrono> +#include <mutex> +#include <stdio.h> +#include <string> +#include <utility> + +#include <arpa/inet.h> +#include <fmt/format.h> +#include <sys/select.h> +#include <unistd.h> + +using namespace WSDiscovery; + +namespace WSDiscovery +{ + +// Templates for SOAPXML messages for WS-Discovery messages +static const std::string soap_msg_templ = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<soap:Envelope " + "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" " + "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " + "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " + "xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\" " + "xmlns:wsdp=\"http://schemas.xmlsoap.org/ws/2006/02/devprof\" " + "xmlns:un0=\"http://schemas.microsoft.com/windows/pnpx/2005/10\" " + "xmlns:pub=\"http://schemas.microsoft.com/windows/pub/2005/07\">\n" + "<soap:Header>\n" + "<wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>\n" + "<wsa:Action>{}</wsa:Action>\n" + "<wsa:MessageID>urn:uuid:{}</wsa:MessageID>\n" + "<wsd:AppSequence InstanceId=\"{}\" MessageNumber=\"{}\" />\n" + "{}" + "</soap:Header>\n" + "{}" + "</soap:Envelope>\n"; + +static const std::string hello_body = "<soap:Body>\n" + "<wsd:Hello>\n" + "<wsa:EndpointReference>\n" + "<wsa:Address>urn:uuid:{}</wsa:Address>\n" + "</wsa:EndpointReference>\n" + "<wsd:Types>wsdp:Device pub:Computer</wsd:Types>\n" + "<wsd:MetadataVersion>1</wsd:MetadataVersion>\n" + "</wsd:Hello>\n" + "</soap:Body>\n"; + +static const std::string bye_body = "<soap:Body>\n" + "<wsd:Bye>\n" + "<wsa:EndpointReference>\n" + "<wsa:Address>urn:uuid:{}</wsa:Address>\n" + "</wsa:EndpointReference>\n" + "<wsd:Types>wsdp:Device pub:Computer</wsd:Types>\n" + "<wsd:MetadataVersion>2</wsd:MetadataVersion>\n" + "</wsd:Bye>\n" + "</soap:Body>\n"; + +static const std::string probe_body = "<soap:Body>\n" + "<wsd:Probe>\n" + "<wsd:Types>wsdp:Device</wsd:Types>\n" + "</wsd:Probe>\n" + "</soap:Body>\n"; + +static const std::string resolve_body = "<soap:Body>\n" + "<wsd:Resolve>\n" + "<wsa:EndpointReference>\n" + "<wsa:Address>" + "{}" + "</wsa:Address>\n" + "</wsa:EndpointReference>\n" + "</wsd:Resolve>\n" + "</soap:Body>\n"; + +// These are the only actions we concern ourselves with for our WS-D implementation +static const std::string WSD_ACT_HELLO = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Hello"; +static const std::string WSD_ACT_BYE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Bye"; +static const std::string WSD_ACT_PROBEMATCH = + "http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches"; +static const std::string WSD_ACT_PROBE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"; +static const std::string WSD_ACT_RESOLVE = + "http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve"; +static const std::string WSD_ACT_RESOLVEMATCHES = + "http://schemas.xmlsoap.org/ws/2005/04/discovery/ResolveMatches"; + + +// These are xml start/finish tags we need info from +static const std::array<std::pair<std::string, std::string>, 2> action_tag{ + {{"<wsa:Action>", "</wsa:Action>"}, + {"<wsa:Action SOAP-ENV:mustUnderstand=\"true\">", "</wsa:Action>"}}}; + +static const std::array<std::pair<std::string, std::string>, 2> msgid_tag{ + {{"<wsa:MessageID>", "</wsa:MessageID>"}, + {"<wsa:MessageID SOAP-ENV:mustUnderstand=\"true\">", "</wsa:MessageID>"}}}; + +static const std::array<std::pair<std::string, std::string>, 1> xaddrs_tag{ + {{"<wsd:XAddrs>", "</wsd:XAddrs>"}}}; + +static const std::array<std::pair<std::string, std::string>, 1> address_tag{ + {{"<wsa:Address>", "</wsa:Address>"}}}; + +static const std::array<std::pair<std::string, std::string>, 1> types_tag{ + {{"<wsd:Types>", "</wsd:Types>"}}}; + +static const std::string get_msg = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<soap:Envelope " + "xmlns:pnpx=\"http://schemas.microsoft.com/windows/pnpx/2005/10\" " + "xmlns:pub=\"http://schemas.microsoft.com/windows/pub/2005/07\" " + "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" " + "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " + "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " + "xmlns:wsdp=\"http://schemas.xmlsoap.org/ws/2006/02/devprof\" " + "xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\"> " + "<soap:Header> " + "<wsa:To>{}</wsa:To> " + "<wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</wsa:Action> " + "<wsa:MessageID>urn:uuid:{}</wsa:MessageID> " + "<wsa:ReplyTo> " + "<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address> " + "</wsa:ReplyTo> " + "<wsa:From> " + "<wsa:Address>urn:uuid:{}</wsa:Address> " + "</wsa:From> " + "</soap:Header> " + "<soap:Body /> " + "</soap:Envelope>"; + +static const std::array<std::pair<std::string, std::string>, 1> computer_tag{ + {{"<pub:Computer>", "</pub:Computer>"}}}; + +CWSDiscoveryListenerUDP::CWSDiscoveryListenerUDP() + : CThread("WSDiscoveryListenerUDP"), wsd_instance_address(StringUtils::CreateUUID()) +{ +} + +CWSDiscoveryListenerUDP::~CWSDiscoveryListenerUDP() +{ +} + +void CWSDiscoveryListenerUDP::Stop() +{ + StopThread(true); + CLog::Log(LOGINFO, "CWSDiscoveryListenerUDP::Stop - Stopped"); +} + +void CWSDiscoveryListenerUDP::Start() +{ + if (IsRunning()) + return; + + // Clear existing data. We dont know how long service has been down, so we may not + // have correct services that may have sent BYE actions + m_vecWSDInfo.clear(); + CWSDiscoveryPosix& WSInstance = + dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery()); + WSInstance.SetItems(m_vecWSDInfo); + + // Create thread and set low priority + Create(); + SetPriority(ThreadPriority::LOWEST); + CLog::Log(LOGINFO, "CWSDiscoveryListenerUDP::Start - Started"); +} + +void CWSDiscoveryListenerUDP::Process() +{ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::Process - Socket Creation failed"); + return; + } + + // set socket reuse + int enable = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable)) < 0) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::Process - Reuse Option failed"); + Cleanup(true); + return; + } + + // set up destination address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(wsdUDP); + + // bind to receive address + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::Process - Bind failed"); + Cleanup(true); + return; + } + + // use setsockopt() to request join a multicast group on all interfaces + // maybe use firstconnected? + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(WDSIPv4MultiGroup); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)) < 0) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::Process - Multicast Option failed"); + Cleanup(true); + return; + } + + // Disable receiving broadcast messages on loopback + // So we dont receive messages we send. + int disable = 0; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&disable, sizeof(disable)) < 0) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::Process - Loopback Disable Option failed"); + Cleanup(true); + return; + } + + std::string bufferoutput; + fd_set rset; + int nready; + struct timeval timeout; + + FD_ZERO(&rset); + + // Send HELLO to the world + AddCommand(WSD_ACT_HELLO); + DispatchCommand(); + + AddCommand(WSD_ACT_PROBE); + DispatchCommand(); + + while (!m_bStop) + { + FD_SET(fd, &rset); + timeout.tv_sec = 3; + timeout.tv_usec = 0; + nready = select((fd + 1), &rset, NULL, NULL, &timeout); + + if (nready < 0) + break; + + if (m_bStop) + break; + + // if udp socket is readable receive the message. + if (FD_ISSET(fd, &rset)) + { + bufferoutput = ""; + char msgbuf[UDPBUFFSIZE]; + socklen_t addrlen = sizeof(addr); + int nbytes = recvfrom(fd, msgbuf, UDPBUFFSIZE, 0, (struct sockaddr*)&addr, &addrlen); + msgbuf[nbytes] = '\0'; + // turn msgbuf into std::string + bufferoutput.append(msgbuf, nbytes); + + ParseBuffer(bufferoutput); + } + // Action any commands queued + while (DispatchCommand()) + { + } + } + + // Be a nice citizen and send BYE to the world + AddCommand(WSD_ACT_BYE); + DispatchCommand(); + Cleanup(false); + return; +} + +void CWSDiscoveryListenerUDP::Cleanup(bool aborted) +{ + close(fd); + + if (aborted) + { + // Thread is stopping due to a failure state + // Update service setting to be disabled + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return; + + auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (!settings) + return; + + if (settings->GetBool(CSettings::SETTING_SERVICES_WSDISCOVERY)) + { + settings->SetBool(CSettings::SETTING_SERVICES_WSDISCOVERY, false); + settings->Save(); + } + } +} + +bool CWSDiscoveryListenerUDP::DispatchCommand() +{ + Command sendCommand; + { + std::unique_lock<CCriticalSection> lock(crit_commandqueue); + if (m_commandbuffer.size() <= 0) + return false; + + auto it = m_commandbuffer.begin(); + sendCommand = *it; + m_commandbuffer.erase(it); + } + + int ret; + + // As its udp, send multiple messages due to unreliability + // Windows seems to send 4-6 times for reference + for (int i = 0; i < retries; i++) + { + do + { + ret = sendto(fd, sendCommand.commandMsg.c_str(), sendCommand.commandMsg.size(), 0, + (struct sockaddr*)&sendCommand.address, sizeof(sendCommand.address)); + } while (ret == -1 && !m_bStop); + std::chrono::seconds sec(1); + CThread::Sleep(sec); + } + + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::DispatchCommand - Command sent"); + + return true; +} + +void CWSDiscoveryListenerUDP::AddCommand(const std::string& message, + const std::string& extraparameter /* = "" */) +{ + + std::unique_lock<CCriticalSection> lock(crit_commandqueue); + + std::string msg{}; + + if (!buildSoapMessage(message, msg, extraparameter)) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::AddCommand - Invalid Soap Message"); + return; + } + + // ToDo: IPv6 + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(wsdUDP); + + addr.sin_addr.s_addr = inet_addr(WDSIPv4MultiGroup); + memset(&addr.sin_zero, 0, sizeof(addr.sin_zero)); + + Command newCommand{{addr}, {msg}}; + + m_commandbuffer.push_back(newCommand); +} + +void CWSDiscoveryListenerUDP::ParseBuffer(const std::string& buffer) +{ + wsd_req_info info; + + // MUST have an action tag + std::string action = wsd_tag_find(buffer, action_tag); + if (action.empty()) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - No Action tag found"); + return; + } + + info.action = action; + + // Only handle actions we need when received + if (!((action == WSD_ACT_HELLO) || (action == WSD_ACT_BYE) || + (action == WSD_ACT_RESOLVEMATCHES) || (action == WSD_ACT_PROBEMATCH))) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - Action not supported"); + PrintWSDInfo(info); + return; + } + + // MUST have a msgid tag + std::string msgid = wsd_tag_find(buffer, msgid_tag); + if (msgid.empty()) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - No msgid tag found"); + return; + } + + info.msgid = msgid; + + std::string types = wsd_tag_find(buffer, types_tag); + info.types = types; + std::string address = wsd_tag_find(buffer, address_tag); + info.address = address; + std::string xaddrs = wsd_tag_find(buffer, xaddrs_tag); + info.xaddrs = xaddrs; + + if (xaddrs.empty() && (action != WSD_ACT_BYE)) + { + // Do a resolve against address to hopefully receive an xaddrs field= + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - Resolve Action dispatched"); + AddCommand(WSD_ACT_RESOLVE, address); + // Discard this message + PrintWSDInfo(info); + return; + } + + const std::string delim1 = "://"; + const std::string delim2 = ":"; + size_t found = info.xaddrs.find(delim1); + if (found != std::string::npos) + { + std::string tmpxaddrs = info.xaddrs.substr(found + delim1.size()); + if (tmpxaddrs[0] != '[') + { + found = std::min(tmpxaddrs.find(delim2), tmpxaddrs.find('/')); + info.xaddrs_host = tmpxaddrs.substr(0, found); + } + } + + { + std::unique_lock<CCriticalSection> lock(crit_wsdqueue); + auto searchitem = std::find_if(m_vecWSDInfo.begin(), m_vecWSDInfo.end(), + [info](const wsd_req_info& item) { return item == info; }); + + if (searchitem == m_vecWSDInfo.end()) + { + CWSDiscoveryPosix& WSInstance = + dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery()); + if (info.action != WSD_ACT_BYE) + { + // Acceptable request received, add to our server list + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - Actionable message"); + UnicastGet(info); + m_vecWSDInfo.emplace_back(info); + WSInstance.SetItems(m_vecWSDInfo); + PrintWSDInfo(info); + return; + } + else + { + // WSD_ACT_BYE does not include an xaddrs tag + // We only want to match the address when receiving a WSD_ACT_BYE message to + // remove from our maintained list + auto searchbye = std::find_if( + m_vecWSDInfo.begin(), m_vecWSDInfo.end(), + [info, this](const wsd_req_info& item) { return equalsAddress(item, info); }); + if (searchbye != m_vecWSDInfo.end()) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::ParseBuffer - BYE action -" + " removing entry"); + m_vecWSDInfo.erase(searchbye); + PrintWSDInfo(info); + WSInstance.SetItems(m_vecWSDInfo); + return; + } + } + } + } + + // Only duplicate items get this far, silently drop + return; +} + +bool CWSDiscoveryListenerUDP::buildSoapMessage(const std::string& action, + std::string& msg, + const std::string& extraparameter) +{ + auto msg_uuid = StringUtils::CreateUUID(); + std::string body; + std::string relatesTo; // Not implemented, may not be needed for our limited usage + int messagenumber = 1; + + CWSDiscoveryPosix& WSInstance = + dynamic_cast<CWSDiscoveryPosix&>(CServiceBroker::GetWSDiscovery()); + long long wsd_instance_id = WSInstance.GetInstanceID(); + + if (action == WSD_ACT_HELLO) + { + body = fmt::format(hello_body, wsd_instance_address); + } + else if (action == WSD_ACT_BYE) + { + body = fmt::format(bye_body, wsd_instance_address); + } + else if (action == WSD_ACT_PROBE) + { + body = probe_body; + } + else if (action == WSD_ACT_RESOLVE) + { + body = fmt::format(resolve_body, extraparameter); + } + if (body.empty()) + { + // May lead to excessive logspam + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::buildSoapMessage unimplemented WSD_ACTION"); + return false; + } + + // Todo: how are fmt exception/failures handled with libfmt? + msg = fmt::format(soap_msg_templ, action, msg_uuid, wsd_instance_id, messagenumber, relatesTo, + body); + + return true; +} + +template<std::size_t SIZE> +const std::string CWSDiscoveryListenerUDP::wsd_tag_find( + const std::string& xml, const std::array<std::pair<std::string, std::string>, SIZE>& tag) +{ + for (const auto& tagpair : tag) + { + std::size_t found1 = xml.find(tagpair.first); + if (found1 != std::string::npos) + { + std::size_t found2 = xml.find(tagpair.second); + if (found2 != std::string::npos) + { + return xml.substr((found1 + tagpair.first.size()), + (found2 - (found1 + tagpair.first.size()))); + } + } + } + return ""; +} + +bool CWSDiscoveryListenerUDP::equalsAddress(const wsd_req_info& lhs, const wsd_req_info& rhs) const +{ + return lhs.address == rhs.address; +} + +void CWSDiscoveryListenerUDP::PrintWSDInfo(const wsd_req_info& info) +{ + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryUtils::printstruct - message contents\n" + "\tAction: {}\n" + "\tMsgID: {}\n" + "\tAddress: {}\n" + "\tTypes: {}\n" + "\tXAddrs: {}\n" + "\tComputer: {}\n", + info.action, info.msgid, info.address, info.types, info.xaddrs, info.computer); +} + +void CWSDiscoveryListenerUDP::UnicastGet(wsd_req_info& info) +{ + if (INADDR_NONE == inet_addr(info.xaddrs_host.c_str())) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, "CWSDiscoveryListenerUDP::UnicastGet - No IP address in {}", + info.xaddrs); + return; + } + + std::string msg = + fmt::format(get_msg, info.address, StringUtils::CreateUUID(), wsd_instance_address); + XFILE::CCurlFile file; + file.SetAcceptEncoding("identity"); + file.SetMimeType("application/soap+xml"); + file.SetUserAgent("wsd"); + file.SetRequestHeader("Connection", "Close"); + + std::string html; + if (!file.Post(info.xaddrs, msg, html)) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::UnicastGet - Could not fetch from {}", info.xaddrs); + return; + } + info.computer = wsd_tag_find(html, computer_tag); + if (info.computer.empty()) + { + CLog::Log(LOGDEBUG, LOGWSDISCOVERY, + "CWSDiscoveryListenerUDP::UnicastGet - No computer tag found"); + return; + } +} + +} // namespace WSDiscovery diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h new file mode 100644 index 0000000..3459973 --- /dev/null +++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021- Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/CriticalSection.h" +#include "threads/Thread.h" + +#include <string> +#include <vector> + +#include <netinet/in.h> + +namespace WSDiscovery +{ +struct wsd_req_info; +} + +namespace WSDiscovery +{ +class CWSDiscoveryListenerUDP : public CThread +{ +public: + CWSDiscoveryListenerUDP(); + ~CWSDiscoveryListenerUDP(); + + void Start(); + void Stop(); + +protected: + // CThread + void Process() override; + +private: + struct Command + { + struct sockaddr_in address; + std::string commandMsg; + }; + + /* + * Dispatch UDP command from command Queue + * return (bool) true if message dispatched + */ + bool DispatchCommand(); + + /* + * Build SOAPXML command and add to command Queue + * in (string) action type + * in (string) extra data field (currently used solely for resolve addresses) + * return (void) + */ + void AddCommand(const std::string& message, const std::string& extraparameter = ""); + + /* + * Process received broadcast messages and add accepted items to + * in (string) SOAPXML Message + * return (void) + */ + void ParseBuffer(const std::string& buffer); + + /* + * Generates an XML SOAP message given a particular action type + * in (string) action type + * in/out (string) created message + * in (string) extra data field (currently used for resolve addresses) + * return (bool) true if full message crafted + */ + bool buildSoapMessage(const std::string& action, + std::string& msg, + const std::string& extraparameter); + + // Closes socket and handles setting state for WS-Discovery + void Cleanup(bool aborted); + + /* + * Use unicast Get to request computer name + * in/out (wsd_req_info&) host info to be updated + * return (void) + */ + void UnicastGet(wsd_req_info& info); + +private: + template<std::size_t SIZE> + const std::string wsd_tag_find(const std::string& xml, + const std::array<std::pair<std::string, std::string>, SIZE>& tag); + + // Debug print WSD packet data + void PrintWSDInfo(const WSDiscovery::wsd_req_info& info); + + // compare WSD entry address + bool equalsAddress(const WSDiscovery::wsd_req_info& lhs, + const WSDiscovery::wsd_req_info& rhs) const; + + // Socket FD for send/recv + int fd; + + std::vector<Command> m_commandbuffer; + + CCriticalSection crit_commandqueue; + CCriticalSection crit_wsdqueue; + + std::vector<WSDiscovery::wsd_req_info> m_vecWSDInfo; + + // GUID that should remain constant for an instance + const std::string wsd_instance_address; + + // Number of sends for UDP messages + const int retries = 4; + + // Max udp packet size (+ UDP header + IP header overhead = 65535) + const int UDPBUFFSIZE = 65507; + + // Port for unicast/multicast WSD traffic + const int wsdUDP = 3702; + + // ipv4 multicast group WSD - https://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf + const char* WDSIPv4MultiGroup = "239.255.255.250"; + + // ToDo: ipv6 broadcast address + // const char* WDSIPv6MultiGroup = "FF02::C" +}; +} // namespace WSDiscovery diff --git a/xbmc/platform/posix/main.cpp b/xbmc/platform/posix/main.cpp new file mode 100644 index 0000000..5028aaa --- /dev/null +++ b/xbmc/platform/posix/main.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#if defined(TARGET_DARWIN_OSX) +// SDL redefines main as SDL_main +#ifdef HAS_SDL +#include <SDL/SDL.h> +#endif +#endif + +#include "PlatformPosix.h" +#include "application/AppEnvironment.h" +#include "application/AppParamParser.h" +#include "platform/xbmc.h" + +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) +#include "platform/linux/AppParamParserLinux.h" +#endif + +#include <cstdio> +#include <cstring> +#include <errno.h> +#include <locale.h> +#include <signal.h> + +#include <sys/resource.h> + +namespace +{ +extern "C" void XBMC_POSIX_HandleSignal(int sig) +{ + // Setting an atomic flag is one of the only useful things that is permitted by POSIX + // in signal handlers + CPlatformPosix::RequestQuit(); +} +} // namespace + + +int main(int argc, char* argv[]) +{ +#if defined(_DEBUG) + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; + if (setrlimit(RLIMIT_CORE, &rlim) == -1) + fprintf(stderr, "Failed to set core size limit (%s).\n", strerror(errno)); +#endif + + // Set up global SIGINT/SIGTERM handler + struct sigaction signalHandler; + std::memset(&signalHandler, 0, sizeof(signalHandler)); + signalHandler.sa_handler = &XBMC_POSIX_HandleSignal; + signalHandler.sa_flags = SA_RESTART; + sigaction(SIGINT, &signalHandler, nullptr); + sigaction(SIGTERM, &signalHandler, nullptr); + + setlocale(LC_NUMERIC, "C"); + +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) + CAppParamParserLinux appParamParser; +#else + CAppParamParser appParamParser; +#endif + appParamParser.Parse(argv, argc); + + CAppEnvironment::SetUp(appParamParser.GetAppParams()); + int status = XBMC_Run(true); + CAppEnvironment::TearDown(); + + return status; +} diff --git a/xbmc/platform/posix/network/CMakeLists.txt b/xbmc/platform/posix/network/CMakeLists.txt new file mode 100644 index 0000000..3f8f850 --- /dev/null +++ b/xbmc/platform/posix/network/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES NetworkPosix.cpp) + +set(HEADERS NetworkPosix.h) + +core_add_library(platform_posix_network) diff --git a/xbmc/platform/posix/network/NetworkPosix.cpp b/xbmc/platform/posix/network/NetworkPosix.cpp new file mode 100644 index 0000000..003e5d7 --- /dev/null +++ b/xbmc/platform/posix/network/NetworkPosix.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "NetworkPosix.h" + +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <utility> + +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +CNetworkInterfacePosix::CNetworkInterfacePosix(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]) + : m_interfaceName(std::move(interfaceName)), + m_interfaceMacAdr(StringUtils::Format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + (uint8_t)interfaceMacAddrRaw[0], + (uint8_t)interfaceMacAddrRaw[1], + (uint8_t)interfaceMacAddrRaw[2], + (uint8_t)interfaceMacAddrRaw[3], + (uint8_t)interfaceMacAddrRaw[4], + (uint8_t)interfaceMacAddrRaw[5])) +{ + m_network = network; + memcpy(m_interfaceMacAddrRaw, interfaceMacAddrRaw, sizeof(m_interfaceMacAddrRaw)); +} + +bool CNetworkInterfacePosix::IsEnabled() const +{ + struct ifreq ifr; + strcpy(ifr.ifr_name, m_interfaceName.c_str()); + if (ioctl(m_network->GetSocket(), SIOCGIFFLAGS, &ifr) < 0) + return false; + + return ((ifr.ifr_flags & IFF_UP) == IFF_UP); +} + +bool CNetworkInterfacePosix::IsConnected() const +{ + struct ifreq ifr; + int zero = 0; + memset(&ifr, 0, sizeof(struct ifreq)); + strcpy(ifr.ifr_name, m_interfaceName.c_str()); + if (ioctl(m_network->GetSocket(), SIOCGIFFLAGS, &ifr) < 0) + return false; + + // ignore loopback + int iRunning = ((ifr.ifr_flags & IFF_RUNNING) && (!(ifr.ifr_flags & IFF_LOOPBACK))); + + if (ioctl(m_network->GetSocket(), SIOCGIFADDR, &ifr) < 0) + return false; + + // return only interfaces which has ip address + return iRunning && (0 != memcmp(ifr.ifr_addr.sa_data + sizeof(short), &zero, sizeof(int))); +} + +std::string CNetworkInterfacePosix::GetCurrentIPAddress() const +{ + std::string result; + + struct ifreq ifr; + strcpy(ifr.ifr_name, m_interfaceName.c_str()); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(m_network->GetSocket(), SIOCGIFADDR, &ifr) >= 0) + { + result = inet_ntoa((*((struct sockaddr_in*)&ifr.ifr_addr)).sin_addr); + } + + return result; +} + +std::string CNetworkInterfacePosix::GetCurrentNetmask() const +{ + std::string result; + + struct ifreq ifr; + strcpy(ifr.ifr_name, m_interfaceName.c_str()); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(m_network->GetSocket(), SIOCGIFNETMASK, &ifr) >= 0) + { + result = inet_ntoa((*((struct sockaddr_in*)&ifr.ifr_addr)).sin_addr); + } + + return result; +} + +std::string CNetworkInterfacePosix::GetMacAddress() const +{ + return m_interfaceMacAdr; +} + +void CNetworkInterfacePosix::GetMacAddressRaw(char rawMac[6]) const +{ + memcpy(rawMac, m_interfaceMacAddrRaw, 6); +} + +CNetworkPosix::CNetworkPosix() : CNetworkBase() +{ + m_sock = socket(AF_INET, SOCK_DGRAM, 0); +} + +CNetworkPosix::~CNetworkPosix() +{ + if (m_sock != -1) + close(CNetworkPosix::m_sock); + + std::vector<CNetworkInterface*>::iterator it = m_interfaces.begin(); + while (it != m_interfaces.end()) + { + CNetworkInterface* nInt = *it; + delete nInt; + it = m_interfaces.erase(it); + } +} + +std::vector<CNetworkInterface*>& CNetworkPosix::GetInterfaceList() +{ + return m_interfaces; +} + +//! @bug +//! Overwrite the GetFirstConnectedInterface and requery +//! the interface list if no connected device is found +//! this fixes a bug when no network is available after first start of xbmc +//! and the interface comes up during runtime +CNetworkInterface* CNetworkPosix::GetFirstConnectedInterface() +{ + CNetworkInterface* pNetIf = CNetworkBase::GetFirstConnectedInterface(); + + // no connected Interfaces found? - requeryInterfaceList + if (!pNetIf) + { + CLog::Log(LOGDEBUG, "{} no connected interface found - requery list", __FUNCTION__); + queryInterfaceList(); + //retry finding a connected if + pNetIf = CNetworkBase::GetFirstConnectedInterface(); + } + + return pNetIf; +} diff --git a/xbmc/platform/posix/network/NetworkPosix.h b/xbmc/platform/posix/network/NetworkPosix.h new file mode 100644 index 0000000..68d885e --- /dev/null +++ b/xbmc/platform/posix/network/NetworkPosix.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "network/Network.h" + +#include <string> +#include <vector> + +class CNetworkPosix; + +class CNetworkInterfacePosix : public CNetworkInterface +{ +public: + CNetworkInterfacePosix(CNetworkPosix* network, + std::string interfaceName, + char interfaceMacAddrRaw[6]); + virtual ~CNetworkInterfacePosix() override = default; + + bool IsEnabled() const override; + bool IsConnected() const override; + std::string GetCurrentIPAddress() const override; + std::string GetCurrentNetmask() const override; + + std::string GetMacAddress() const override; + void GetMacAddressRaw(char rawMac[6]) const override; + +protected: + std::string m_interfaceName; + CNetworkPosix* m_network; + +private: + std::string m_interfaceMacAdr; + char m_interfaceMacAddrRaw[6]; +}; + +class CNetworkPosix : public CNetworkBase +{ +public: + virtual ~CNetworkPosix() override; + + std::vector<CNetworkInterface*>& GetInterfaceList() override; + CNetworkInterface* GetFirstConnectedInterface() override; + + int GetSocket() { return m_sock; } + +protected: + CNetworkPosix(); + std::vector<CNetworkInterface*> m_interfaces; + +private: + virtual void GetMacAddress(const std::string& interfaceName, char macAddrRaw[6]) = 0; + virtual void queryInterfaceList() = 0; + int m_sock; +}; diff --git a/xbmc/platform/posix/storage/discs/CMakeLists.txt b/xbmc/platform/posix/storage/discs/CMakeLists.txt new file mode 100644 index 0000000..7587043 --- /dev/null +++ b/xbmc/platform/posix/storage/discs/CMakeLists.txt @@ -0,0 +1,7 @@ +if(ENABLE_OPTICAL) + set(SOURCES DiscDriveHandlerPosix.cpp) + + set(HEADERS DiscDriveHandlerPosix.h) + + core_add_library(platform_posix_storage_discs) +endif() diff --git a/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp new file mode 100644 index 0000000..1ef288a --- /dev/null +++ b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DiscDriveHandlerPosix.h" + +#include "storage/cdioSupport.h" +#include "utils/log.h" + +#include <memory> + +namespace +{ +constexpr int MAX_OPEN_RETRIES = 3; +} +using namespace MEDIA_DETECT; + +std::shared_ptr<IDiscDriveHandler> IDiscDriveHandler::CreateInstance() +{ + return std::make_shared<CDiscDriveHandlerPosix>(); +} + +DriveState CDiscDriveHandlerPosix::GetDriveState(const std::string& devicePath) +{ + DriveState driveStatus = DriveState::NOT_READY; + const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance(); + if (!libCdio) + { + CLog::LogF(LOGERROR, "Failed to obtain libcdio handler"); + return driveStatus; + } + + CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN); + if (!cdio) + { + return driveStatus; + } + + CdioTrayStatus status = libCdio->mmc_get_tray_status(cdio); + + switch (status) + { + case CdioTrayStatus::CLOSED: + case CdioTrayStatus::UNKNOWN: + driveStatus = DriveState::CLOSED_MEDIA_UNDEFINED; + break; + + case CdioTrayStatus::OPEN: + driveStatus = DriveState::OPEN; + break; + + case CdioTrayStatus::DRIVER_ERROR: + driveStatus = DriveState::NOT_READY; + break; + + default: + CLog::LogF(LOGWARNING, "Unknown/unhandled drive state interpreted as DRIVE_NOT_READY"); + break; + } + libCdio->cdio_destroy(cdio); + + return driveStatus; +} + +TrayState CDiscDriveHandlerPosix::GetTrayState(const std::string& devicePath) +{ + TrayState trayStatus = TrayState::UNDEFINED; + const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance(); + if (!libCdio) + { + CLog::LogF(LOGERROR, "Failed to obtain libcdio handler"); + return trayStatus; + } + + discmode_t discmode = CDIO_DISC_MODE_NO_INFO; + CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN); + if (!cdio) + { + return trayStatus; + } + + discmode = libCdio->cdio_get_discmode(cdio); + + if (discmode == CDIO_DISC_MODE_NO_INFO) + { + trayStatus = TrayState::CLOSED_NO_MEDIA; + } + else if (discmode == CDIO_DISC_MODE_ERROR) + { + trayStatus = TrayState::UNDEFINED; + } + else + { + trayStatus = TrayState::CLOSED_MEDIA_PRESENT; + } + libCdio->cdio_destroy(cdio); + + return trayStatus; +} + +void CDiscDriveHandlerPosix::EjectDriveTray(const std::string& devicePath) +{ + const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance(); + if (!libCdio) + { + CLog::LogF(LOGERROR, "Failed to obtain libcdio handler"); + return; + } + + int retries = MAX_OPEN_RETRIES; + CdIo_t* cdio = libCdio->cdio_open(devicePath.c_str(), DRIVER_UNKNOWN); + while (cdio && retries-- > 0) + { + const driver_return_code_t ret = libCdio->cdio_eject_media(&cdio); + if (ret == DRIVER_OP_SUCCESS) + break; + } + libCdio->cdio_destroy(cdio); +} + +void CDiscDriveHandlerPosix::CloseDriveTray(const std::string& devicePath) +{ + const std::shared_ptr<CLibcdio> libCdio = CLibcdio::GetInstance(); + if (!libCdio) + { + CLog::LogF(LOGERROR, "Failed to obtain libcdio handler"); + return; + } + + const driver_return_code_t ret = libCdio->cdio_close_tray(devicePath.c_str(), nullptr); + if (ret != DRIVER_OP_SUCCESS) + { + CLog::LogF(LOGERROR, "Closing tray failed for device {}: {}", devicePath, + libCdio->cdio_driver_errmsg(ret)); + } +} + +void CDiscDriveHandlerPosix::ToggleDriveTray(const std::string& devicePath) +{ + if (GetDriveState(devicePath) == DriveState::OPEN) + { + CloseDriveTray(devicePath); + } + else + { + EjectDriveTray(devicePath); + } +} diff --git a/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h new file mode 100644 index 0000000..11c4d9a --- /dev/null +++ b/xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "storage/discs/IDiscDriveHandler.h" + +#include <string> + +class CDiscDriveHandlerPosix : public IDiscDriveHandler +{ +public: + /*! \brief Posix DiscDriveHandler constructor + */ + CDiscDriveHandlerPosix() = default; + + /*! \brief Posix DiscDriveHandler default destructor + */ + ~CDiscDriveHandlerPosix() override = default; + + /*! \brief Get the optical drive state provided its device path + * \param devicePath the path for the device drive (e.g. /dev/sr0) + * \return The drive state + */ + DriveState GetDriveState(const std::string& devicePath) override; + + /*! \brief Get the optical drive tray state provided the drive device path + * \param devicePath the path for the device drive (e.g. /dev/sr0) + * \return The drive state + */ + TrayState GetTrayState(const std::string& devicePath) override; + + /*! \brief Eject the provided drive device + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + void EjectDriveTray(const std::string& devicePath) override; + + /*! \brief Close the provided drive device + * \note Some drives support closing appart from opening/eject + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + void CloseDriveTray(const std::string& devicePath) override; + + /*! \brief Toggle the state of a given drive device + * + * Will internally call EjectDriveTray or CloseDriveTray depending on + * the internal state of the drive (i.e. if open -> CloseDriveTray / + * if closed -> EjectDriveTray) + * + * \param devicePath the path for the device drive (e.g. /dev/sr0) + */ + void ToggleDriveTray(const std::string& devicePath) override; +}; diff --git a/xbmc/platform/posix/threads/CMakeLists.txt b/xbmc/platform/posix/threads/CMakeLists.txt new file mode 100644 index 0000000..251a7e1 --- /dev/null +++ b/xbmc/platform/posix/threads/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES RecursiveMutex.cpp) +set(HEADERS RecursiveMutex.h) + +if(NOT CORE_SYSTEM_NAME STREQUAL linux AND NOT CORE_SYSTEM_NAME STREQUAL android) + list(APPEND SOURCES ThreadImplPosix.cpp) + list(APPEND HEADERS ThreadImplPosix.h) +endif() + +core_add_library(platform_posix_threads) diff --git a/xbmc/platform/posix/threads/RecursiveMutex.cpp b/xbmc/platform/posix/threads/RecursiveMutex.cpp new file mode 100644 index 0000000..652bc37 --- /dev/null +++ b/xbmc/platform/posix/threads/RecursiveMutex.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RecursiveMutex.h" + +namespace XbmcThreads +{ + +static pthread_mutexattr_t recursiveAttr; + +static bool SetRecursiveAttr() +{ + static bool alreadyCalled = false; + + if (!alreadyCalled) + { + pthread_mutexattr_init(&recursiveAttr); + pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); +#if !defined(TARGET_ANDROID) + pthread_mutexattr_setprotocol(&recursiveAttr, PTHREAD_PRIO_INHERIT); +#endif + alreadyCalled = true; + } + + return true; // note, we never call destroy. +} + +static bool recursiveAttrSet = SetRecursiveAttr(); + +pthread_mutexattr_t& CRecursiveMutex::getRecursiveAttr() +{ + if (!recursiveAttrSet) // this is only possible in the single threaded startup code + recursiveAttrSet = SetRecursiveAttr(); + + return recursiveAttr; +} + +} // namespace XbmcThreads diff --git a/xbmc/platform/posix/threads/RecursiveMutex.h b/xbmc/platform/posix/threads/RecursiveMutex.h new file mode 100644 index 0000000..eed0fb2 --- /dev/null +++ b/xbmc/platform/posix/threads/RecursiveMutex.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <mutex> + +#include <pthread.h> +namespace XbmcThreads +{ + +/*! + * \brief This class exists purely for the ability to + * set mutex attribute PTHREAD_PRIO_INHERIT. + * Currently there is no way to set this using + * std::recursive_mutex. + * + */ +class CRecursiveMutex +{ +private: + pthread_mutex_t m_mutex; + + static pthread_mutexattr_t& getRecursiveAttr(); + +public: + CRecursiveMutex(const CRecursiveMutex&) = delete; + CRecursiveMutex& operator=(const CRecursiveMutex&) = delete; + + inline CRecursiveMutex() { pthread_mutex_init(&m_mutex, &getRecursiveAttr()); } + + inline ~CRecursiveMutex() { pthread_mutex_destroy(&m_mutex); } + + inline void lock() { pthread_mutex_lock(&m_mutex); } + + inline void unlock() { pthread_mutex_unlock(&m_mutex); } + + inline bool try_lock() { return (pthread_mutex_trylock(&m_mutex) == 0); } + + inline std::recursive_mutex::native_handle_type native_handle() { return &m_mutex; } +}; + +} // namespace XbmcThreads diff --git a/xbmc/platform/posix/threads/ThreadImplPosix.cpp b/xbmc/platform/posix/threads/ThreadImplPosix.cpp new file mode 100644 index 0000000..ae5b849 --- /dev/null +++ b/xbmc/platform/posix/threads/ThreadImplPosix.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ThreadImplPosix.h" + +#include "utils/log.h" + +#include <pthread.h> + +std::unique_ptr<IThreadImpl> IThreadImpl::CreateThreadImpl(std::thread::native_handle_type handle) +{ + return std::make_unique<CThreadImplPosix>(handle); +} + +CThreadImplPosix::CThreadImplPosix(std::thread::native_handle_type handle) : IThreadImpl(handle) +{ +} + +void CThreadImplPosix::SetThreadInfo(const std::string& name) +{ +#if defined(TARGET_DARWIN) + pthread_setname_np(name.c_str()); +#endif +} + +bool CThreadImplPosix::SetPriority(const ThreadPriority& priority) +{ + CLog::Log(LOGDEBUG, "[threads] setting priority is not supported on this platform"); + return false; +} diff --git a/xbmc/platform/posix/threads/ThreadImplPosix.h b/xbmc/platform/posix/threads/ThreadImplPosix.h new file mode 100644 index 0000000..7bba5e1 --- /dev/null +++ b/xbmc/platform/posix/threads/ThreadImplPosix.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/IThreadImpl.h" + +class CThreadImplPosix : public IThreadImpl +{ +public: + CThreadImplPosix(std::thread::native_handle_type handle); + + ~CThreadImplPosix() override = default; + + void SetThreadInfo(const std::string& name) override; + + bool SetPriority(const ThreadPriority& priority) override; +}; diff --git a/xbmc/platform/posix/utils/CMakeLists.txt b/xbmc/platform/posix/utils/CMakeLists.txt new file mode 100644 index 0000000..8fb2abc --- /dev/null +++ b/xbmc/platform/posix/utils/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES Mmap.cpp + PosixInterfaceForCLog.cpp + SharedMemory.cpp) + +set(HEADERS FileHandle.h + Mmap.h + PosixInterfaceForCLog.h + SharedMemory.h) + +core_add_library(platform_posix_utils) diff --git a/xbmc/platform/posix/utils/FileHandle.h b/xbmc/platform/posix/utils/FileHandle.h new file mode 100644 index 0000000..e50a357 --- /dev/null +++ b/xbmc/platform/posix/utils/FileHandle.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/ScopeGuard.h" + +#include <unistd.h> + +namespace KODI +{ +namespace UTILS +{ +namespace POSIX +{ + +class CFileHandle : public CScopeGuard<int, -1, decltype(close)> +{ +public: + CFileHandle() noexcept : CScopeGuard(close, -1) {} + explicit CFileHandle(int fd) : CScopeGuard(close, fd) {} +}; + +} +} +} diff --git a/xbmc/platform/posix/utils/Mmap.cpp b/xbmc/platform/posix/utils/Mmap.cpp new file mode 100644 index 0000000..3d40d3c --- /dev/null +++ b/xbmc/platform/posix/utils/Mmap.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Mmap.h" + +#include <system_error> + +using namespace KODI::UTILS::POSIX; + +CMmap::CMmap(void* addr, std::size_t length, int prot, int flags, int fildes, off_t offset) +: m_size{length}, m_memory{mmap(addr, length, prot, flags, fildes, offset)} +{ + if (m_memory == MAP_FAILED) + { + throw std::system_error(errno, std::generic_category(), "mmap failed"); + } +} + +CMmap::~CMmap() +{ + munmap(m_memory, m_size); +}
\ No newline at end of file diff --git a/xbmc/platform/posix/utils/Mmap.h b/xbmc/platform/posix/utils/Mmap.h new file mode 100644 index 0000000..2470d60 --- /dev/null +++ b/xbmc/platform/posix/utils/Mmap.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstddef> + +#include <sys/mman.h> + +namespace KODI +{ +namespace UTILS +{ +namespace POSIX +{ + +/** + * Wrapper for mapped memory that automatically calls munmap on destruction + */ +class CMmap +{ +public: + /** + * See mmap(3p) for parameter description + */ + CMmap(void* addr, std::size_t length, int prot, int flags, int fildes, off_t offset); + ~CMmap(); + + void* Data() const + { + return m_memory; + } + std::size_t Size() const + { + return m_size; + } + +private: + CMmap(CMmap const& other) = delete; + CMmap& operator=(CMmap const& other) = delete; + + std::size_t m_size; + void* m_memory; +}; + +} +} +} diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp new file mode 100644 index 0000000..6b8bf48 --- /dev/null +++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PosixInterfaceForCLog.h" + +#include "ServiceBroker.h" +#include "application/AppParams.h" + +#include <spdlog/sinks/dist_sink.h> +#include <spdlog/sinks/stdout_color_sinks.h> + +#if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN) +std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog() +{ + return std::make_unique<CPosixInterfaceForCLog>(); +} +#endif + +void CPosixInterfaceForCLog::AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const +{ + if (CServiceBroker::GetAppParams()->GetLogTarget() == "console") + distributionSink->add_sink(std::make_shared<spdlog::sinks::stdout_color_sink_st>()); +} diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.h b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h new file mode 100644 index 0000000..0336b16 --- /dev/null +++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IPlatformLog.h" + +class CPosixInterfaceForCLog : public IPlatformLog +{ +public: + CPosixInterfaceForCLog() = default; + virtual ~CPosixInterfaceForCLog() override = default; + + spdlog_filename_t GetLogFilename(const std::string& filename) const override { return filename; } + void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override; +}; diff --git a/xbmc/platform/posix/utils/SharedMemory.cpp b/xbmc/platform/posix/utils/SharedMemory.cpp new file mode 100644 index 0000000..6c1318e --- /dev/null +++ b/xbmc/platform/posix/utils/SharedMemory.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SharedMemory.h" + +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> + +#if defined(HAVE_LINUX_MEMFD) +#include <linux/memfd.h> +#include <sys/syscall.h> +#endif + +#include <cerrno> +#include <cstdlib> +#include <system_error> + +#include "utils/log.h" + +using namespace KODI::UTILS::POSIX; + +CSharedMemory::CSharedMemory(std::size_t size) +: m_size{size}, m_fd{Open()}, m_mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0) +{ +} + +CFileHandle CSharedMemory::Open() +{ + CFileHandle fd; + try + { + fd = OpenMemfd(); + } + catch (std::system_error const& error) + { + if (error.code() == std::errc::function_not_supported) + { + CLog::Log(LOGDEBUG, "Kernel does not support memfd, falling back to plain shm"); + fd = OpenShm(); + } + else + { + throw; + } + } + + if (ftruncate(fd, m_size) < 0) + { + throw std::system_error(errno, std::generic_category(), "ftruncate"); + } + + return fd; +} + +CFileHandle CSharedMemory::OpenMemfd() +{ +#if defined(SYS_memfd_create) && defined(HAVE_LINUX_MEMFD) + // This is specific to Linux >= 3.17, but preferred over shm_create if available + // because it is race-free + int fd = syscall(SYS_memfd_create, "kodi", MFD_CLOEXEC); + if (fd < 0) + { + throw std::system_error(errno, std::generic_category(), "memfd_create"); + } + + return CFileHandle(fd); +#else + throw std::system_error(std::make_error_code(std::errc::function_not_supported), "memfd_create"); +#endif +} + +CFileHandle CSharedMemory::OpenShm() +{ + char* xdgRuntimeDir = std::getenv("XDG_RUNTIME_DIR"); + if (!xdgRuntimeDir) + { + throw std::runtime_error("XDG_RUNTIME_DIR environment variable must be set"); + } + + std::string tmpFilename(xdgRuntimeDir); + tmpFilename.append("/kodi-shared-XXXXXX"); + + int rawFd; +#if defined(HAVE_MKOSTEMP) + // Opening the file with O_CLOEXEC is preferred since it avoids races where + // other threads might exec() before setting the CLOEXEC flag + rawFd = mkostemp(&tmpFilename[0], O_CLOEXEC); +#else + rawFd = mkstemp(&tmpFilename[0]); +#endif + + if (rawFd < 0) + { + throw std::system_error(errno, std::generic_category(), "mkstemp"); + } + CFileHandle fd(rawFd); + + int flags = fcntl(fd, F_GETFD); + if (flags < 0) + { + throw std::system_error(errno, std::generic_category(), "fcntl F_GETFD"); + } + // Set FD_CLOEXEC if unset + if (!(flags & FD_CLOEXEC) && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + throw std::system_error(errno, std::generic_category(), "fcntl F_SETFD FD_CLOEXEC"); + } + + unlink(tmpFilename.c_str()); + + return fd; +}
\ No newline at end of file diff --git a/xbmc/platform/posix/utils/SharedMemory.h b/xbmc/platform/posix/utils/SharedMemory.h new file mode 100644 index 0000000..b556de6 --- /dev/null +++ b/xbmc/platform/posix/utils/SharedMemory.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileHandle.h" +#include "Mmap.h" + +#include <memory> + +namespace KODI +{ +namespace UTILS +{ +namespace POSIX +{ + +/** + * Get a chunk of shared memory of specified size + * + * The shared memory is automatically allocated, truncated to the correct size + * and memory-mapped. + */ +class CSharedMemory +{ +public: + explicit CSharedMemory(std::size_t size); + + std::size_t Size() const + { + return m_size; + } + void* Data() const + { + return m_mmap.Data(); + } + int Fd() const + { + return m_fd; + } + +private: + CSharedMemory(CSharedMemory const& other) = delete; + CSharedMemory& operator=(CSharedMemory const& other) = delete; + + CFileHandle Open(); + CFileHandle OpenMemfd(); + CFileHandle OpenShm(); + + std::size_t m_size; + CFileHandle m_fd; + CMmap m_mmap; +}; + +} +} +} diff --git a/xbmc/platform/xbmc.cpp b/xbmc/platform/xbmc.cpp new file mode 100644 index 0000000..a6de8fb --- /dev/null +++ b/xbmc/platform/xbmc.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "application/Application.h" +#include "platform/MessagePrinter.h" + +#ifdef TARGET_WINDOWS_DESKTOP +#include "platform/win32/IMMNotificationClient.h" +#include <mmdeviceapi.h> +#include <wrl/client.h> +#endif + +#if defined(TARGET_ANDROID) +#include "platform/android/activity/XBMCApp.h" +#endif + +extern "C" int XBMC_Run(bool renderGUI) +{ + int status = -1; + + if (!g_application.Create()) + { + CMessagePrinter::DisplayError("ERROR: Unable to create application. Exiting"); + return status; + } + +#if defined(TARGET_ANDROID) + CXBMCApp::Get().Initialize(); +#endif + + if (renderGUI && !g_application.CreateGUI()) + { + CMessagePrinter::DisplayError("ERROR: Unable to create GUI. Exiting"); + if (g_application.Stop(EXITCODE_QUIT)) + g_application.Cleanup(); + return status; + } + if (!g_application.Initialize()) + { + CMessagePrinter::DisplayError("ERROR: Unable to Initialize. Exiting"); + return status; + } + +#ifdef TARGET_WINDOWS_DESKTOP + Microsoft::WRL::ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr; + CMMNotificationClient cMMNC; + HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, + reinterpret_cast<void**>(pEnumerator.GetAddressOf())); + if (SUCCEEDED(hr)) + { + pEnumerator->RegisterEndpointNotificationCallback(&cMMNC); + pEnumerator = nullptr; + } +#endif + + status = g_application.Run(); + +#ifdef TARGET_WINDOWS_DESKTOP + // the end + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, + reinterpret_cast<void**>(pEnumerator.GetAddressOf())); + if (SUCCEEDED(hr)) + { + pEnumerator->UnregisterEndpointNotificationCallback(&cMMNC); + pEnumerator = nullptr; + } +#endif + +#if defined(TARGET_ANDROID) + CXBMCApp::Get().Deinitialize(); +#endif + + return status; +} diff --git a/xbmc/platform/xbmc.h b/xbmc/platform/xbmc.h new file mode 100644 index 0000000..206acdb --- /dev/null +++ b/xbmc/platform/xbmc.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +class CAppParams; + +extern "C" int XBMC_Run(bool renderGUI); |