exec: Spawn processes as direct children (#11735)
* exec: Spawn processes as direct children Spawn processes as children rather than grandchildren. This way, spawned processes may track Hyprland's state by watching their parent, either directly or indirectly (e.g., Linux's `PR_SET_PDEATH_SIG`). Fixes #11728 * tests/exec: Add the test on process spawning Add a test that ensures that: - A spawned process remains a direct child of Hyprland; - Upon termination, the process does not become a zombie.
This commit is contained in:
parent
70a7047ee1
commit
29b103c376
3 changed files with 95 additions and 38 deletions
64
hyprtester/src/tests/main/exec.cpp
Normal file
64
hyprtester/src/tests/main/exec.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
static std::string execAndGet(const std::string& cmd) {
|
||||
CProcess proc("/bin/sh", {"-c", cmd});
|
||||
|
||||
if (!proc.runSync()) {
|
||||
return "error";
|
||||
}
|
||||
|
||||
return proc.stdOut();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing process spawning", Colors::GREEN);
|
||||
|
||||
// Note: POSIX sleep does not support fractional seconds, so
|
||||
// can't sleep for less than 1 second.
|
||||
OK(getFromSocket("/dispatch exec sleep 1"));
|
||||
|
||||
// Ensure that sleep is our child
|
||||
const std::string sleepPidS = execAndGet("pgrep sleep");
|
||||
pid_t sleepPid;
|
||||
try {
|
||||
sleepPid = std::stoull(sleepPidS);
|
||||
} catch (...) {
|
||||
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sleepParentComm = execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\"");
|
||||
NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
|
||||
EXPECT_CONTAINS(sleepParentComm, "Hyprland");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
// Ensure that sleep did not become a zombie
|
||||
EXPECT(Tests::processAlive(sleepPid), false);
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
14
src/main.cpp
14
src/main.cpp
|
|
@ -5,6 +5,7 @@
|
|||
#include "init/initHelpers.hpp"
|
||||
#include "debug/HyprCtl.hpp"
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
|
|
@ -35,6 +36,17 @@ static void help() {
|
|||
--version -v - Print this binary's version)");
|
||||
}
|
||||
|
||||
static void reapZombieChildrenAutomatically() {
|
||||
struct sigaction act;
|
||||
act.sa_handler = SIG_DFL;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_NOCLDWAIT;
|
||||
#ifdef SA_RESTORER
|
||||
act.sa_restorer = NULL;
|
||||
#endif
|
||||
sigaction(SIGCHLD, &act, nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
if (!getenv("XDG_RUNTIME_DIR"))
|
||||
|
|
@ -183,6 +195,8 @@ int main(int argc, char** argv) {
|
|||
if (!envEnabled("HYPRLAND_NO_RT"))
|
||||
NInit::gainRealTime();
|
||||
|
||||
reapZombieChildrenAutomatically();
|
||||
|
||||
Debug::log(LOG, "Hyprland init finished.");
|
||||
|
||||
// If all's good to go, start.
|
||||
|
|
|
|||
|
|
@ -949,17 +949,9 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo
|
|||
|
||||
const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace);
|
||||
|
||||
int socket[2];
|
||||
if (pipe(socket) != 0) {
|
||||
Debug::log(LOG, "Unable to create pipe for fork");
|
||||
}
|
||||
|
||||
CFileDescriptor pipeSock[2] = {CFileDescriptor{socket[0]}, CFileDescriptor{socket[1]}};
|
||||
|
||||
pid_t child, grandchild;
|
||||
child = fork();
|
||||
pid_t child = fork();
|
||||
if (child < 0) {
|
||||
Debug::log(LOG, "Fail to create the first fork");
|
||||
Debug::log(LOG, "Fail to fork");
|
||||
return 0;
|
||||
}
|
||||
if (child == 0) {
|
||||
|
|
@ -970,41 +962,28 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo
|
|||
sigemptyset(&set);
|
||||
sigprocmask(SIG_SETMASK, &set, nullptr);
|
||||
|
||||
grandchild = fork();
|
||||
if (grandchild == 0) {
|
||||
// run in grandchild
|
||||
for (auto const& e : HLENV) {
|
||||
setenv(e.first.c_str(), e.second.c_str(), 1);
|
||||
}
|
||||
setenv("WAYLAND_DISPLAY", g_pCompositor->m_wlDisplaySocket.c_str(), 1);
|
||||
|
||||
int devnull = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
|
||||
execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr);
|
||||
// exit grandchild
|
||||
_exit(0);
|
||||
for (auto const& e : HLENV) {
|
||||
setenv(e.first.c_str(), e.second.c_str(), 1);
|
||||
}
|
||||
write(pipeSock[1].get(), &grandchild, sizeof(grandchild));
|
||||
setenv("WAYLAND_DISPLAY", g_pCompositor->m_wlDisplaySocket.c_str(), 1);
|
||||
|
||||
int devnull = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
|
||||
execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr);
|
||||
|
||||
// exit child
|
||||
_exit(0);
|
||||
}
|
||||
// run in parent
|
||||
read(pipeSock[0].get(), &grandchild, sizeof(grandchild));
|
||||
// clear child and leave grandchild to init
|
||||
waitpid(child, nullptr, 0);
|
||||
if (grandchild < 0) {
|
||||
Debug::log(LOG, "Fail to create the second fork");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Process Created with pid {}", grandchild);
|
||||
Debug::log(LOG, "Process Created with pid {}", child);
|
||||
|
||||
return grandchild;
|
||||
return child;
|
||||
}
|
||||
|
||||
SDispatchResult CKeybindManager::killActive(std::string args) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue