87 lines
3.8 KiB
C++
87 lines
3.8 KiB
C++
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// Discovery across processes: an app's Registration writes its record
|
||
|
|
// (readable by its user only) and holds the lock while it lives, and
|
||
|
|
// ListInstances lists it. An app killed with SIGKILL, which never runs its
|
||
|
|
// destructor, leaves its files behind with the lock free, and the next
|
||
|
|
// listing removes them instead of offering a dead instance; one that ends
|
||
|
|
// normally removes its own. Pids cannot tell this apart: every `flatpak run`
|
||
|
|
// sandbox has its own pid namespace. Also checks the generated build info.
|
||
|
|
#include <signal.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
#include <sys/wait.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
import std;
|
||
|
|
import Crafter.Debug.Core;
|
||
|
|
using namespace Crafter::Debug;
|
||
|
|
namespace fs = std::filesystem;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
std::int32_t failures = 0;
|
||
|
|
|
||
|
|
void Check(bool ok, std::string_view what) {
|
||
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||
|
|
if (!ok) ++failures;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint32_t Mode(const fs::path& path) {
|
||
|
|
struct stat info {};
|
||
|
|
::stat(path.c_str(), &info);
|
||
|
|
return info.st_mode & 0777;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
const fs::path dir = fs::temp_directory_path() / "crafter-debug-ShouldDiscoverLiveInstances";
|
||
|
|
fs::remove_all(dir);
|
||
|
|
::setenv("CRAFTER_DEBUG_DIR", dir.c_str(), 1);
|
||
|
|
|
||
|
|
std::optional<Registration> one;
|
||
|
|
one.emplace(InstanceRecord{ .name = "one", .port = 4100, .token = "secret", .protocol = protocolVersion });
|
||
|
|
const std::vector<Instance> first = ListInstances();
|
||
|
|
Check(first.size() == 1 && first[0].record.name == "one" && first[0].record.port == 4100 && first[0].record.token == "secret", "a registered instance is listed");
|
||
|
|
Check(first.size() == 1 && first[0].record.pid == ::getpid() && !first[0].record.instanceId.empty() && first[0].record.exe.ends_with("ShouldDiscoverLiveInstances"),
|
||
|
|
"the record carries the pid, an instance id and the executable");
|
||
|
|
Check(Mode(dir) == 0700 && first.size() == 1 && Mode(first[0].recordPath) == 0600, "the directory and record are its user's only");
|
||
|
|
if (fs::exists("/.flatpak-info")) {
|
||
|
|
Check(!one->Record().flatpakInstance.empty() && one->Record().hostInitPid > 0, "inside a Flatpak, the sandbox's instance id and host init pid are recorded");
|
||
|
|
}
|
||
|
|
|
||
|
|
std::array<std::int32_t, 2> ready{};
|
||
|
|
::pipe(ready.data());
|
||
|
|
const ::pid_t child = ::fork();
|
||
|
|
if (child == 0) {
|
||
|
|
::close(ready[0]);
|
||
|
|
Registration two(InstanceRecord{ .name = "two" });
|
||
|
|
const char signal = 'r';
|
||
|
|
(void)::write(ready[1], &signal, 1);
|
||
|
|
while (true) ::pause();
|
||
|
|
}
|
||
|
|
::close(ready[1]);
|
||
|
|
char signal = 0;
|
||
|
|
(void)::read(ready[0], &signal, 1);
|
||
|
|
const std::vector<Instance> both = ListInstances();
|
||
|
|
Check(both.size() == 2, std::format("two live instances are listed ({})", both.size()));
|
||
|
|
fs::path twoRecord;
|
||
|
|
for (const Instance& instance : both) {
|
||
|
|
if (instance.record.name == "two") twoRecord = instance.recordPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
::kill(child, SIGKILL);
|
||
|
|
::waitpid(child, nullptr, 0);
|
||
|
|
Check(fs::exists(twoRecord), "a killed instance leaves its record behind");
|
||
|
|
const std::vector<Instance> afterKill = ListInstances();
|
||
|
|
Check(afterKill.size() == 1 && afterKill[0].record.name == "one", "the next listing leaves out the killed instance");
|
||
|
|
Check(!fs::exists(twoRecord) && !fs::exists(fs::path(twoRecord).replace_extension(".lock")), "and removes its files");
|
||
|
|
|
||
|
|
one.reset();
|
||
|
|
Check(ListInstances().empty() && fs::is_empty(dir), "an instance that ends removes its own files");
|
||
|
|
|
||
|
|
Check(Detail::SourceId().size() == 16, std::format("the build has a source id ({})", Detail::SourceId()));
|
||
|
|
Check(Detail::ClientPath().ends_with("/crafter-debug"), std::format("the build knows its client ({})", Detail::ClientPath()));
|
||
|
|
|
||
|
|
fs::remove_all(dir);
|
||
|
|
return failures == 0 ? 0 : 1;
|
||
|
|
}
|