From 934c0ee6d053ee20dd5a804ea25796cfc1ff6769 Mon Sep 17 00:00:00 2001 From: timvisee Date: Sun, 7 Nov 2021 21:14:14 +0100 Subject: [PATCH] Add simple SIGINT handling --- Cargo.lock | 1 + Cargo.toml | 3 +- src/config.rs | 2 +- src/main.rs | 50 +++++++++++++-------------- src/monitor.rs | 39 +-------------------- src/protocol.rs | 8 ++--- src/server.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 71 deletions(-) create mode 100644 src/server.rs diff --git a/Cargo.lock b/Cargo.lock index 5be95c2..d0a5925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,7 @@ version = "0.1.0" dependencies = [ "bytes", "futures", + "libc", "minecraft-protocol", "rand 0.8.4", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 13395f8..6295d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ edition = "2021" [dependencies] bytes = "1.1" futures = { version = "0.3", default-features = false } +libc = "0.2" minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" } rand = "0.8" -tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process"] } +tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] } diff --git a/src/config.rs b/src/config.rs index 161539b..1071670 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ pub(crate) const ADDRESS_PUBLIC: &str = "127.0.0.1:9090"; pub(crate) const ADDRESS_PROXY: &str = "127.0.0.1:9091"; /// Server description shown when server is starting. -pub(crate) const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping...\n§2☻ Join to start it up"; +pub(crate) const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping\n§2☻ Join to start it up"; /// Server description shown when server is starting. pub(crate) const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait..."; diff --git a/src/main.rs b/src/main.rs index dfd2125..d80ffaf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ -pub mod config; -pub mod monitor; -pub mod protocol; -pub mod types; +pub(crate) mod config; +pub(crate) mod monitor; +pub(crate) mod protocol; +pub(crate) mod server; +pub(crate) mod types; use std::sync::Arc; @@ -19,11 +20,10 @@ use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use tokio::net::tcp::ReadHalf; use tokio::net::{TcpListener, TcpStream}; -use tokio::process::Command; use config::*; -use monitor::ServerState; use protocol::{Client, ClientState, RawPacket}; +use server::ServerState; #[tokio::main] async fn main() -> Result<(), ()> { @@ -32,7 +32,7 @@ async fn main() -> Result<(), ()> { ADDRESS_PUBLIC, ADDRESS_PROXY, ); - let server_state = monitor::ServerState::default().shared(); + let server_state = Arc::new(ServerState::default()); // Listen for new connections // TODO: do not drop error here @@ -42,6 +42,17 @@ async fn main() -> Result<(), ()> { let addr = ADDRESS_PROXY.parse().expect("invalid server IP"); tokio::spawn(monitor::monitor_server(addr, server_state.clone())); + let sub = server_state.clone(); + tokio::spawn(async move { + loop { + tokio::signal::ctrl_c().await.unwrap(); + if !sub.kill_server() { + // TODO: gracefully kill itself instead + std::process::exit(1) + } + } + }); + // Proxy all incomming connections while let Ok((inbound, _)) = listener.accept().await { let client = Client::default(); @@ -164,7 +175,7 @@ async fn status_server( // Start server if not starting yet if !server.starting() { server.set_starting(true); - tokio::spawn(start_server(server).map(|_| ())); + tokio::spawn(server::start(server).map(|_| ())); } break; @@ -223,10 +234,10 @@ async fn status_server( continue; } - // Show unhandled packet warning - eprintln!("Received unhandled packet:"); - eprintln!("- State: {:?}", client.state()); - eprintln!("- Packet ID: {}", packet.id); + // // Show unhandled packet warning + // eprintln!("Received unhandled packet:"); + // eprintln!("- State: {:?}", client.state()); + // eprintln!("- Packet ID: {}", packet.id); } // Gracefully close connection @@ -259,18 +270,3 @@ async fn proxy(mut inbound: TcpStream, addr_target: String) -> Result<(), ()> { Ok(()) } - -/// Start Minecraft server. -async fn start_server(state: Arc) -> Result<(), Box> { - let mut cmd = Command::new(SERVER_CMD); - - let status = cmd.status().await?; - - println!("Server exited, status: {}", status); - - // Reset online and starting state - state.set_online(false); - state.set_starting(false); - - Ok(()) -} diff --git a/src/monitor.rs b/src/monitor.rs index 9f229e5..66c7e2b 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,7 +1,6 @@ // TODO: remove all unwraps/expects here! use std::net::SocketAddr; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -15,6 +14,7 @@ use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; use crate::protocol::{self, ClientState, RawPacket}; +use crate::server::ServerState; /// Minecraft protocol version used when polling server status. const PROTOCOL_VERSION: i32 = 754; @@ -25,43 +25,6 @@ const MONITOR_PING_INTERVAL: u64 = 2; /// Ping timeout in seconds. const PING_TIMEOUT: u64 = 8; -/// Shared server state. -#[derive(Default, Debug)] -pub struct ServerState { - /// Whether the server is online. - online: AtomicBool, - - /// Whether the server is starting. - starting: AtomicBool, -} - -impl ServerState { - /// Transform into shared instance. - pub fn shared(self) -> Arc { - Arc::new(self) - } - - /// Whether the server is online. - pub fn online(&self) -> bool { - self.online.load(Ordering::Relaxed) - } - - /// Set whether the server is online. - pub fn set_online(&self, online: bool) { - self.online.store(online, Ordering::Relaxed) - } - - /// Whether the server is starting. - pub fn starting(&self) -> bool { - self.starting.load(Ordering::Relaxed) - } - - /// Set whether the server is starting. - pub fn set_starting(&self, starting: bool) { - self.starting.store(starting, Ordering::Relaxed) - } -} - /// Poll server state. /// /// Returns `true` if a ping succeeded. diff --git a/src/protocol.rs b/src/protocol.rs index f97f347..f633aec 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -46,10 +46,10 @@ impl ClientState { /// From state ID. pub fn from_id(id: i32) -> Option { match id { - // 0 => Self::Handshake, + 0 => Some(Self::Handshake), 1 => Some(Self::Status), 2 => Some(Self::Login), - // 2 => Self::Play, + 3 => Some(Self::Play), _ => None, } } @@ -57,10 +57,10 @@ impl ClientState { /// Get state ID. pub fn to_id(self) -> i32 { match self { - Self::Handshake => unimplemented!(), + Self::Handshake => 0, Self::Status => 1, Self::Login => 2, - Self::Play => unimplemented!(), + Self::Play => 3, } } } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..7b77b18 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,92 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use tokio::process::Command; + +use crate::config::SERVER_CMD; + +/// Shared server state. +#[derive(Default, Debug)] +pub struct ServerState { + /// Whether the server is online. + online: AtomicBool, + + /// Whether the server is starting. + starting: AtomicBool, + + /// Server PID. + pid: Mutex>, +} + +impl ServerState { + /// Whether the server is online. + pub fn online(&self) -> bool { + self.online.load(Ordering::Relaxed) + } + + /// Set whether the server is online. + pub fn set_online(&self, online: bool) { + self.online.store(online, Ordering::Relaxed) + } + + /// Whether the server is starting. + pub fn starting(&self) -> bool { + self.starting.load(Ordering::Relaxed) + } + + /// Set whether the server is starting. + pub fn set_starting(&self, starting: bool) { + self.starting.store(starting, Ordering::Relaxed) + } + + /// Kill any running server. + pub fn kill_server(&self) -> bool { + if let Some(pid) = *self.pid.lock().unwrap() { + eprintln!("[lazymc] Sending kill signal to server"); + kill_gracefully(pid); + return true; + } + + false + } + + /// Set server PID. + pub fn set_pid(&self, pid: Option) { + *self.pid.lock().unwrap() = pid; + } +} + +/// Start Minecraft server. +pub async fn start(state: Arc) -> Result<(), Box> { + let mut cmd = Command::new(SERVER_CMD); + + eprintln!("[lazymc] Starting server..."); + let mut child = cmd.spawn()?; + + state.set_pid(Some(child.id().expect("unknown server PID"))); + + let status = child.wait().await?; + eprintln!("[lazymc] Server stopped (status: {})\n", status); + + // Reset online and starting state + // TODO: also set this when returning early due to error + state.set_pid(None); + state.set_online(false); + state.set_starting(false); + + Ok(()) +} + +/// Gracefully kill process. +fn kill_gracefully(pid: u32) { + #[cfg(unix)] + unsafe { + libc::kill(pid as i32, libc::SIGINT); + } + + #[cfg(not(unix))] + { + // TODO: implement this for Windows + unimplemented!(); + } +}