diff options
Diffstat (limited to 'xbmc/platform/posix')
53 files changed, 5503 insertions, 0 deletions
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; +}; + +} +} +} |