8c55e5c288
Lists every session pid file found under
$XDG_STATE_HOME/slocker-lite/run/, showing pid, container name, and
status (running or exited). Status is determined the same way any
external tool could check it: a non-blocking exclusive flock() on the
file that succeeds means it's actually stale (nothing holds it), so
the session is reported as exited in that case; the lock is always
released again immediately, never left held by the check itself.
list_sessions() (pid_file.{h,cpp}) reads the real pid from each file's
own contents rather than parsing it out of the filename, which would
be ambiguous for container names that themselves contain '-'.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
156 lines
4.9 KiB
C++
156 lines
4.9 KiB
C++
// Copyright (C) 2026 Viorel Munteanu
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#include "pid_file.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/file.h>
|
|
#include <unistd.h>
|
|
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <system_error>
|
|
|
|
#include <fmt/core.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
namespace {
|
|
|
|
std::string sanitize_for_filename(std::string_view name) {
|
|
std::string result;
|
|
result.reserve(name.size());
|
|
for (char c : name) {
|
|
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.') {
|
|
result += c;
|
|
} else {
|
|
result += '_';
|
|
}
|
|
}
|
|
return result.empty() ? "container" : result;
|
|
}
|
|
|
|
std::filesystem::path session_run_dir() {
|
|
const char* xdg_state_home = std::getenv("XDG_STATE_HOME");
|
|
std::filesystem::path state_home;
|
|
if (xdg_state_home && *xdg_state_home) {
|
|
state_home = xdg_state_home;
|
|
} else {
|
|
const char* home = std::getenv("HOME");
|
|
state_home = std::filesystem::path(home ? home : "") / ".local" / "state";
|
|
}
|
|
return state_home / "slocker-lite" / "run";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::filesystem::path session_pid_file_path(std::string_view container_name, pid_t pid) {
|
|
return session_run_dir() / fmt::format("{}-{}", sanitize_for_filename(container_name), pid);
|
|
}
|
|
|
|
std::optional<SessionLock> create_session_lock(std::string_view container_name, pid_t pid) {
|
|
auto path = session_pid_file_path(container_name, pid);
|
|
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(path.parent_path(), ec);
|
|
if (ec) {
|
|
spdlog::warn("failed to create session state directory {}: {}", path.parent_path().string(),
|
|
ec.message());
|
|
return std::nullopt;
|
|
}
|
|
|
|
int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);
|
|
if (fd < 0) {
|
|
spdlog::warn("failed to create session pid file {}: {}", path.string(), strerror(errno));
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
|
spdlog::warn("failed to lock session pid file {}: {}", path.string(), strerror(errno));
|
|
close(fd);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string contents = fmt::format("{}\n", pid);
|
|
if (write(fd, contents.data(), contents.size()) < 0) {
|
|
spdlog::warn("failed to write session pid file {}: {}", path.string(), strerror(errno));
|
|
}
|
|
|
|
return SessionLock{path, fd};
|
|
}
|
|
|
|
void release_session_lock(const SessionLock& lock) {
|
|
if (lock.fd >= 0) {
|
|
close(lock.fd);
|
|
}
|
|
std::error_code ec;
|
|
std::filesystem::remove(lock.path, ec);
|
|
if (ec) {
|
|
spdlog::warn("failed to remove session pid file {}: {}", lock.path.string(), ec.message());
|
|
}
|
|
}
|
|
|
|
std::vector<SessionInfo> list_sessions() {
|
|
std::vector<SessionInfo> sessions;
|
|
|
|
auto dir = session_run_dir();
|
|
std::error_code dir_ec;
|
|
if (!std::filesystem::is_directory(dir, dir_ec)) {
|
|
return sessions;
|
|
}
|
|
|
|
std::error_code it_ec;
|
|
for (auto it = std::filesystem::directory_iterator(dir, it_ec);
|
|
!it_ec && it != std::filesystem::directory_iterator(); it.increment(it_ec)) {
|
|
const auto& path = it->path();
|
|
|
|
int fd = open(path.c_str(), O_RDWR);
|
|
if (fd < 0) {
|
|
spdlog::debug("failed to open session file {}: {}", path.string(), strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
char buf[32] = {};
|
|
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
|
pid_t pid = n > 0 ? static_cast<pid_t>(std::atoi(buf)) : 0;
|
|
|
|
bool running = true;
|
|
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
|
|
running = false;
|
|
flock(fd, LOCK_UN);
|
|
}
|
|
close(fd);
|
|
|
|
if (pid <= 0) {
|
|
spdlog::debug("skipping malformed session pid file {}", path.string());
|
|
continue;
|
|
}
|
|
|
|
std::string filename = path.filename().string();
|
|
std::string suffix = fmt::format("-{}", pid);
|
|
std::string container_name =
|
|
filename.size() > suffix.size() &&
|
|
filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0
|
|
? filename.substr(0, filename.size() - suffix.size())
|
|
: filename;
|
|
|
|
sessions.push_back(SessionInfo{pid, container_name, running});
|
|
}
|
|
|
|
return sessions;
|
|
}
|