Core/Posix: Rework implementation (using posix_spawn)

This commit is contained in:
SirLynix 2024-01-23 15:10:27 +01:00 committed by Jérôme Leclercq
parent b63c9fcc49
commit a02990beff
2 changed files with 24 additions and 38 deletions

View File

@ -24,7 +24,7 @@ namespace Nz::PlatformImpl
{
ret = ::close(fd);
}
while (ret != -1 || errno == EINTR);
while (ret == -1 && errno == EINTR);
return ret;
#endif
@ -37,7 +37,7 @@ namespace Nz::PlatformImpl
{
ret = ::read(fd, buf, count);
}
while (ret != -1 || errno == EINTR);
while (ret == -1 && errno == EINTR);
return ret;
}
@ -49,7 +49,7 @@ namespace Nz::PlatformImpl
{
ret = ::write(fd, buf, count);
}
while (ret != -1 || errno == EINTR);
while (ret == -1 && errno == EINTR);
return ret;
}

View File

@ -9,6 +9,7 @@
#include <NazaraUtils/StackArray.hpp>
#include <cerrno>
#include <cstdlib>
#include <spawn.h>
#include <unistd.h>
#include <sys/wait.h>
#include <Nazara/Core/Debug.hpp>
@ -21,7 +22,7 @@ namespace Nz::PlatformImpl
return Ok(true);
if (errno == ESRCH)
return OK(false);
return Ok(false);
return Err(Error::GetLastSystemError());
}
@ -43,9 +44,10 @@ namespace Nz::PlatformImpl
if (!pipe)
return Err("failed to create pipe: " + Error::GetLastSystemError());
std::filesystem::path fullpath = std::filesystem::absolute(program);
// 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
// TODO: Maybe use vfork for the child process too?
// TODO: Use posix_spawn if possible instead
pid_t childPid = ::fork();
if (childPid == -1)
@ -71,40 +73,24 @@ namespace Nz::PlatformImpl
}
}
pid_t grandChildPid = ::vfork();
if (grandChildPid == 0)
StackArray<char*> args = 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
args[0] = const_cast<char*>(program.c_str());
for (std::size_t i = 0; i < arguments.size(); ++i)
args[i + 1] = const_cast<char*>(arguments[i].data());
args.back() = nullptr;
char* envs[] = { nullptr };
pid_t grandChildPid;
if (posix_spawn(&grandChildPid, fullpath.c_str(), nullptr, nullptr, args.data(), envs) == 0)
{
// Grand-child process
StackArray<char*> argv = NazaraStackArrayNoInit(char*, arguments.size() + 2);
PidOrErr pid;
pid.pid = grandChildPid;
// 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 };
::execve(program.c_str(), argv.data(), envs);
// If we get here, execve failed
// Remember we share the memory of our parent (vfork) so we need to exit using _exit() to avoid calling the parent exit handlers
PidOrErr err;
err.pid = -1;
err.err = errno;
pipe.Write(&err, sizeof(err));
_exit(1);
}
else if (grandChildPid != -1)
{
PidOrErr err;
err.pid = grandChildPid;
pipe.Write(&err, sizeof(err));
pipe.Write(&pid, sizeof(pid));
// Exits the child process, at this point the grand-child should have started
std::exit(0);
@ -131,7 +117,7 @@ namespace Nz::PlatformImpl
::waitpid(childPid, &childStatus, 0);
PidOrErr pidOrErr;
if (pipe.Read(&pidOrErr, sizeof(pidOrErr) != sizeof(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");