Core: Add initial process support (Process::SpawnDetached)
This commit is contained in:
parent
278e59934b
commit
ac1422c221
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_CORE_PROCESS_HPP
|
||||||
|
#define NAZARA_CORE_PROCESS_HPP
|
||||||
|
|
||||||
|
#include <NazaraUtils/Prerequisites.hpp>
|
||||||
|
#include <Nazara/Core/Config.hpp>
|
||||||
|
#include <NazaraUtils/Result.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Nz
|
||||||
|
{
|
||||||
|
using Pid = UInt32;
|
||||||
|
|
||||||
|
class NAZARA_CORE_API Process
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Process() = default;
|
||||||
|
Process(const Process&) = delete;
|
||||||
|
Process(Process&&) = delete;
|
||||||
|
~Process() = default;
|
||||||
|
|
||||||
|
Process& operator=(const Process&) = delete;
|
||||||
|
Process& operator=(Process&&) = delete;
|
||||||
|
|
||||||
|
static Result<Pid, std::string> SpawnDetached(const std::filesystem::path& program, std::span<const std::string> arguments = {}, const std::filesystem::path& workingDirectory = {}); };
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Nazara/Core/Process.inl>
|
||||||
|
|
||||||
|
#endif // NAZARA_CORE_PROCESS_HPP
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Core/Debug.hpp>
|
||||||
|
|
||||||
|
namespace Nz
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Nazara/Core/DebugOff.hpp>
|
||||||
|
|
@ -36,6 +36,7 @@ namespace Nz
|
||||||
inline bool IsNumber(std::string_view str);
|
inline bool IsNumber(std::string_view str);
|
||||||
|
|
||||||
NAZARA_CORE_API void IterateOnCodepoints(std::string_view str, FunctionRef<bool(std::u32string_view characters)> callback);
|
NAZARA_CORE_API void IterateOnCodepoints(std::string_view str, FunctionRef<bool(std::u32string_view characters)> callback);
|
||||||
|
NAZARA_CORE_API void IterateOnWideChars(std::string_view str, FunctionRef<bool(std::wstring_view characters)> callback);
|
||||||
|
|
||||||
NAZARA_CORE_API bool MatchPattern(std::string_view str, std::string_view pattern);
|
NAZARA_CORE_API bool MatchPattern(std::string_view str, std::string_view pattern);
|
||||||
|
|
||||||
|
|
@ -43,7 +44,9 @@ namespace Nz
|
||||||
|
|
||||||
NAZARA_CORE_API std::string PointerToString(const void* ptr);
|
NAZARA_CORE_API std::string PointerToString(const void* ptr);
|
||||||
|
|
||||||
inline std::string& ReplaceStr(std::string& str, std::string_view from, std::string_view to);
|
template<typename T> std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, T from, T to);
|
||||||
|
template<typename T> std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, const T* from, const T* to);
|
||||||
|
template<typename T> std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, std::basic_string_view<T> from, std::basic_string_view<T> to);
|
||||||
|
|
||||||
inline bool StartsWith(std::string_view str, std::string_view s);
|
inline bool StartsWith(std::string_view str, std::string_view s);
|
||||||
NAZARA_CORE_API bool StartsWith(std::string_view lhs, std::string_view rhs, CaseIndependent);
|
NAZARA_CORE_API bool StartsWith(std::string_view lhs, std::string_view rhs, CaseIndependent);
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,28 @@ namespace Nz
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string& ReplaceStr(std::string& str, std::string_view from, std::string_view to)
|
template<typename T>
|
||||||
|
std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, T from, T to)
|
||||||
{
|
{
|
||||||
if (str.empty())
|
std::size_t startPos = 0;
|
||||||
return str;
|
while ((startPos = str.find(from, startPos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
str[startPos] = to;
|
||||||
|
startPos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, const T* from, const T* to)
|
||||||
|
{
|
||||||
|
return ReplaceStr(str, std::basic_string_view<T>(from), std::basic_string_view<T>(to));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::basic_string<T>& ReplaceStr(std::basic_string<T>& str, std::basic_string_view<T> from, std::basic_string_view<T> to)
|
||||||
|
{
|
||||||
std::size_t startPos = 0;
|
std::size_t startPos = 0;
|
||||||
while ((startPos = str.find(from, startPos)) != std::string::npos)
|
while ((startPos = str.find(from, startPos)) != std::string::npos)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Core/Posix/PosixUtils.hpp>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <utility>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <Nazara/Core/Debug.hpp>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
int SafeClose(int fd)
|
||||||
|
{
|
||||||
|
#if defined(NAZARA_PLATFORM_LINUX) || defined(NAZARA_PLATFORM_ANDROID)
|
||||||
|
// Retrying close on Linux is dangerous
|
||||||
|
// https://android.googlesource.com/platform/bionic/+/master/docs/EINTR.md
|
||||||
|
// https://lwn.net/Articles/576478/
|
||||||
|
return ::close(fd);
|
||||||
|
#else
|
||||||
|
int ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = ::close(fd);
|
||||||
|
}
|
||||||
|
while (ret != -1 || errno == EINTR);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t SafeRead(int fd, void* buf, size_t count)
|
||||||
|
{
|
||||||
|
ssize_t ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = ::read(fd, buf, count);
|
||||||
|
}
|
||||||
|
while (ret != -1 || errno == EINTR);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t SafeWrite(int fd, const void* buf, size_t count)
|
||||||
|
{
|
||||||
|
ssize_t ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = ::write(fd, buf, count);
|
||||||
|
}
|
||||||
|
while (ret != -1 || errno == EINTR);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe::Pipe(int flags) :
|
||||||
|
m_readFd(-1),
|
||||||
|
m_writeFd(-1)
|
||||||
|
{
|
||||||
|
int fds[2];
|
||||||
|
if (::pipe2(fds, flags & O_CLOEXEC) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_readFd = fds[0];
|
||||||
|
m_writeFd = fds[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe::Pipe(Pipe&& other) noexcept :
|
||||||
|
m_readFd(std::exchange(other.m_readFd, -1)),
|
||||||
|
m_writeFd(std::exchange(other.m_writeFd, -1))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe::~Pipe()
|
||||||
|
{
|
||||||
|
if (m_readFd != -1)
|
||||||
|
{
|
||||||
|
assert(m_writeFd != -1);
|
||||||
|
SafeClose(m_readFd);
|
||||||
|
SafeClose(m_writeFd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe& Pipe::operator=(Pipe&& other) noexcept
|
||||||
|
{
|
||||||
|
m_readFd = std::exchange(other.m_readFd, -1);
|
||||||
|
m_writeFd = std::exchange(other.m_writeFd, -1);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe::operator bool() const
|
||||||
|
{
|
||||||
|
return m_readFd != -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_CORE_POSIX_POSIXUTILS_HPP
|
||||||
|
#define NAZARA_CORE_POSIX_POSIXUTILS_HPP
|
||||||
|
|
||||||
|
#include <NazaraUtils/Prerequisites.hpp>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
int SafeClose(int fd);
|
||||||
|
ssize_t SafeRead(int fd, void* buf, size_t count);
|
||||||
|
ssize_t SafeWrite(int fd, const void* buf, size_t count);
|
||||||
|
|
||||||
|
class Pipe
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Pipe(int flags = 0);
|
||||||
|
Pipe(const Pipe&) = delete;
|
||||||
|
Pipe(Pipe&& other) noexcept;
|
||||||
|
~Pipe();
|
||||||
|
|
||||||
|
inline int GetReadFd();
|
||||||
|
inline int GetWriteFd();
|
||||||
|
|
||||||
|
inline ssize_t Read(void* buf, size_t count);
|
||||||
|
inline ssize_t Write(const void* buf, size_t count);
|
||||||
|
|
||||||
|
Pipe& operator=(const Pipe&) = delete;
|
||||||
|
Pipe& operator=(Pipe&& other) noexcept;
|
||||||
|
|
||||||
|
explicit operator bool() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_readFd;
|
||||||
|
int m_writeFd;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Nazara/Core/Posix/PosixUtils.inl>
|
||||||
|
|
||||||
|
#endif // NAZARA_CORE_POSIX_POSIXUTILS_HPP
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
inline int Pipe::GetReadFd()
|
||||||
|
{
|
||||||
|
return m_readFd;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int Pipe::GetWriteFd()
|
||||||
|
{
|
||||||
|
return m_writeFd;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ssize_t Pipe::Read(void* buf, size_t count)
|
||||||
|
{
|
||||||
|
return SafeRead(m_readFd, buf, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ssize_t Pipe::Write(const void* buf, size_t count)
|
||||||
|
{
|
||||||
|
return SafeWrite(m_writeFd, buf, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Core/Posix/ProcessImpl.hpp>
|
||||||
|
#include <Nazara/Core/Error.hpp>
|
||||||
|
#include <Nazara/Core/Posix/PosixUtils.hpp>
|
||||||
|
#include <NazaraUtils/Algorithm.hpp>
|
||||||
|
#include <NazaraUtils/StackArray.hpp>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <Nazara/Core/Debug.hpp>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
Result<Pid, std::string> SpawnDetachedProcess(const std::filesystem::path& program, std::span<const std::string> arguments, const std::filesystem::path& workingDirectory)
|
||||||
|
{
|
||||||
|
struct PidOrErr
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int err;
|
||||||
|
};
|
||||||
|
|
||||||
|
Pipe pipe;
|
||||||
|
if (!pipe)
|
||||||
|
return Err("failed to create pipe: " + Error::GetLastSystemError());
|
||||||
|
|
||||||
|
// Double fork (see https://0xjet.github.io/3OHA/2022/04/11/post.html)
|
||||||
|
// We will create a child and a grand-child process, using a pipe to retrieve the grand-child pid
|
||||||
|
pid_t childPid = ::fork();
|
||||||
|
if (childPid == -1)
|
||||||
|
return Err("failed to create child: " + Error::GetLastSystemError());
|
||||||
|
|
||||||
|
if (childPid == 0)
|
||||||
|
{
|
||||||
|
// Child process
|
||||||
|
::setsid();
|
||||||
|
|
||||||
|
pid_t grandChildPid = ::vfork();
|
||||||
|
if (grandChildPid == 0)
|
||||||
|
{
|
||||||
|
// Grand-child process
|
||||||
|
StackArray<char*> argv = NazaraStackArrayNoInit(char*, arguments.size() + 2);
|
||||||
|
|
||||||
|
// It's safe to const_cast here as we're using a copy of the memory (from child) from the original process
|
||||||
|
argv[0] = const_cast<char*>(program.c_str());
|
||||||
|
for (std::size_t i = 0; i < arguments.size(); ++i)
|
||||||
|
argv[i + 1] = const_cast<char*>(arguments[i].data());
|
||||||
|
|
||||||
|
argv[argv.size() - 1] = nullptr;
|
||||||
|
|
||||||
|
char* envs[] = { nullptr };
|
||||||
|
|
||||||
|
if (!workingDirectory.empty())
|
||||||
|
::chdir(workingDirectory.c_str());
|
||||||
|
|
||||||
|
if (::execve(program.c_str(), argv.data(), envs) == -1)
|
||||||
|
{
|
||||||
|
PidOrErr err;
|
||||||
|
err.pid = -1;
|
||||||
|
err.err = errno;
|
||||||
|
|
||||||
|
pipe.Write(&err, sizeof(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (grandChildPid != -1)
|
||||||
|
{
|
||||||
|
PidOrErr err;
|
||||||
|
err.pid = grandChildPid;
|
||||||
|
|
||||||
|
pipe.Write(&err, sizeof(err));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PidOrErr err;
|
||||||
|
err.pid = -1;
|
||||||
|
err.err = errno;
|
||||||
|
|
||||||
|
pipe.Write(&err, sizeof(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exits the child process, at this point the grand-child should have started
|
||||||
|
std::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent process
|
||||||
|
|
||||||
|
// Wait for and reap the child
|
||||||
|
int childStatus;
|
||||||
|
::waitpid(childPid, &childStatus, 0);
|
||||||
|
|
||||||
|
PidOrErr pidOrErr;
|
||||||
|
if (pipe.Read(&pidOrErr, sizeof(pidOrErr) != sizeof(pidOrErr)))
|
||||||
|
{
|
||||||
|
// this should never happen
|
||||||
|
return Err("failed to create child: couldn't retrieve status from pipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pidOrErr.pid < 0)
|
||||||
|
return Err(Error::GetLastSystemError(pidOrErr.err));
|
||||||
|
|
||||||
|
return SafeCast<Pid>(pidOrErr.pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_CORE_POSIX_PROCESSIMPL_HPP
|
||||||
|
#define NAZARA_CORE_POSIX_PROCESSIMPL_HPP
|
||||||
|
|
||||||
|
#include <NazaraUtils/Prerequisites.hpp>
|
||||||
|
#include <Nazara/Core/Process.hpp>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
NAZARA_CORE_API Result<Pid, std::string> SpawnDetachedProcess(const std::filesystem::path& program, std::span<const std::string> arguments = {}, const std::filesystem::path& workingDirectory = {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NAZARA_CORE_POSIX_PROCESSIMPL_HPP
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Core/Process.hpp>
|
||||||
|
|
||||||
|
#if defined(NAZARA_PLATFORM_WINDOWS)
|
||||||
|
#include <Nazara/Core/Win32/ProcessImpl.hpp>
|
||||||
|
#elif defined(NAZARA_PLATFORM_POSIX)
|
||||||
|
#include <Nazara/Core/Posix/ProcessImpl.hpp>
|
||||||
|
#else
|
||||||
|
#error OS not handled
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <Nazara/Core/Debug.hpp>
|
||||||
|
|
||||||
|
namespace Nz
|
||||||
|
{
|
||||||
|
Result<Pid, std::string> Process::SpawnDetached(const std::filesystem::path& program, std::span<const std::string> arguments, const std::filesystem::path& workingDirectory)
|
||||||
|
{
|
||||||
|
return PlatformImpl::SpawnDetachedProcess(program, arguments, workingDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,14 @@ namespace Nz
|
||||||
template<>
|
template<>
|
||||||
struct WideConverter<2>
|
struct WideConverter<2>
|
||||||
{
|
{
|
||||||
|
static constexpr std::size_t MaxCharacterPerCodepoint = 2;
|
||||||
|
|
||||||
// UTF-16 (Windows)
|
// UTF-16 (Windows)
|
||||||
|
static std::size_t Append(char32_t codepoint, wchar_t* output)
|
||||||
|
{
|
||||||
|
return utf8::append16(codepoint, output) - output;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string From(const wchar_t* wstr, std::size_t size)
|
static std::string From(const wchar_t* wstr, std::size_t size)
|
||||||
{
|
{
|
||||||
return FromUtf16String(std::u16string_view(reinterpret_cast<const char16_t*>(wstr), size));
|
return FromUtf16String(std::u16string_view(reinterpret_cast<const char16_t*>(wstr), size));
|
||||||
|
|
@ -76,7 +83,15 @@ namespace Nz
|
||||||
template<>
|
template<>
|
||||||
struct WideConverter<4>
|
struct WideConverter<4>
|
||||||
{
|
{
|
||||||
|
static constexpr std::size_t MaxCharacterPerCodepoint = 1;
|
||||||
|
|
||||||
// UTF-32 (POSIX)
|
// UTF-32 (POSIX)
|
||||||
|
static std::size_t Append(char32_t codepoint, wchar_t* output)
|
||||||
|
{
|
||||||
|
*output = codepoint;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string From(const wchar_t* wstr, std::size_t size)
|
static std::string From(const wchar_t* wstr, std::size_t size)
|
||||||
{
|
{
|
||||||
return FromUtf32String(std::u32string_view(reinterpret_cast<const char32_t*>(wstr), size));
|
return FromUtf32String(std::u32string_view(reinterpret_cast<const char32_t*>(wstr), size));
|
||||||
|
|
@ -91,6 +106,8 @@ namespace Nz
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using NativeWideConverter = WideConverter<sizeof(wchar_t)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t ComputeCharacterCount(std::string_view str)
|
std::size_t ComputeCharacterCount(std::string_view str)
|
||||||
|
|
@ -183,7 +200,7 @@ namespace Nz
|
||||||
{
|
{
|
||||||
NAZARA_USE_ANONYMOUS_NAMESPACE
|
NAZARA_USE_ANONYMOUS_NAMESPACE
|
||||||
|
|
||||||
return WideConverter<sizeof(wchar_t)>::From(wstr.data(), wstr.size());
|
return NativeWideConverter::From(wstr.data(), wstr.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t GetCharacterPosition(std::string_view str, std::size_t characterIndex)
|
std::size_t GetCharacterPosition(std::string_view str, std::size_t characterIndex)
|
||||||
|
|
@ -283,6 +300,31 @@ namespace Nz
|
||||||
callback(std::u32string_view(&buffer[0], charCount));
|
callback(std::u32string_view(&buffer[0], charCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IterateOnWideChars(std::string_view str, FunctionRef<bool(std::wstring_view characters)> callback)
|
||||||
|
{
|
||||||
|
std::array<wchar_t, 128> buffer;
|
||||||
|
std::size_t charCount = 0;
|
||||||
|
|
||||||
|
utf8::unchecked::iterator<const char*> it(str.data());
|
||||||
|
utf8::unchecked::iterator<const char*> end(str.data() + str.size());
|
||||||
|
for (; it != end; ++it)
|
||||||
|
{
|
||||||
|
charCount += NativeWideConverter::Append(*it, &buffer[charCount]);
|
||||||
|
|
||||||
|
// Leave enough space for a full character (using up to MaxCharacterPerCodepoint)
|
||||||
|
if (buffer.size() - charCount < NativeWideConverter::MaxCharacterPerCodepoint)
|
||||||
|
{
|
||||||
|
if (!callback(std::wstring_view(&buffer[0], charCount)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
charCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charCount != 0)
|
||||||
|
callback(std::wstring_view(&buffer[0], charCount));
|
||||||
|
}
|
||||||
|
|
||||||
bool MatchPattern(std::string_view str, std::string_view pattern)
|
bool MatchPattern(std::string_view str, std::string_view pattern)
|
||||||
{
|
{
|
||||||
if (str.empty() || pattern.empty())
|
if (str.empty() || pattern.empty())
|
||||||
|
|
@ -514,7 +556,7 @@ namespace Nz
|
||||||
{
|
{
|
||||||
NAZARA_USE_ANONYMOUS_NAMESPACE
|
NAZARA_USE_ANONYMOUS_NAMESPACE
|
||||||
|
|
||||||
return WideConverter<sizeof(wchar_t)>::To(str);
|
return NativeWideConverter::To(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view TrimLeft(std::string_view str)
|
std::string_view TrimLeft(std::string_view str)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Core/Win32/ProcessImpl.hpp>
|
||||||
|
#include <Nazara/Core/Error.hpp>
|
||||||
|
#include <Nazara/Core/StringExt.hpp>
|
||||||
|
#include <Nazara/Core/Win32/Win32Utils.hpp>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <Nazara/Core/Debug.hpp>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
std::wstring BuildCommandLine(const std::filesystem::path& program, std::span<const std::string> arguments)
|
||||||
|
{
|
||||||
|
std::wstring commandLine;
|
||||||
|
|
||||||
|
auto AddProgramName = [&commandLine](const WidePathHolder& program)
|
||||||
|
{
|
||||||
|
if (!program.starts_with(L'"') && !program.ends_with(L'"') && program.find(L' ') != program.npos)
|
||||||
|
{
|
||||||
|
commandLine += L'"';
|
||||||
|
commandLine += program;
|
||||||
|
commandLine += L'"';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
commandLine += program;
|
||||||
|
|
||||||
|
ReplaceStr(commandLine, L'/', L'\\');
|
||||||
|
commandLine += L' ';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a lambda to keep WidePathHolder alive
|
||||||
|
AddProgramName(PathToWideTemp(program));
|
||||||
|
|
||||||
|
for (std::string_view arg : arguments)
|
||||||
|
{
|
||||||
|
commandLine += L' ';
|
||||||
|
|
||||||
|
if (arg.empty())
|
||||||
|
{
|
||||||
|
// Empty argument (ensures quotes)
|
||||||
|
commandLine += LR"("")";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Characters requiring quotes from cmd /?
|
||||||
|
constexpr std::string_view specialChars = "\t &()[]{}^=;!'+,`~%|<>";
|
||||||
|
|
||||||
|
bool requiresQuote = arg.find_first_of(specialChars) != arg.npos;
|
||||||
|
|
||||||
|
if (requiresQuote)
|
||||||
|
commandLine += L'"';
|
||||||
|
|
||||||
|
std::size_t backslashCount = 0;
|
||||||
|
IterateOnWideChars(arg, [&](std::wstring_view characters)
|
||||||
|
{
|
||||||
|
for (wchar_t character : characters)
|
||||||
|
{
|
||||||
|
if (character != L'\\')
|
||||||
|
{
|
||||||
|
// Escape quotes and double their preceding backslashes ('\\\"' => '\\\\\\\"')
|
||||||
|
if (character == L'"')
|
||||||
|
commandLine.append(backslashCount + 1, L'\\');
|
||||||
|
|
||||||
|
backslashCount = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
backslashCount++;
|
||||||
|
|
||||||
|
commandLine.push_back(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requiresQuote)
|
||||||
|
{
|
||||||
|
commandLine.append(backslashCount, L'\\');
|
||||||
|
commandLine += L'"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Pid, std::string> SpawnDetachedProcess(const std::filesystem::path& program, std::span<const std::string> arguments, const std::filesystem::path& workingDirectory)
|
||||||
|
{
|
||||||
|
DWORD creationFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;
|
||||||
|
|
||||||
|
std::wstring commandLine = BuildCommandLine(program, arguments);
|
||||||
|
|
||||||
|
STARTUPINFOW startupInfo = {
|
||||||
|
.cb = sizeof(startupInfo),
|
||||||
|
.dwX = DWORD(CW_USEDEFAULT),
|
||||||
|
.dwY = DWORD(CW_USEDEFAULT),
|
||||||
|
.dwXSize = DWORD(CW_USEDEFAULT),
|
||||||
|
.dwYSize = DWORD(CW_USEDEFAULT)
|
||||||
|
};
|
||||||
|
|
||||||
|
PROCESS_INFORMATION processInfo;
|
||||||
|
BOOL success = CreateProcessW(
|
||||||
|
nullptr, // Application name
|
||||||
|
commandLine.data(), // Command line
|
||||||
|
nullptr, // Process attributes
|
||||||
|
nullptr, // Thread attributes
|
||||||
|
false, // Inherit handles
|
||||||
|
creationFlags, // Creation flags
|
||||||
|
nullptr, // Environment
|
||||||
|
(!workingDirectory.empty()) ? PathToWideTemp(workingDirectory).data() : nullptr, // Current directory
|
||||||
|
&startupInfo, // Startup info
|
||||||
|
&processInfo // Process information
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return Err(Error::GetLastSystemError());
|
||||||
|
|
||||||
|
CloseHandle(processInfo.hThread);
|
||||||
|
CloseHandle(processInfo.hProcess);
|
||||||
|
|
||||||
|
return Pid(processInfo.dwProcessId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <Nazara/Core/AntiWindows.hpp>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
||||||
|
// This file is part of the "Nazara Engine - Core module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_CORE_WIN32_PROCESSIMPL_HPP
|
||||||
|
#define NAZARA_CORE_WIN32_PROCESSIMPL_HPP
|
||||||
|
|
||||||
|
#include <NazaraUtils/Prerequisites.hpp>
|
||||||
|
#include <Nazara/Core/Process.hpp>
|
||||||
|
|
||||||
|
namespace Nz::PlatformImpl
|
||||||
|
{
|
||||||
|
NAZARA_CORE_API Result<Pid, std::string> SpawnDetachedProcess(const std::filesystem::path& program, std::span<const std::string> arguments = {}, const std::filesystem::path& workingDirectory = {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NAZARA_CORE_WIN32_PROCESSIMPL_HPP
|
||||||
Loading…
Reference in New Issue