lazymc/src/service/file_watcher.rs

164 lines
5.4 KiB
Rust

use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{self, BannedIps};
use crate::mc::whitelist;
use crate::server::Server;
/// File watcher debounce time.
const WATCH_DEBOUNCE: Duration = Duration::from_secs(2);
/// Service to watch server file changes.
pub fn service(config: Arc<Config>, server: Arc<Server>) {
// Ensure server directory is set, it must exist
let dir = match ConfigServer::server_directory(&config) {
Some(dir) if dir.is_dir() => dir,
_ => {
warn!(target: "lazymc", "Server directory doesn't exist, can't watch file changes to reload whitelist and banned IPs");
return;
}
};
// Keep watching
#[allow(clippy::blocks_in_if_conditions)]
while {
// Update all files once
reload_bans(&config, &server, &dir.join(ban::FILE));
reload_whitelist(&config, &server, &dir);
// Watch for changes, update accordingly
watch_server(&config, &server, &dir)
} {}
}
/// Watch server directory.
///
/// Returns `true` if we should watch again.
#[must_use]
fn watch_server(config: &Config, server: &Server, dir: &Path) -> bool {
// Directory must exist
if !dir.is_dir() {
error!(target: "lazymc", "Server directory does not exist at {} anymore, not watching changes", dir.display());
return false;
}
// Create watcher for directory
let (tx, rx) = channel();
let mut watcher =
watcher(tx, WATCH_DEBOUNCE).expect("failed to create watcher for banned-ips.json");
if let Err(err) = watcher.watch(dir, RecursiveMode::NonRecursive) {
error!(target: "lazymc", "An error occured while creating watcher for server files: {}", err);
return true;
}
// Handle change events
loop {
match rx.recv().unwrap() {
// Handle file updates
DebouncedEvent::Create(ref path)
| DebouncedEvent::Write(ref path)
| DebouncedEvent::Remove(ref path) => {
update(config, server, dir, path);
}
// Handle file updates on both paths for rename
DebouncedEvent::Rename(ref before_path, ref after_path) => {
update(config, server, dir, before_path);
update(config, server, dir, after_path);
}
// Ignore write/remove notices, will receive write/remove event later
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {}
// Ignore chmod changes
DebouncedEvent::Chmod(_) => {}
// Rewatch on rescan
DebouncedEvent::Rescan => {
debug!(target: "lazymc", "Rescanning server directory files due to file watching problem");
return true;
}
// Rewatch on error
DebouncedEvent::Error(err, _) => {
error!(target: "lazymc", "Error occurred while watching server directory for file changes: {}", err);
return true;
}
}
}
}
/// Process a file change on the given path.
///
/// Should be called both when created, changed or removed.
fn update(config: &Config, server: &Server, dir: &Path, path: &Path) {
// Update bans
if path.ends_with(ban::FILE) {
reload_bans(config, server, path);
}
// Update whitelist
if path.ends_with(whitelist::WHITELIST_FILE) || path.ends_with(whitelist::OPS_FILE) {
reload_whitelist(config, server, dir);
}
// TODO: update on server.properties change
}
/// Reload banned IPs.
fn reload_bans(config: &Config, server: &Server, path: &Path) {
// Bans must be enabled
if !config.server.block_banned_ips && !config.server.drop_banned_ips {
return;
}
trace!(target: "lazymc", "Reloading banned IPs...");
// File must exist, clear file otherwise
if !path.is_file() {
debug!(target: "lazymc", "No banned IPs, {} does not exist", ban::FILE);
// warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE);
server.set_banned_ips_blocking(BannedIps::default());
return;
}
// Load and update banned IPs
match ban::load(path) {
Ok(ips) => server.set_banned_ips_blocking(ips),
Err(err) => {
debug!(target: "lazymc", "Failed load banned IPs from {}, ignoring: {}", ban::FILE, err);
}
}
// Show warning if 127.0.0.1 is banned
if server.is_banned_ip_blocking(&("127.0.0.1".parse().unwrap())) {
warn!(target: "lazymc", "Local address 127.0.0.1 IP banned, probably not what you want");
warn!(target: "lazymc", "Use '/pardon-ip 127.0.0.1' on the server to unban");
}
}
/// Reload whitelisted users.
fn reload_whitelist(config: &Config, server: &Server, dir: &Path) {
// Whitelist must be enabled
if !config.server.wake_whitelist {
return;
}
// TODO: whitelist must be enabled in server.properties
trace!(target: "lazymc", "Reloading whitelisted users...");
// Load and update whitelisted users
match whitelist::load_dir(dir) {
Ok(whitelist) => server.set_whitelist_blocking(Some(whitelist)),
Err(err) => {
debug!(target: "lazymc", "Failed load whitelist from {}, ignoring: {}", dir.display(), err);
}
}
}