From 23afed056b14f66b345a4b36dd742848bec91ea2 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 26 Sep 2023 17:50:56 +1000 Subject: [PATCH] Add a daemonize-session command and refactor local client handling Make it possible to move the current session to a daemon one after the fact, which is useful to ensure the session state survives client disconnecting, for example when working from ssh. --- doc/pages/changelog.asciidoc | 3 ++ src/commands.cc | 12 +++++++ src/main.cc | 70 +++++++++++++----------------------- src/remote.hh | 1 + 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/doc/pages/changelog.asciidoc b/doc/pages/changelog.asciidoc index f60b9ce2..e8331d13 100644 --- a/doc/pages/changelog.asciidoc +++ b/doc/pages/changelog.asciidoc @@ -8,6 +8,9 @@ released versions. * `+` only duplicates identical selections a single time to avoid surprising and slow exponential growth in the number of selections. +* `daemonize-session` command makes it possible to convert the current session + to a deamon one (which will not exit on last client disconnecting) + == Kakoune 2023.08.08 * Fix compilation errors on FreeBSD and MacOS using clang diff --git a/src/commands.cc b/src/commands.cc index fe986c42..685ced82 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -688,6 +688,17 @@ const CommandDesc force_kill_cmd = { kill }; +const CommandDesc daemonize_session_cmd = { + "daemonize-session", + nullptr, + "daemonize-session: set the session server not to quit on last client exit", + { {} }, + CommandFlags::None, + CommandHelper{}, + CommandCompleter{}, + [](const ParametersParser&, Context&, const ShellContext&) { Server::instance().daemonize(); } +}; + template void quit(const ParametersParser& parser, Context& context, const ShellContext&) { @@ -2756,6 +2767,7 @@ void register_commands() register_command(write_all_quit_cmd); register_command(kill_cmd); register_command(force_kill_cmd); + register_command(daemonize_session_cmd); register_command(quit_cmd); register_command(force_quit_cmd); register_command(write_quit_cmd); diff --git a/src/main.cc b/src/main.cc index 411e7dfe..438a3012 100644 --- a/src/main.cc +++ b/src/main.cc @@ -47,6 +47,7 @@ struct { } constexpr version_notes[] = { { 0, "» {+b}+{} only duplicates identical selections a single time\n" + "» {+u}daemonize-session{} command\n" }, { 20230805, "» Fix FreeBSD/MacOS clang compilation\n" @@ -603,7 +604,6 @@ void register_options() } static Client* local_client = nullptr; -static int local_client_exit = 0; static bool convert_to_client_pending = false; enum class UIType @@ -671,38 +671,7 @@ pid_t fork_server_to_background() std::unique_ptr create_local_ui(UIType ui_type) { - if (ui_type != UIType::Terminal) - return make_ui(ui_type); - - struct LocalUI : TerminalUI - { - LocalUI() - { - set_signal_handler(SIGTSTP, [](int) { - if (ClientManager::instance().count() == 1 and - *ClientManager::instance().begin() == local_client) - TerminalUI::instance().suspend(); - else - convert_to_client_pending = true; - }); - } - - ~LocalUI() override - { - local_client = nullptr; - if (convert_to_client_pending or - ClientManager::instance().empty()) - return; - - if (fork_server_to_background()) - { - this->TerminalUI::~TerminalUI(); - exit(local_client_exit); - } - } - }; - - if (not isatty(0)) + if (ui_type == UIType::Terminal and not isatty(0)) { // move stdin to another fd, and restore tty as stdin int fd = dup(0); @@ -712,7 +681,19 @@ std::unique_ptr create_local_ui(UIType ui_type) create_fifo_buffer("*stdin*", fd, Buffer::Flags::None); } - return std::make_unique(); + auto ui = make_ui(ui_type); + + static SignalHandler old_handler = set_signal_handler(SIGTSTP, [](int sig) { + if (ClientManager::instance().count() == 1 and + *ClientManager::instance().begin() == local_client) + old_handler(sig); + else + { + convert_to_client_pending = true; + set_signal_handler(SIGTSTP, old_handler); + } + }); + return ui; } int run_client(StringView session, StringView name, StringView client_init, @@ -875,13 +856,14 @@ int run_server(StringView session, StringView server_init, write_to_debug_buffer(format("error while opening command line files: {}", error.what())); } + int exit_status = 0; try { if (not server.is_daemon()) { local_client = client_manager.create_client( create_local_ui(ui_type), getpid(), {}, get_env_vars(), client_init, init_buffer, std::move(init_coord), - [](int status) { local_client_exit = status; }); + [&](int status) { exit_status = status; }); if (startup_error and local_client) local_client->print_status({ @@ -920,24 +902,22 @@ int run_server(StringView session, StringView server_init, global_scope.option_registry().clear_option_trash(); if (local_client and not contains(client_manager, local_client)) - local_client = nullptr; - else if (local_client and not local_client->is_ui_ok()) { - ClientManager::instance().remove_client(*local_client, false, -1); local_client = nullptr; - if (not client_manager.empty() and fork_server_to_background()) - return 0; + if ((not client_manager.empty() or server.is_daemon()) and fork_server_to_background()) + exit(exit_status); // We do not want to run destructors and hooks here } else if (convert_to_client_pending) { kak_assert(local_client); auto& local_context = local_client->context(); - const String client_name = local_context.name(); - const String buffer_name = local_context.buffer().name(); - const String selections = selection_list_to_string(ColumnType::Byte, local_context.selections()); + String client_name = local_context.name(); + String buffer_name = local_context.buffer().name(); + String selections = selection_list_to_string(ColumnType::Byte, local_context.selections()); ClientManager::instance().remove_client(*local_client, true, 0); client_manager.clear_client_trash(); + local_client = nullptr; convert_to_client_pending = false; if (fork_server_to_background()) @@ -952,7 +932,7 @@ int run_server(StringView session, StringView server_init, } catch (const kill_session& kill) { - local_client_exit = kill.exit_status; + exit_status = kill.exit_status; } { @@ -960,7 +940,7 @@ int run_server(StringView session, StringView server_init, global_scope.hooks().run_hook(Hook::KakEnd, "", empty_context); } - return local_client_exit; + return exit_status; } int run_filter(StringView keystr, ConstArrayView files, bool quiet, StringView suffix_backup) diff --git a/src/remote.hh b/src/remote.hh index 903e03f1..99e60cbd 100644 --- a/src/remote.hh +++ b/src/remote.hh @@ -59,6 +59,7 @@ struct Server : public Singleton bool negotiating() const { return not m_accepters.empty(); } + void daemonize() { m_is_daemon = true; } bool is_daemon() const { return m_is_daemon; } private: