From 4200f85bd27fcf1f70a893fc64b204998c75d1a1 Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Sat, 22 Aug 2026 16:58:51 -0400 Subject: [PATCH 1/3] Update to make the port mapping prompt clearer in Create Container, trap ctrl+c SIGINT when doing container operations/in busy state --- main.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index a732178..a98081f 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include "src/operation_state.hpp" #include +#include #include #include #include @@ -19,6 +20,53 @@ #include #include +namespace { + +volatile std::sig_atomic_t g_sigint_busy = 0; +volatile std::sig_atomic_t g_sigint_trapped = 0; +bool g_sigint_trap_installed = false; +struct sigaction g_sigint_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 InstallSigintTrap() { + g_sigint_trapped = 0; + g_sigint_busy = 1; + if (g_sigint_trap_installed) return; + 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; + } +} + +void RemoveSigintTrap() { + g_sigint_busy = 0; + g_sigint_trapped = 0; + if (g_sigint_trap_installed) { + sigaction(SIGINT, &g_sigint_previous, nullptr); + g_sigint_trap_installed = false; + } +} + +} + class TuxDockApp { public: int Run(); @@ -377,6 +425,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title, operation_state_.begin(title, message); modal_mode_ = ModalMode::Busy; spinner_frame_ = 0; + InstallSigintTrap(); auto* active = screen_; StartSpinner(); if (active) active->PostEvent(ftxui::Event::Custom); @@ -384,6 +433,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title, const auto message = action(); if (active && ftxui::ScreenInteractive::Active() == active) { active->Post([this, message] { + RemoveSigintTrap(); operation_state_.complete(message); StopSpinner(); modal_mode_ = ModalMode::None; @@ -397,6 +447,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; + InstallSigintTrap(); auto* active = screen_; spinner_frame_ = 0; StartSpinner(); @@ -407,6 +458,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 { + RemoveSigintTrap(); ApplyRefreshResults(std::move(containers), std::move(images)); operation_state_.complete(message); StopSpinner(); @@ -510,7 +562,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)) { @@ -708,11 +760,15 @@ 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); + } + 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_)) | From 8b0a14d8a8f8a93b472c674f2402039672cc1e88 Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Sat, 22 Aug 2026 17:22:16 -0400 Subject: [PATCH 2/3] Add handlers for blocking CTRL+Z in busy operations, 0.1.2-beta complete --- README.md | 2 +- main.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e581156..009f04c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/main.cpp b/main.cpp index a98081f..66de887 100644 --- a/main.cpp +++ b/main.cpp @@ -24,8 +24,11 @@ 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) { @@ -43,26 +46,58 @@ void TuxDockHandleSigint(int signal_number) { std::raise(signal_number); } -void InstallSigintTrap() { +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) return; - 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_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 RemoveSigintTrap() { +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; + } } } @@ -425,7 +460,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title, operation_state_.begin(title, message); modal_mode_ = ModalMode::Busy; spinner_frame_ = 0; - InstallSigintTrap(); + InstallSignalTraps(); auto* active = screen_; StartSpinner(); if (active) active->PostEvent(ftxui::Event::Custom); @@ -433,7 +468,7 @@ void TuxDockApp::BeginBusyOperation(const std::string& title, const auto message = action(); if (active && ftxui::ScreenInteractive::Active() == active) { active->Post([this, message] { - RemoveSigintTrap(); + RemoveSignalTraps(); operation_state_.complete(message); StopSpinner(); modal_mode_ = ModalMode::None; @@ -447,7 +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; - InstallSigintTrap(); + InstallSignalTraps(); auto* active = screen_; spinner_frame_ = 0; StartSpinner(); @@ -458,7 +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 { - RemoveSigintTrap(); + RemoveSignalTraps(); ApplyRefreshResults(std::move(containers), std::move(images)); operation_state_.complete(message); StopSpinner(); @@ -664,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() { @@ -768,6 +803,9 @@ ftxui::Element TuxDockApp::RenderModal() const { 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 { From 2be5aa17ccbb4cfac522f2d44ef3932930b36c6e Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Sat, 22 Aug 2026 17:30:38 -0400 Subject: [PATCH 3/3] DEVLOG update for 0.1.2-beta --- DEVLOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 123ee6a..a464156 100644 --- a/DEVLOG.md +++ b/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.