Skip to content

Commit

Permalink
NativeIO: Add Linux support (vmangos#2696)
Browse files Browse the repository at this point in the history
* Create NativeAliases.h

* IO::Networking moved some files around

* `IocpOperationTask` as member of `AsyncSocket` to avoid `new` memory allocation

* Native IO: Able to compile empty impl on linux

* Fix windows build

* Native IO: Linux add fs support

* Native IO: Linux, able to connect

* Fix uninitialized `m_sessionDurationTimeout`

* Fix SO_REUSEADDR on linux

* Sortof impl of unix epoll AsyncSocket

* Add own implementation of `AsyncSystemTimer`

* Code Style

* Native IO: Add all other Write handlers

* NativeIO: Linux add EPOLLRDHUP for disconnect requests

* Fix windows build

* NativeIO: Windows Code Style

* Fix EnumFlags

* Update comment
  • Loading branch information
0blu authored Jun 30, 2024
1 parent c4dd43f commit cbd8523
Show file tree
Hide file tree
Showing 39 changed files with 1,548 additions and 647 deletions.
7 changes: 3 additions & 4 deletions src/realmd/AuthSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ void AuthSocket::ProcessIncomingData()
{
if (error)
{
if (error.Error != IO::NetworkError::ErrorType::SocketClosed)
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[Auth] ProcessIncomingData Read(cmd) error");
if (error.GetErrorType() != IO::NetworkError::ErrorType::SocketClosed)
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[Auth] ProcessIncomingData Read(cmd) error: %s", error.ToString().c_str());
return;
}

Expand Down Expand Up @@ -1455,7 +1455,7 @@ void AuthSocket::RepeatInternalXferLoop(std::shared_ptr<uint8_t> rawChunk)
{
if (error)
{
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[XFER]: Write(...) failed: %s", error.toString().c_str());
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[XFER]: Write(...) failed: %s", error.ToString().c_str());
return;
}
self->RepeatInternalXferLoop(std::move(rawChunk));
Expand Down Expand Up @@ -1614,4 +1614,3 @@ bool AuthSocket::VerifyVersion(uint8 const* a, int32 aLength, uint8 const* versi

return false;
}

4 changes: 1 addition & 3 deletions src/realmd/AuthSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ struct sAuthLogonProof_C;
class AuthSocket : public IO::Networking::AsyncSocket<AuthSocket>
{
public:
const static int s_BYTE_SIZE = 32;

explicit AuthSocket(IO::Networking::SocketDescriptor const& clientAddress);
~AuthSocket();

Expand Down Expand Up @@ -140,7 +138,7 @@ class AuthSocket : public IO::Networking::AsyncSocket<AuthSocket>
AccountSecurityMap m_accountSecurityOnRealm;

// Auto kick realmd client connection after some time
std::shared_ptr<IO::Timer::TimerHandle> m_sessionDurationTimeout;
std::shared_ptr<IO::Timer::TimerHandle> m_sessionDurationTimeout = nullptr;

// Patching stuff
void InitAndHandOverControlToPatchHandler();
Expand Down
9 changes: 5 additions & 4 deletions src/realmd/ClientPatchCache.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include "./ClientPatchCache.h"
#include "Policies/SingletonImp.h"
#include "Log.h"
#include "md5.h"
#include "Config/Config.h"
#include "IO/Filesystem/FileSystem.h"
#include "IO/Filesystem/FileHandle.h"

#include <openssl/md5.h>

INSTANTIATE_SINGLETON_1(ClientPatchCache);

ClientPatchCache::ClientPatchCache()
Expand Down Expand Up @@ -41,19 +42,19 @@ Md5HashDigest ClientPatchCache::GetOrCalculateHash(std::unique_ptr<IO::Filesyste
auto lastModifyDate = fileHandle->GetLastModifyDate();
auto fileSize = fileHandle->GetTotalFileSize();

m_knownPatches_mutex.lock_shared();
m_knownPatches_mutex.lock();
auto const& exisingEntry = m_knownPatches.find(filePath);
if (exisingEntry == m_knownPatches.end() || exisingEntry->second.lastModifyDate != lastModifyDate || exisingEntry->second.fileSize != fileSize)
{ // file does not exist in cache or was changed
m_knownPatches_mutex.unlock_shared();
m_knownPatches_mutex.unlock();
sLog.Out(LOG_BASIC, LOG_LVL_BASIC, "[PatchCache] Detected change of file '%s'. Will recalculate hash.", filePath.c_str());
// It's important to have a duplicate file handle here, since we want to guarantee easy access
return CalculateAndCacheHash(fileHandle->DuplicateFileHandle());
}
else
{ // we can use the existent entry
Md5HashDigest md5Hash = exisingEntry->second.md5Hash;
m_knownPatches_mutex.unlock_shared();
m_knownPatches_mutex.unlock();
return md5Hash;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/realmd/ClientPatchCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ClientPatchCache : public MaNGOS::Singleton<ClientPatchCache, MaNGOS::Clas

private:
void LoadPatchesInfo();
std::shared_mutex m_knownPatches_mutex; // will be read-lock when normal access, will be write-lock when adding a new entry
std::mutex m_knownPatches_mutex;
std::unordered_map<std::string /*filePath*/, PatchCacheEntry> m_knownPatches;
};

Expand Down
5 changes: 5 additions & 0 deletions src/realmd/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include <ace/Get_Opt.h>
#include "IO/Networking/AsyncServerListener.h"
#include "IO/Timer/AsyncSystemTimer.h"
#include "IO/Multithreading/CreateThread.h"

#ifdef USE_SENDGRID
#include "MailerService.h"
Expand Down Expand Up @@ -292,6 +294,9 @@ extern int main(int argc, char **argv)
return 1;
}

(void) sAsyncSystemTimer; // <-- Pre-Initialize SystemTimer
IO::Multithreading::RenameCurrentThread("Main/IO");

// Catch termination signals
HookSignals();

Expand Down
21 changes: 17 additions & 4 deletions src/shared/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,30 @@ set (shared_SRCS
Database/SQLStorage.cpp
Multithreading/Messager.cpp
SRP6/SRP6.cpp
IO/SystemErrorToString.h
IO/SystemErrorToString.cpp
IO/Networking/AsyncServerListener.h
IO/Networking/AsyncSocket.h
IO/Networking/NetworkError.h
IO/Networking/NetworkError.cpp
IO/Networking/SocketDescriptor.h
IO/Networking/Utils.h
IO/Networking/Utils.cpp
IO/Networking/IpAddress.h
IO/Networking/IpAddress.cpp
IO/Multithreading/CreateThread.h
IO/Multithreading/CreateThread.cpp
IO/Timer/impl/windows/AsyncSystemTimer.h
IO/Timer/impl/windows/AsyncSystemTimer.cpp
IO/Timer/impl/windows/TimerHandle.h
IO/Timer/impl/windows/TimerHandle.cpp
IO/Timer/impl/unix/AsyncSystemTimer.cpp
IO/Timer/impl/unix/TimerHandle.cpp
IO/Timer/AsyncSystemTimer.h
IO/Filesystem/FileSystem.h
IO/Filesystem/FileSystem.cpp
IO/Filesystem/FileHandle.h
IO/Filesystem/FileHandle.cpp
IO/Filesystem/impl/windows/FileSystem.cpp
IO/Filesystem/impl/windows/FileHandle.cpp
IO/Filesystem/impl/unix/FileSystem.cpp
IO/Filesystem/impl/unix/FileHandle.cpp
)

if(USE_LIBCURL)
Expand All @@ -138,6 +143,10 @@ if(WIN32)
PosixDaemon.cpp
revision.h
migrations_list.h
IO/Timer/impl/unix/AsyncSystemTimer.cpp
IO/Timer/impl/unix/TimerHandle.cpp
IO/Filesystem/impl/unix/FileSystem.cpp
IO/Filesystem/impl/unix/FileHandle.cpp
)

if (NOT MSVC)
Expand All @@ -156,6 +165,10 @@ else()
ServiceWin32.h
revision.h
migrations_list.h
IO/Timer/impl/windows/AsyncSystemTimer.cpp
IO/Timer/impl/windows/TimerHandle.cpp
IO/Filesystem/impl/windows/FileSystem.cpp
IO/Filesystem/impl/windows/FileHandle.cpp
)
endif()

Expand Down
2 changes: 1 addition & 1 deletion src/shared/EnumFlag.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ constexpr bool IsEnumFlag(T) { return false; }
namespace EnumTraits
{
template<typename T>
using IsFlag = std::conjunction<std::is_enum<T>, std::integral_constant<bool, IsEnumFlag(T{})>>;
using IsFlag = std::integral_constant<bool, IsEnumFlag(T{})>;
}

template<typename T, std::enable_if_t<EnumTraits::IsFlag<T>::value, std::nullptr_t> = nullptr>
Expand Down
11 changes: 4 additions & 7 deletions src/shared/IO/Filesystem/FileHandle.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#ifndef MANGOS_IO_FILESYSTEM_FILEHANDLE_H
#define MANGOS_IO_FILESYSTEM_FILEHANDLE_H

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#undef WIN32_LEAN_AND_MEAN

#include <cstdint>
#include <chrono>
#include <string>
#include <memory>
#include "IO/NativeAliases.h"

namespace IO { namespace Filesystem {

Expand All @@ -31,14 +28,14 @@ namespace IO { namespace Filesystem {
std::string GetAbsoluteFilePath() const;

protected:
explicit FileHandle(HANDLE nativeFileHandle);
HANDLE m_nativeFileHandle;
explicit FileHandle(IO::Native::FileHandle nativeFileHandle);
IO::Native::FileHandle m_nativeFileHandle;
};

class FileHandleReadonly : public FileHandle
{
public:
explicit FileHandleReadonly(HANDLE nativeFileHandle) : FileHandle(nativeFileHandle) {};
explicit FileHandleReadonly(IO::Native::FileHandle nativeFileHandle) : FileHandle(nativeFileHandle) {};
FileHandleReadonly(FileHandleReadonly const&) = delete;
FileHandleReadonly& operator=(FileHandleReadonly const&) = delete;
FileHandleReadonly(FileHandleReadonly&&) = delete;
Expand Down
96 changes: 96 additions & 0 deletions src/shared/IO/Filesystem/impl/unix/FileHandle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include "IO/Filesystem/FileHandle.h"
#include "Log.h"
#include "IO/SystemErrorToString.h"
#include <sys/param.h>

IO::Filesystem::FileHandle::FileHandle(IO::Native::FileHandle nativeFileHandle) : m_nativeFileHandle(nativeFileHandle)
{
}

IO::Filesystem::FileHandle::~FileHandle()
{
sLog.Out(LOG_NETWORK, LOG_LVL_DETAIL, "Destructor called ~FileHandle: No references left");
::close(m_nativeFileHandle);
}

uint64_t IO::Filesystem::FileHandle::GetTotalFileSize() const
{
struct stat file_stat;

if (fstat(m_nativeFileHandle, &file_stat) == -1)
throw std::runtime_error("GetTotalFileSize -> ::fstat() Failed: " + SystemErrorToString(errno));

return file_stat.st_size;
}

std::chrono::system_clock::time_point IO::Filesystem::FileHandle::GetLastModifyDate() const
{
struct stat file_stat;

if (fstat(m_nativeFileHandle, &file_stat) == -1)
throw std::runtime_error("GetLastModifyDate -> ::fstat() Failed: " + SystemErrorToString(errno));

uint64_t unixSecs = file_stat.st_mtime;

std::chrono::system_clock::time_point result(std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::seconds(unixSecs)));
return result;
}

std::string IO::Filesystem::FileHandle::GetAbsoluteFilePath() const
{
char filePath[PATH_MAX];
#if defined(__linux__)
// I really hate LINUX, there is no universal way to get a file path
// Everyone on the internet recommends the following approach.
// (Which can fail if we dont have access to the virtual-"/proc"-fs).

char pathToLink[PATH_MAX];
snprintf(pathToLink, sizeof(pathToLink), "/proc/self/fd/%d", m_nativeFileHandle);

ssize_t len = ::readlink(pathToLink, filePath, sizeof(filePath) - 1);
if (len == -1)
throw std::runtime_error("GetAbsoluteFilePath -> ::readlink() Failed: " + SystemErrorToString(errno));
filePath[len] = '\0';

#elif defined(MACOS)
if (fcntl(m_nativeFileHandle, F_GETPATH, filePath) == -1)
throw std::runtime_error("::fstat(GetLastModifyDate) Failed: " + SystemErrorToString(errno));
#else
#error "How to implement FileHandle::GetAbsoluteFilePath() on your OS?"
#endif

return filePath;
}

uint64_t IO::Filesystem::FileHandleReadonly::ReadSync(uint8_t* dest, uint64_t amountToRead)
{
uint64_t leftToRead = amountToRead;
while (leftToRead > 0)
{
size_t amountToReadThisCycle = (size_t) std::min(leftToRead, ((uint64_t) std::numeric_limits<size_t>::max()) - 1);

ssize_t actuallyReadThisCycle = ::read(m_nativeFileHandle, dest, amountToReadThisCycle);
if (actuallyReadThisCycle == -1)
{
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[ERROR] ReadSync -> ::read() Error: %s", SystemErrorToCString(errno));
return 0;
}
leftToRead -= actuallyReadThisCycle;

if (actuallyReadThisCycle != amountToReadThisCycle)
{
break;
}
}
return amountToRead - leftToRead;
}

std::unique_ptr<IO::Filesystem::FileHandleReadonly> IO::Filesystem::FileHandleReadonly::DuplicateFileHandle()
{
IO::Native::FileHandle newNativeFileHandle = ::dup(m_nativeFileHandle);

if (newNativeFileHandle == -1)
throw std::runtime_error("DuplicateFileHandle -> ::dup() Failed: " + SystemErrorToString(errno));

return std::make_unique<FileHandleReadonly>(newNativeFileHandle);
}
87 changes: 87 additions & 0 deletions src/shared/IO/Filesystem/impl/unix/FileSystem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "IO/Filesystem/FileSystem.h"
#include "IO/Filesystem/FileHandle.h"
#include "Log.h"
#include "IO/SystemErrorToString.h"
#include <limits.h>
#include <unistd.h>
#include <dirent.h>

/// This function will open a file in read shared and binary mode
/// You have to check the resulting pointer for nullptr!
/// If the file does not exists or you dont have permission to open it the ptr will be null
std::unique_ptr<IO::Filesystem::FileHandleReadonly> IO::Filesystem::TryOpenFileReadonly(std::string const& filePath, EnumFlag<FileOpenFlags> flags)
{
int nativeFlags = O_RDONLY;
IO::Native::FileHandle fileHandle = ::open(filePath.c_str(), nativeFlags);

if (fileHandle == -1) {
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Unable to open file. Error %s on file: %s", SystemErrorToCString(errno), filePath.c_str());
return nullptr;
}

if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_WILLNEED) != 0) // Tell Kernel: we might need the file in the near future, please preload it into mem
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Failed to set WILLNEED hint for file");

if (flags.HasFlag(FileOpenFlags::HintSequentialRead))
{
if (posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) // Tell Kernel: to preallocate and load memory pages while reading
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Failed to set SEQUENTIAL hint for file");
}

return std::unique_ptr<FileHandleReadonly>(new FileHandleReadonly(fileHandle));
}

/// Will convert a partial path like "./data/myCoolFile.txt" to a complete absolute path like "/home/user/data/myCoolFile.txt"
std::string IO::Filesystem::ToAbsolutePath(std::string const& partialPath)
{
// There is no absolute/canonicalize path function in linux.
// There is :realpath, but it requires the file/folder to be present

if (partialPath.find('/') == 0)
return partialPath; // already absolute

std::string trimmedPath = (partialPath.find("./") == 0)
? partialPath.substr(2) // starts with "relative from CWD path"
: partialPath;

char temp[PATH_MAX];
if (::getcwd(temp, sizeof(temp)) == nullptr)
throw std::runtime_error("ToAbsolutePath -> ::getcwd(...) Failed: " + SystemErrorToString(errno));

// TODO: Find a way to canonicalize_filepath without reimplementing it by my own
// TODO: For own impl: Keep in mind all the stuff that can be included like "../abc/../.\.\//d" or "./abc\./.conf/bash.config"
std::string completePath = std::string(temp) + "/" + trimmedPath;
return completePath;
}

/// Returns all files in a folder, non-recursively.
/// if OutputFilePath::JustFileName the path will be based on the folderPath e.g. "myCoolFile.txt"
/// if OutputFilePath::FullFilePath the path will be absolute e.g. "/home/user/data/myCoolFile.txt"
std::vector<std::string> IO::Filesystem::GetAllFilesInFolder(std::string const& folderPath, IO::Filesystem::OutputFilePath filePathOption)
{
std::vector<std::string> files;
DIR* dir = opendir(folderPath.c_str());
if (dir == nullptr)
return files;

std::string safeFolderPath = (folderPath.rfind('/') == (folderPath.size() - 1))
? folderPath // folder already ends with /
: folderPath + "/";

struct dirent* entry;
while ((entry = readdir(dir)) != nullptr)
{
std::string fullFilePath = safeFolderPath + entry->d_name; // we need the fullFilePath to check if it's a file via ::stat(...)
struct stat info{};
if (::stat(fullFilePath.c_str(), &info) == 0 && S_ISREG(info.st_mode)) // S_ISREG means IsRegularFile
{
std::string filePath = filePathOption == OutputFilePath::FullFilePath
? fullFilePath
: entry->d_name;

files.emplace_back(filePath);
}
}
::closedir(dir);
return files;
}
Loading

0 comments on commit cbd8523

Please sign in to comment.