Compare commits
3 commits
7b59748ff7
...
2be5aa17cc
| Author | SHA1 | Date | |
|---|---|---|---|
| 2be5aa17cc | |||
| 8b0a14d8a8 | |||
| 4200f85bd2 |
3 changed files with 117 additions and 5 deletions
18
DEVLOG.md
18
DEVLOG.md
|
|
@ -1,5 +1,23 @@
|
|||
# Tux-Dock Development Log
|
||||
|
||||
## 0.1.2-beta
|
||||
|
||||
This release adds signal trapping to prevent state corruption during long-running Docker operations.
|
||||
|
||||
### Reliability
|
||||
|
||||
- Added SIGINT (Ctrl+C) signal trapping during busy operations to prevent premature exit and socket corruption
|
||||
- Added SIGTSTP (Ctrl+Z) signal trapping during busy operations to prevent suspension mid-operation
|
||||
- Signals chain to previous handlers when not busy, preserving normal exit and suspend behavior
|
||||
- Trap re-installs after terminal I/O restoration cycles to maintain protection through Attach Shell sessions
|
||||
- Used SA_RESTART flag to prevent EINTR on blocking socket reads during signal delivery
|
||||
|
||||
### TUI
|
||||
|
||||
- Added visual feedback in busy modal when signals are trapped
|
||||
- Shows "Ctrl+C ignored: operation in progress" when SIGINT is trapped
|
||||
- Shows "Ctrl+Z ignored: operation in progress" when SIGTSTP is trapped
|
||||
|
||||
## 0.1.1-beta
|
||||
|
||||
This release establishes the first beta-quality Docker integration and TUI workflow.
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ ctest --test-dir build --output-on-failure
|
|||
|
||||
## About / Version
|
||||
|
||||
- Version: `0.1.1-beta`
|
||||
- Version: `0.1.2-beta`
|
||||
- Created by: `markmental`
|
||||
- GitHub: https://github.com/MARKMENTAL/tuxdock
|
||||
- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock
|
||||
|
|
|
|||
102
main.cpp
102
main.cpp
|
|
@ -2,6 +2,7 @@
|
|||
#include "src/operation_state.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
|
@ -19,6 +20,88 @@
|
|||
#include <ftxui/component/screen_interactive.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
volatile std::sig_atomic_t g_sigint_busy = 0;
|
||||
volatile std::sig_atomic_t g_sigint_trapped = 0;
|
||||
volatile std::sig_atomic_t g_sigtstp_trapped = 0;
|
||||
bool g_sigint_trap_installed = false;
|
||||
bool g_sigtstp_trap_installed = false;
|
||||
struct sigaction g_sigint_previous {};
|
||||
struct sigaction g_sigtstp_previous {};
|
||||
|
||||
void TuxDockHandleSigint(int signal_number) {
|
||||
if (g_sigint_busy != 0) {
|
||||
++g_sigint_trapped;
|
||||
return;
|
||||
}
|
||||
if (g_sigint_trap_installed) {
|
||||
const auto previous_handler = g_sigint_previous.sa_handler;
|
||||
if (previous_handler != nullptr && previous_handler != SIG_DFL) {
|
||||
if (previous_handler != SIG_IGN) previous_handler(signal_number);
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::signal(signal_number, SIG_DFL);
|
||||
std::raise(signal_number);
|
||||
}
|
||||
|
||||
void TuxDockHandleSigtstp(int signal_number) {
|
||||
if (g_sigint_busy != 0) {
|
||||
++g_sigtstp_trapped;
|
||||
return;
|
||||
}
|
||||
if (g_sigtstp_trap_installed) {
|
||||
const auto previous_handler = g_sigtstp_previous.sa_handler;
|
||||
if (previous_handler != nullptr && previous_handler != SIG_DFL) {
|
||||
if (previous_handler != SIG_IGN) previous_handler(signal_number);
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::signal(signal_number, SIG_DFL);
|
||||
std::raise(signal_number);
|
||||
}
|
||||
|
||||
void InstallSignalTraps() {
|
||||
g_sigint_trapped = 0;
|
||||
g_sigtstp_trapped = 0;
|
||||
g_sigint_busy = 1;
|
||||
if (!g_sigint_trap_installed) {
|
||||
struct sigaction trap {};
|
||||
sigemptyset(&trap.sa_mask);
|
||||
trap.sa_flags = SA_RESTART;
|
||||
trap.sa_handler = TuxDockHandleSigint;
|
||||
if (sigaction(SIGINT, &trap, &g_sigint_previous) == 0) {
|
||||
g_sigint_trap_installed = true;
|
||||
}
|
||||
}
|
||||
if (!g_sigtstp_trap_installed) {
|
||||
struct sigaction trap {};
|
||||
sigemptyset(&trap.sa_mask);
|
||||
trap.sa_flags = SA_RESTART;
|
||||
trap.sa_handler = TuxDockHandleSigtstp;
|
||||
if (sigaction(SIGTSTP, &trap, &g_sigtstp_previous) == 0) {
|
||||
g_sigtstp_trap_installed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveSignalTraps() {
|
||||
g_sigint_busy = 0;
|
||||
g_sigint_trapped = 0;
|
||||
g_sigtstp_trapped = 0;
|
||||
if (g_sigint_trap_installed) {
|
||||
sigaction(SIGINT, &g_sigint_previous, nullptr);
|
||||
g_sigint_trap_installed = false;
|
||||
}
|
||||
if (g_sigtstp_trap_installed) {
|
||||
sigaction(SIGTSTP, &g_sigtstp_previous, nullptr);
|
||||
g_sigtstp_trap_installed = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TuxDockApp {
|
||||
public:
|
||||
int Run();
|
||||
|
|
@ -377,6 +460,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title,
|
|||
operation_state_.begin(title, message);
|
||||
modal_mode_ = ModalMode::Busy;
|
||||
spinner_frame_ = 0;
|
||||
InstallSignalTraps();
|
||||
auto* active = screen_;
|
||||
StartSpinner();
|
||||
if (active) active->PostEvent(ftxui::Event::Custom);
|
||||
|
|
@ -384,6 +468,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title,
|
|||
const auto message = action();
|
||||
if (active && ftxui::ScreenInteractive::Active() == active) {
|
||||
active->Post([this, message] {
|
||||
RemoveSignalTraps();
|
||||
operation_state_.complete(message);
|
||||
StopSpinner();
|
||||
modal_mode_ = ModalMode::None;
|
||||
|
|
@ -397,6 +482,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title,
|
|||
void TuxDockApp::BeginStopOperation(const std::string& id) {
|
||||
operation_state_.begin("Stopping container", "Stopping and refreshing state...");
|
||||
modal_mode_ = ModalMode::Busy;
|
||||
InstallSignalTraps();
|
||||
auto* active = screen_;
|
||||
spinner_frame_ = 0;
|
||||
StartSpinner();
|
||||
|
|
@ -407,6 +493,7 @@ void TuxDockApp::BeginStopOperation(const std::string& id) {
|
|||
auto images = docker_.getImageList();
|
||||
if (!active || ftxui::ScreenInteractive::Active() != active) return;
|
||||
active->Post([this, stopped, message, containers = std::move(containers), images = std::move(images)]() mutable {
|
||||
RemoveSignalTraps();
|
||||
ApplyRefreshResults(std::move(containers), std::move(images));
|
||||
operation_state_.complete(message);
|
||||
StopSpinner();
|
||||
|
|
@ -510,7 +597,7 @@ void TuxDockApp::PromptNextPort(
|
|||
}
|
||||
|
||||
OpenInput(
|
||||
"Port Mapping", "Enter mapping #" + std::to_string(index + 1),
|
||||
"Port Mapping", "Enter host-to-container port mapping #" + std::to_string(index + 1) + ". e.g: 8080:80.",
|
||||
[this, context, index](bool ok, const std::string& value) {
|
||||
if (!ok) return;
|
||||
if (!IsValidPortMapping(value)) {
|
||||
|
|
@ -612,7 +699,7 @@ void TuxDockApp::ActionExecDetachedCommand() {
|
|||
}
|
||||
|
||||
void TuxDockApp::ActionAbout() {
|
||||
OpenMessage("About Tux-Dock", "Tux-Dock 0.1.1-beta | Created by markmental");
|
||||
OpenMessage("About Tux-Dock", "Tux-Dock 0.1.2-beta | Created by markmental");
|
||||
}
|
||||
|
||||
void TuxDockApp::ExecuteSelectedAction() {
|
||||
|
|
@ -708,11 +795,18 @@ ftxui::Element TuxDockApp::RenderModal() const {
|
|||
footer = text("Up/Down: choose Enter: confirm Esc: cancel") | dim;
|
||||
} else if (modal_mode_ == ModalMode::Busy) {
|
||||
static const std::string spinner = "|/-\\";
|
||||
body = vbox(Elements{
|
||||
Elements busy_elements = Elements{
|
||||
text(operation_state_.message()),
|
||||
separator(),
|
||||
text(std::string(" ") + spinner[spinner_frame_ % spinner.size()]) | bold,
|
||||
});
|
||||
};
|
||||
if (g_sigint_trapped > 0) {
|
||||
busy_elements.push_back(text("Ctrl+C ignored: operation in progress") | dim);
|
||||
}
|
||||
if (g_sigtstp_trapped > 0) {
|
||||
busy_elements.push_back(text("Ctrl+Z ignored: operation in progress") | dim);
|
||||
}
|
||||
body = vbox(std::move(busy_elements));
|
||||
footer = text("Please wait; input is disabled") | dim;
|
||||
} else {
|
||||
body = (modal_content_ ? std::move(modal_content_) : paragraph(modal_text_)) |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue