summaryrefslogtreecommitdiffstats
path: root/xbmc/platform/posix
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/platform/posix')
-rw-r--r--xbmc/platform/posix/CMakeLists.txt21
-rw-r--r--xbmc/platform/posix/CPUInfoPosix.cpp60
-rw-r--r--xbmc/platform/posix/CPUInfoPosix.h23
-rw-r--r--xbmc/platform/posix/ConvUtils.cpp23
-rw-r--r--xbmc/platform/posix/ConvUtils.h15
-rw-r--r--xbmc/platform/posix/Filesystem.cpp128
-rw-r--r--xbmc/platform/posix/MessagePrinter.cpp40
-rw-r--r--xbmc/platform/posix/PlatformDefs.h153
-rw-r--r--xbmc/platform/posix/PlatformPosix.cpp63
-rw-r--r--xbmc/platform/posix/PlatformPosix.h25
-rw-r--r--xbmc/platform/posix/PosixMountProvider.cpp144
-rw-r--r--xbmc/platform/posix/PosixMountProvider.h37
-rw-r--r--xbmc/platform/posix/PosixResourceCounter.cpp71
-rw-r--r--xbmc/platform/posix/PosixResourceCounter.h31
-rw-r--r--xbmc/platform/posix/PosixTimezone.cpp272
-rw-r--r--xbmc/platform/posix/PosixTimezone.h58
-rw-r--r--xbmc/platform/posix/XHandle.cpp138
-rw-r--r--xbmc/platform/posix/XHandle.h59
-rw-r--r--xbmc/platform/posix/XHandlePublic.h19
-rw-r--r--xbmc/platform/posix/XTimeUtils.cpp228
-rw-r--r--xbmc/platform/posix/filesystem/CMakeLists.txt18
-rw-r--r--xbmc/platform/posix/filesystem/PosixDirectory.cpp207
-rw-r--r--xbmc/platform/posix/filesystem/PosixDirectory.h29
-rw-r--r--xbmc/platform/posix/filesystem/PosixFile.cpp388
-rw-r--r--xbmc/platform/posix/filesystem/PosixFile.h47
-rw-r--r--xbmc/platform/posix/filesystem/SMBDirectory.cpp372
-rw-r--r--xbmc/platform/posix/filesystem/SMBDirectory.h33
-rw-r--r--xbmc/platform/posix/filesystem/SMBFile.cpp721
-rw-r--r--xbmc/platform/posix/filesystem/SMBFile.h93
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscovery.cpp126
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscovery.h107
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp608
-rw-r--r--xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h128
-rw-r--r--xbmc/platform/posix/main.cpp75
-rw-r--r--xbmc/platform/posix/network/CMakeLists.txt5
-rw-r--r--xbmc/platform/posix/network/NetworkPosix.cpp150
-rw-r--r--xbmc/platform/posix/network/NetworkPosix.h61
-rw-r--r--xbmc/platform/posix/storage/discs/CMakeLists.txt7
-rw-r--r--xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.cpp152
-rw-r--r--xbmc/platform/posix/storage/discs/DiscDriveHandlerPosix.h58
-rw-r--r--xbmc/platform/posix/threads/CMakeLists.txt9
-rw-r--r--xbmc/platform/posix/threads/RecursiveMutex.cpp43
-rw-r--r--xbmc/platform/posix/threads/RecursiveMutex.h48
-rw-r--r--xbmc/platform/posix/threads/ThreadImplPosix.cpp35
-rw-r--r--xbmc/platform/posix/threads/ThreadImplPosix.h23
-rw-r--r--xbmc/platform/posix/utils/CMakeLists.txt10
-rw-r--r--xbmc/platform/posix/utils/FileHandle.h31
-rw-r--r--xbmc/platform/posix/utils/Mmap.cpp27
-rw-r--r--xbmc/platform/posix/utils/Mmap.h53
-rw-r--r--xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp29
-rw-r--r--xbmc/platform/posix/utils/PosixInterfaceForCLog.h22
-rw-r--r--xbmc/platform/posix/utils/SharedMemory.cpp118
-rw-r--r--xbmc/platform/posix/utils/SharedMemory.h62
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;
+};
+
+}
+}
+}