diff --git a/3rd-party/libssh/CMakeLists.txt b/3rd-party/libssh/CMakeLists.txt index e778b24ac3..20a3602806 100644 --- a/3rd-party/libssh/CMakeLists.txt +++ b/3rd-party/libssh/CMakeLists.txt @@ -59,7 +59,7 @@ file(STRINGS libssh/CMakeLists.txt libssh_VERSION REGEX "^project\\(libssh") file(STRINGS libssh/CMakeLists.txt LIBRARY_VERSION REGEX "^set\\(LIBRARY_VERSION") file(STRINGS libssh/CMakeLists.txt LIBRARY_SOVERSION REGEX "^set\\(LIBRARY_SOVERSION") -string(REGEX MATCH "^project\\(libssh VERSION ([0-9]+)\\.([0-9]+)\\.([0-9]+) LANGUAGES C\\)$" +string(REGEX MATCH "^project\\(libssh VERSION ([0-9]+)\\.([0-9]+)\\.([0-9]+) LANGUAGES C CXX\\)$" libssh_VERSION "${libssh_VERSION}") if (NOT libssh_VERSION) message(FATAL_ERROR "unable to find libssh project version") diff --git a/3rd-party/libssh/libssh b/3rd-party/libssh/libssh index a09a5a5b5d..f23d1454e5 160000 --- a/3rd-party/libssh/libssh +++ b/3rd-party/libssh/libssh @@ -1 +1 @@ -Subproject commit a09a5a5b5dd414bf422ab3ac3c50626eb46e8d67 +Subproject commit f23d1454e50d0dbb314edd9bf4227ab72303484b diff --git a/3rd-party/submodule_info.md b/3rd-party/submodule_info.md index 4e1aa30c29..eaf19314af 100644 --- a/3rd-party/submodule_info.md +++ b/3rd-party/submodule_info.md @@ -12,7 +12,7 @@ Version: 1.52.1 (+[our patches](https://github.com/CanonicalLtd/grpc/compare/v1. ### libssh -Version: 0.10.6 (+[our patches](https://github.com/canonical/libssh/compare/libssh-0.10.6...multipass)) | +Version: 0.11.1 (+[our patches](https://github.com/canonical/libssh/compare/libssh-0.11.1...multipass)) | | diff --git a/include/multipass/platform.h b/include/multipass/platform.h index a5c153d9de..5760d45695 100644 --- a/include/multipass/platform.h +++ b/include/multipass/platform.h @@ -91,7 +91,11 @@ std::unique_ptr make_sshfs_server_process(const SSHFSServerConfig& conf std::unique_ptr make_process(std::unique_ptr&& process_spec); int symlink_attr_from(const char* path, sftp_attributes_struct* attr); -std::function make_quit_watchdog(); // call while single-threaded; call result later, in dedicated thread +// Creates a function that will wait for signals or until the passed function returns false. +// The passed function is checked every `period` milliseconds. +// If a signal is received the optional contains it, otherwise the optional is empty. +std::function(const std::function&)> make_quit_watchdog( + const std::chrono::milliseconds& period); // call while single-threaded; call result later, in dedicated thread std::string reinterpret_interface_id(const std::string& ux_id); // give platforms a chance to reinterpret network IDs diff --git a/include/multipass/platform_unix.h b/include/multipass/platform_unix.h index ef82893201..95d306c1bc 100644 --- a/include/multipass/platform_unix.h +++ b/include/multipass/platform_unix.h @@ -18,10 +18,9 @@ #ifndef MULTIPASS_PLATFORM_UNIX_H #define MULTIPASS_PLATFORM_UNIX_H +#include #include -#include - namespace multipass { namespace platform diff --git a/include/multipass/ssh/ssh_session.h b/include/multipass/ssh/ssh_session.h index 483eb73124..50223bbc1e 100644 --- a/include/multipass/ssh/ssh_session.h +++ b/include/multipass/ssh/ssh_session.h @@ -33,11 +33,7 @@ class SSHKeyProvider; class SSHSession { public: - SSHSession(const std::string& host, - int port, - const std::string& ssh_username, - const SSHKeyProvider& key_provider, - const std::chrono::milliseconds timeout = std::chrono::seconds(20)); + SSHSession(const std::string& host, int port, const std::string& ssh_username, const SSHKeyProvider& key_provider); // just being explicit (unique_ptr member already caused these to be deleted) SSHSession(const SSHSession&) = delete; diff --git a/src/platform/console/unix_console.cpp b/src/platform/console/unix_console.cpp index 4759893563..43e9ee4322 100644 --- a/src/platform/console/unix_console.cpp +++ b/src/platform/console/unix_console.cpp @@ -71,13 +71,14 @@ mp::UnixConsole::UnixConsole(ssh_channel channel, UnixTerminal* term) : term{ter if (term->is_live()) { - setup_console(); - const char* term_type = std::getenv("TERM"); term_type = (term_type == nullptr) ? "xterm" : term_type; update_local_pty_size(term->cout_fd()); ssh_channel_request_pty_size(channel, term_type, local_pty_size.columns, local_pty_size.rows); + + // set stdin to Raw Mode after libssh inherits sane settings from stdin. + setup_console(); } } diff --git a/src/platform/platform_unix.cpp b/src/platform/platform_unix.cpp index 9e00b6aaf3..f4e2c62f7d 100644 --- a/src/platform/platform_unix.cpp +++ b/src/platform/platform_unix.cpp @@ -14,12 +14,14 @@ * along with this program. If not, see . */ +#include #include #include #include #include #include +#include #include #include #include @@ -170,16 +172,53 @@ sigset_t mp::platform::make_sigset(const std::vector& sigs) sigset_t mp::platform::make_and_block_signals(const std::vector& sigs) { - auto sigset{make_sigset(sigs)}; - pthread_sigmask(SIG_BLOCK, &sigset, nullptr); + const auto sigset{make_sigset(sigs)}; + if (const auto ec = pthread_sigmask(SIG_BLOCK, &sigset, nullptr); ec) + throw std::runtime_error(fmt::format("Failed to block signals: {}", strerror(ec))); + return sigset; } -std::function mp::platform::make_quit_watchdog() +template +timespec make_timespec(std::chrono::duration duration) +{ + const auto seconds = std::chrono::duration_cast(duration); + return timespec{seconds.count(), std::chrono::duration_cast(duration - seconds).count()}; +} + +std::function(const std::function&)> mp::platform::make_quit_watchdog( + const std::chrono::milliseconds& period) { - return [sigset = make_and_block_signals({SIGQUIT, SIGTERM, SIGHUP})]() { - int sig = -1; - sigwait(&sigset, &sig); - return sig; + return [sigset = make_and_block_signals({SIGQUIT, SIGTERM, SIGHUP, SIGUSR2}), + period](const std::function& condition) -> std::optional { + std::mutex sig_mtx; + std::condition_variable sig_cv; + int sig = SIGUSR2; + + // A signal generator that triggers after `timeout` + AutoJoinThread signaler([&sig_mtx, &sig_cv, &sig, &period, signalee = pthread_self()] { + std::unique_lock lock(sig_mtx); + while (!sig_cv.wait_for(lock, period, [&sig] { return sig != SIGUSR2; })) + { + pthread_kill(signalee, SIGUSR2); + } + }); + + // wait on signals and condition + int ret = SIGUSR2; + while (ret == SIGUSR2 && condition()) + { + // can't use sigtimedwait since macOS doesn't support it + sigwait(&sigset, &ret); + } + + { + std::lock_guard lock(sig_mtx); + sig = ret == SIGUSR2 ? 0 : ret; + } + sig_cv.notify_all(); + + // if sig is SIGUSR2 then we know we're exiting because condition() was false + return ret == SIGUSR2 ? std::nullopt : std::make_optional(sig); }; } diff --git a/src/ssh/ssh_session.cpp b/src/ssh/ssh_session.cpp index 3c3a358a0a..91f666e010 100644 --- a/src/ssh/ssh_session.cpp +++ b/src/ssh/ssh_session.cpp @@ -33,14 +33,13 @@ namespace mpl = multipass::logging; mp::SSHSession::SSHSession(const std::string& host, int port, const std::string& username, - const SSHKeyProvider& key_provider, - const std::chrono::milliseconds timeout) + const SSHKeyProvider& key_provider) : session{ssh_new(), ssh_free}, mut{} { if (session == nullptr) throw mp::SSHException("could not allocate ssh session"); - const long timeout_secs = std::chrono::duration_cast(timeout).count(); + const long timeout_secs = std::numeric_limits::max(); const int nodelay{1}; auto ssh_dir = QDir(MP_STDPATHS.writableLocation(StandardPaths::AppConfigLocation)).filePath("ssh").toStdString(); diff --git a/src/sshfs_mount/sshfs_mount.cpp b/src/sshfs_mount/sshfs_mount.cpp index bca8090c90..ceb3e6c0c3 100644 --- a/src/sshfs_mount/sshfs_mount.cpp +++ b/src/sshfs_mount/sshfs_mount.cpp @@ -151,21 +151,29 @@ auto make_sftp_server(mp::SSHSession&& session, const std::string& source, const } // namespace -mp::SshfsMount::SshfsMount(SSHSession&& session, const std::string& source, const std::string& target, - const mp::id_mappings& gid_mappings, const mp::id_mappings& uid_mappings) +mp::SshfsMount::SshfsMount(SSHSession&& session, + const std::string& source, + const std::string& target, + const mp::id_mappings& gid_mappings, + const mp::id_mappings& uid_mappings) : sftp_server{make_sftp_server(std::move(session), source, target, gid_mappings, uid_mappings)}, - sftp_thread{[this]() { + sftp_thread{[this] { + state.store(State::Running, std::memory_order_release); + mp::top_catch_all(category, [this] { std::cout << "Connected" << std::endl; sftp_server->run(); std::cout << "Stopped" << std::endl; }); + + state.store(State::Stopped, std::memory_order_release); }} { } mp::SshfsMount::~SshfsMount() { + state.store(State::Stopped, std::memory_order_release); stop(); } @@ -175,3 +183,8 @@ void mp::SshfsMount::stop() if (sftp_thread.joinable()) sftp_thread.join(); } + +bool mp::SshfsMount::alive() const +{ + return state.load(std::memory_order_acquire) != State::Stopped; +} diff --git a/src/sshfs_mount/sshfs_mount.h b/src/sshfs_mount/sshfs_mount.h index bee80681f4..3799bcd46f 100644 --- a/src/sshfs_mount/sshfs_mount.h +++ b/src/sshfs_mount/sshfs_mount.h @@ -38,7 +38,17 @@ class SshfsMount void stop(); + [[nodiscard]] bool alive() const; + private: + enum class State + { + Unstarted, + Running, + Stopped + }; + + std::atomic state{State::Unstarted}; // sftp_server Doesn't need to be a pointer, but done for now to avoid bringing sftp.h // which has an error with -pedantic. std::unique_ptr sftp_server; diff --git a/src/sshfs_mount/sshfs_server.cpp b/src/sshfs_mount/sshfs_server.cpp index 8eb92a5478..ee49d7a3e5 100644 --- a/src/sshfs_mount/sshfs_server.cpp +++ b/src/sshfs_mount/sshfs_server.cpp @@ -105,17 +105,22 @@ int main(int argc, char* argv[]) try { - auto watchdog = mpp::make_quit_watchdog(); // called while there is only one thread + auto watchdog = + mpp::make_quit_watchdog(std::chrono::milliseconds{500}); // called while there is only one thread mp::SSHSession session{host, port, username, mp::SSHClientKeyProvider{priv_key_blob}}; mp::SshfsMount sshfs_mount(std::move(session), source_path, target_path, gid_mappings, uid_mappings); // ssh lives on its own thread, use this thread to listen for quit signal - if (int sig = watchdog()) - cout << "Received signal " << sig << ". Stopping" << endl; + auto sig = watchdog([&sshfs_mount] { return sshfs_mount.alive(); }); + + if (sig.has_value()) + cout << "Received signal " << *sig << ". Stopping" << endl; + else + cerr << "SFTP server thread stopped unexpectedly." << endl; sshfs_mount.stop(); - exit(0); + exit(sig.has_value() ? EXIT_SUCCESS : EXIT_FAILURE); } catch (const mp::SSHFSMissingError&) {