Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions WinPort/src/Backend/TTY/TTYBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <signal.h>
#include <fcntl.h>
#include <exception>
#include <chrono>
#include <sys/ioctl.h>

#ifdef __linux__
Expand Down Expand Up @@ -32,6 +33,13 @@ static uint16_t g_far2l_term_width = 80, g_far2l_term_height = 25;
static volatile long s_terminal_size_change_id = 0;
static TTYBackend * g_vtb = nullptr;

static volatile sig_atomic_t s_parent_dead = 0;

static void OnParentDead(int)
{
s_parent_dead = 1;
}

long _iterm2_cmd_ts = 0;
bool _iterm2_cmd_state = 0;

Expand Down Expand Up @@ -86,6 +94,7 @@ TTYBackend::TTYBackend(const char *full_exe_path, int std_in, int std_out, bool
_ext_clipboard(ext_clipboard),
_restrict(restrict),
_esc_expiration(esc_expiration),
_is_forktty_child(notify_pipe != -1),
_notify_pipe(notify_pipe),
_result(result),
_largest_window_size_ready(false)
Expand Down Expand Up @@ -306,7 +315,21 @@ void TTYBackend::ReaderThread()
break;
}
usleep(10000);
if (_is_forktty_child && s_parent_dead) {
fprintf(stderr, "TTYBackend::ReaderThread: parent gone during suspend, exiting\n");
_exiting = true;
WINPORT(GenerateConsoleCtrlEvent)(CTRL_CLOSE_EVENT, 0);
break;
}
} else {
// If the parent session manager (ForkTTYChild wrapper) is gone,
// no one will ever connect for revival — exit gracefully.
if (_is_forktty_child && s_parent_dead) {
fprintf(stderr, "TTYBackend::ReaderThread: parent gone, exiting\n");
_exiting = true;
WINPORT(GenerateConsoleCtrlEvent)(CTRL_CLOSE_EVENT, 0);
break;
}
const std::string &info = StrWide2MB(g_winport_con_out->GetTitle());
int np = TTYReviveMe(_stdin, _stdout, _kickass[0], info);
if (np != -1) {
Expand Down Expand Up @@ -465,7 +488,9 @@ void TTYBackend::WriterThread()
}

tty_out.Flush();
tcdrain(_stdout);
if (!_exiting && !_deadio) {
tcdrain(_stdout);
}

if (ae.go_background) {
gone_background = true;
Expand Down Expand Up @@ -1020,7 +1045,7 @@ void TTYBackend::OSC52SetClipboard(const char *text)

bool TTYBackend::Far2lInteract(StackSerializer &stk_ser, bool wait)
{
if (_tty_caps.kind != TTYCaps::FAR2L || _exiting)
if (_tty_caps.kind != TTYCaps::FAR2L || _exiting || _deadio)
return false;

std::shared_ptr<Far2lInteractData> pfi = std::make_shared<Far2lInteractData>();
Expand All @@ -1037,10 +1062,24 @@ bool TTYBackend::Far2lInteract(StackSerializer &stk_ser, bool wait)
if (!wait)
return true;

pfi->evnt.Wait();
static constexpr unsigned int FAR2L_INTERACT_TIMEOUT_MSEC = 10000;
static constexpr unsigned int FAR2L_INTERACT_POLL_MSEC = 500;
const auto deadline = std::chrono::steady_clock::now()
+ std::chrono::milliseconds(FAR2L_INTERACT_TIMEOUT_MSEC);
bool completed = false;
while (!completed) {
completed = pfi->evnt.TimedWait(FAR2L_INTERACT_POLL_MSEC);
if (completed || _exiting || _deadio)
break;
if (std::chrono::steady_clock::now() >= deadline) {
fprintf(stderr, "TTYBackend::Far2lInteract: timeout\n");
pfi->stk_ser.Swap(stk_ser);
return false;
}
}

std::unique_lock<std::mutex> lock_sent(_far2l_interacts_sent);
if (_exiting)
if (_exiting || _deadio)
return false;

pfi->stk_ser.Swap(stk_ser);
Expand Down Expand Up @@ -1431,6 +1470,7 @@ static void OnSigHup(int signo)
}
}


void TTYBackend::OnSigHup()
{
// drop sudo privileges once pending sudo operation completes
Expand Down Expand Up @@ -1656,15 +1696,17 @@ bool WinPortMainTTY(const char *full_exe_path, int std_in, int std_out,
auto orig_tstp = signal(SIGTSTP, OnSigTstp);
auto orig_cont = signal(SIGCONT, OnSigCont);
auto orig_hup = signal(SIGHUP, (notify_pipe != -1) ? OnSigHup : SIG_DFL); // notify_pipe == -1 means --mortal specified
auto orig_usr1 = signal(SIGUSR1, (notify_pipe != -1) ? OnParentDead : SIG_DFL);

*result = 0; // set OK status for case if app will go background
*result = AppMain(argc, argv);

signal(SIGHUP, orig_hup);
signal(SIGCONT, orig_tstp);
signal(SIGTSTP, orig_cont);
signal(SIGCONT, orig_cont);
signal(SIGTSTP, orig_tstp);
signal(SIGWINCH, orig_winch);
signal(SIGTERM, orig_term);
signal(SIGUSR1, orig_usr1);

return true;
}
5 changes: 3 additions & 2 deletions WinPort/src/Backend/TTY/TTYBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TTYBackend : IConsoleOutputBackend, ITTYInputSpecialSequenceHandler, IFar2
bool _ext_clipboard = false;
TTYRestrict _restrict{};
unsigned int _esc_expiration = 0;
bool _is_forktty_child = false;
int _notify_pipe = -1;
int *_result = nullptr;
int _kickass[2] = {-1, -1};
Expand All @@ -56,8 +57,8 @@ class TTYBackend : IConsoleOutputBackend, ITTYInputSpecialSequenceHandler, IFar2
long _terminal_size_change_id = 0;

pthread_t _reader_trd = 0;
volatile bool _exiting = false;
volatile bool _deadio = false;
std::atomic<bool> _exiting{false};
std::atomic<bool> _deadio{false};

static void *sReaderThread(void *p) { ((TTYBackend *)p)->ReaderThread(); return nullptr; }
static void *sWriterThread(void *p) { ((TTYBackend *)p)->WriterThread(); return nullptr; }
Expand Down
4 changes: 0 additions & 4 deletions WinPort/src/Backend/TTY/TTYRevive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ int TTYReviveMe(int std_in, int std_out, int kickass, const std::string &info)
} catch (LocalSocketCancelled &e) {
(void)e;
fprintf(stderr, "TTYReviveMe: kickass signalled\n");
char c;
if (read(kickass, &c, 1) < 0) {
perror("read kickass");
}

} catch (std::exception &e) {
fprintf(stderr, "TTYReviveMe: %s\n", e.what());
Expand Down
15 changes: 15 additions & 0 deletions WinPort/src/Backend/WinPortMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
# include <termios.h>
# include <linux/kd.h>
# include <linux/keyboard.h>
# include <sys/prctl.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
# include <sys/ioctl.h>
# include <sys/kbio.h>
# include <sys/procctl.h>
#endif

#include <ScopeHelpers.h>
Expand Down Expand Up @@ -356,6 +358,12 @@ static bool IsFar2lTerminal()
return (env && strstr(env, "far2l") != NULL);
}

static volatile sig_atomic_t s_pdeathsig_pending = 0;
static void OnParentDead(int)
{
s_pdeathsig_pending = 1;
}

extern "C" int WinPortMain(const char *full_exe_path, int argc, char **argv, int(*AppMain)(int argc, char **argv))
{
std::unique_ptr<ConsoleOutput> winport_con_out(new ConsoleOutput);
Expand Down Expand Up @@ -560,6 +568,13 @@ extern "C" int WinPortMain(const char *full_exe_path, int argc, char **argv, int
MakeFDCloexec(std_out);
MakeFDCloexec(new_notify_pipe[1]);
if (g_sigwinch_pid == 0) {
signal(SIGUSR1, OnParentDead);
#ifdef __linux__
prctl(PR_SET_PDEATHSIG, SIGUSR1);
#elif defined(__FreeBSD__) || defined(__DragonFly__)
int sig = SIGUSR1;
procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sig);
#endif
{
setsid();
SudoAskpassImpl askass_impl;
Expand Down
7 changes: 7 additions & 0 deletions far2l/src/console/keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "headers.hpp"

#include <ctype.h>
#include "InterThreadCall.hpp"
#include "keyboard.hpp"
#include "farqueue.hpp"
#include "lang.hpp"
Expand Down Expand Up @@ -708,6 +709,12 @@ static DWORD GetInputRecordInner(INPUT_RECORD *rec, bool ExcludeMacro, bool Proc
}

#endif
if (rec->EventType == NOOP_EVENT) {
Console.ReadInput(*rec);
DispatchInterThreadCalls();
CheckForPendingCtrlHandleEvent();
continue;
}
break;
}

Expand Down
46 changes: 30 additions & 16 deletions far2l/src/vt/vtshell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,28 +530,42 @@ class VTShell : VTOutputReader::IProcessor, VTInputReader::IProcessor, IVTShell

void OnConsoleLog(ConsoleLogKind kind)//NB: called not from main thread!
{
std::unique_lock<std::mutex> lock(_inout_control_mutex, std::try_to_lock);
if (!lock) {
fprintf(stderr, "VTShell::OnConsoleLog: SKIPPED\n");
return;
// Two-phase lock discipline to avoid main-thread starvation:
// Phase 1: lock → stop output reader → unlock.
// VTA is suspended before the lock (VTAnsiSuspend RAII).
// Output reader stays stopped across the InterThreadCall.
// Phase 2: blocking InterThreadCall with NO mutex held —
// the main thread can freely call StartIOReaders/StopIOReaders.
// VTAnsiSuspend destructor fires at inner-scope exit:
// VTA is resumed BEFORE the reader is restarted.
// Phase 3: lock → restart output reader (only if we stopped it) → unlock.
bool did_stop = false;
{
VTAnsiSuspend vta_suspend(_vta);
if (!vta_suspend)
return;
{
std::lock_guard<std::mutex> lock(_inout_control_mutex);
if (_output_reader.IsStarted()) {
_output_reader.Stop();
did_stop = true;
}
}
DeliverPendingWindowInfo();
InterThreadCall<int>(std::bind(sShowConsoleLog, kind));
} // VTA resumed here, before reader restart
// Restart the output reader so terminal output flows again.
if (did_stop) {
std::lock_guard<std::mutex> lock(_inout_control_mutex);
if (!_output_reader.IsStarted()) {
_output_reader.Start(_fd_out);
}
}

//called in input thread context
//we're input, stop output and remember _vta state

StopAndStart<VTOutputReader> sas(_output_reader);
VTAnsiSuspend vta_suspend(_vta);
if (!vta_suspend)
return;

DeliverPendingWindowInfo();
InterThreadCall<int>(std::bind(sShowConsoleLog, kind));
if (!_slavename.empty())
UpdateTerminalSize(_fd_out);
if (_far2l_exts)
_far2l_exts->OnTerminalResized();
}

void OnConsoleSwitch()
{
InterThreadLockAndWake ittlaw;
Expand Down
3 changes: 3 additions & 0 deletions far2l/src/vt/vtshell_ioreaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class WithThread
volatile bool _started;

bool Start();
bool IsStarted() const { return _started; }

void Join();
virtual void OnJoin();
virtual void *ThreadProc() = 0;
Expand All @@ -53,6 +55,7 @@ class VTOutputReader : protected WithThread
void Start(int fd_out = -1);
void Stop();
inline bool IsDeactivated() const { return _deactivated; }
inline bool IsStarted() const { return WithThread::IsStarted(); }
void KickAss();


Expand Down
4 changes: 4 additions & 0 deletions utils/src/LocalSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ void LocalSocketServer::WaitForClient(int fd_cancel, int tmout_msec)

if (fd_cancel != -1) {
if (FD_ISSET(fd_cancel, &fde) || FD_ISSET(fd_cancel, &fdr)) {
char c;
if (read(fd_cancel, &c, 1) > 0) {
// byte consumed — prevents immediate LocalSocketCancelled rethrow
}
throw LocalSocketCancelled();
}
}
Expand Down
Loading