Track running -r/--run sessions with a locked PID file

Each bwrap session is now recorded under
$XDG_STATE_HOME/slocker-lite/run/<container-name>-<pid> (falling back
to $HOME/.local/state/...), holding an exclusive advisory flock() for
as long as it's running -- so any tool can tell a stale leftover file
apart from a live session by attempting the same non-blocking flock().
The file is removed once the run ends, on every exit path including a
forwarded Ctrl-C.

run_process_foreground() gained an optional on_start(pid) callback,
fired right after fork() succeeds -- the only point the real bwrap pid
is knowable, since exec() (including nsenter handing off to bwrap)
never changes it. run_bwrap() uses this to create/release the session
lock. The container name comes from read_image_ref(), promoted from a
list_oci_images()-only helper to public API in oci_image.h so
run_container() can reuse the same name/tag derivation for a single
image tar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-22 08:56:12 +00:00
parent 9877ffe7f1
commit 23f380e180
12 changed files with 319 additions and 67 deletions
+105
View File
@@ -0,0 +1,105 @@
// 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());
}
}