From 444cac3c4711cab90b8c02eaa20653f74489a40f Mon Sep 17 00:00:00 2001 From: Jeongkyu Shin Date: Mon, 11 May 2026 21:38:27 +0900 Subject: [PATCH] refactor: drop stale and redundant dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit the full direct dependency set in `Cargo.toml`, then remove or replace crates that were dead, redundant with the standard library, or duplicated with an existing dependency. Five crates (`arrayvec`, `ctrlc`, `directories`, `signal-hook 0.4.4`, plus the macOS `objc2`/`block2`/`dispatch2` chain pulled in by `ctrlc`) are now completely gone from the build graph; three more (`lazy_static`, `once_cell`, `fastrand`) remain transitively but are no longer directly referenced by our code. Replacements: - `lazy_static!` → `std::sync::LazyLock` in `src/ui/tui/progress.rs` (edition 2024 already mandates Rust 1.85+). - `once_cell::sync::Lazy` / `OnceCell` → `std::sync::LazyLock` / `OnceLock` across `executor/output_sync.rs`, `pty/terminal.rs`, `utils/logging.rs`, `ssh/ssh_config/env_cache/global.rs`, `ssh/config_cache/global.rs`. - `fastrand::u64(range)` → `rand::random_range(range)` in the three forwarding reconnect-jitter sites; `rand` 0.10 was already a direct dependency. - `directories::ProjectDirs` / `BaseDirs` → `dirs::config_dir` / `dirs::home_dir`, unifying on `dirs` which was already used in 16 other sites and produces equivalent platform paths. - `ctrlc::set_handler` → `tokio::signal::ctrl_c` inside a `tokio::spawn`, guarded by `Handle::try_current` to preserve the previous best-effort semantics in non-runtime contexts. The only caller (`execute_traditional`) is already async, and the matching test runs under `#[tokio::test]`. - `signal-hook = "0.4.4"` → `"0.3"` so we share the same instance `crossterm` already pulls in via `signal-hook-mio`. Our usage (`Signals::new`, `Signals::forever`, `consts::SIGWINCH`) has identical signatures across both major lines, so no source change was needed — only the `Cargo.toml` pin. The `arrayvec` crate was declared but unused anywhere in the source tree, so it is simply dropped. Build, full `cargo test -p bssh` (1303 tests), `cargo clippy --all-targets`, and `cargo fmt --check` all pass. `Cargo.lock` loses 76 lines including the entire `signal-hook 0.4.4` subtree. --- Cargo.lock | 86 ++------------------------ Cargo.toml | 9 +-- src/commands/interactive_signal.rs | 30 +++++---- src/config/loader.rs | 5 +- src/executor/output_sync.rs | 7 +-- src/forwarding/dynamic/forwarder.rs | 2 +- src/forwarding/local.rs | 2 +- src/forwarding/remote.rs | 2 +- src/pty/terminal.rs | 5 +- src/ssh/config_cache/global.rs | 4 +- src/ssh/known_hosts.rs | 3 +- src/ssh/ssh_config/env_cache/global.rs | 7 +-- src/ui/tui/progress.rs | 22 ++++--- src/utils/logging.rs | 5 +- 14 files changed, 54 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15478997..a3922a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,12 +198,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -404,15 +398,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - [[package]] name = "blowfish" version = "0.9.1" @@ -439,7 +424,6 @@ version = "2.1.4" dependencies = [ "anyhow", "argon2", - "arrayvec", "async-compression", "async-trait", "bcrypt", @@ -450,22 +434,17 @@ dependencies = [ "clap", "criterion", "crossterm", - "ctrlc", - "directories", "dirs", - "fastrand", "futures", "glob", "indicatif", "insta", "ipnetwork", - "lazy_static", "libc", "lru 0.17.0", "mockall", "mockito", "nix 0.31.2", - "once_cell", "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", @@ -483,7 +462,7 @@ dependencies = [ "serde_yaml", "serial_test", "shell-words", - "signal-hook 0.4.4", + "signal-hook", "smallvec", "socket2", "ssh-key", @@ -1038,7 +1017,7 @@ dependencies = [ "mio", "parking_lot", "rustix", - "signal-hook 0.3.18", + "signal-hook", "signal-hook-mio", "winapi", ] @@ -1147,17 +1126,6 @@ dependencies = [ "cipher 0.5.1", ] -[[package]] -name = "ctrlc" -version = "3.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" -dependencies = [ - "dispatch2", - "nix 0.31.2", - "windows-sys 0.61.2", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -1337,15 +1305,6 @@ dependencies = [ "ctutils", ] -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "6.0.0" @@ -1367,18 +1326,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -2884,15 +2831,6 @@ dependencies = [ "libc", ] -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -2902,12 +2840,6 @@ dependencies = [ "bitflags 2.11.1", ] -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - [[package]] name = "objc2-system-configuration" version = "0.3.2" @@ -4455,16 +4387,6 @@ dependencies = [ "signal-hook-registry", ] -[[package]] -name = "signal-hook" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-mio" version = "0.2.5" @@ -4473,7 +4395,7 @@ checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", - "signal-hook 0.3.18", + "signal-hook", ] [[package]] @@ -4784,7 +4706,7 @@ dependencies = [ "pest_derive", "phf", "sha2 0.10.9", - "signal-hook 0.3.18", + "signal-hook", "siphasher", "terminfo", "termios", diff --git a/Cargo.toml b/Cargo.toml index 51162665..b4bc3154 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ futures = "0.3.32" async-trait = "0.1.89" indicatif = "0.18.4" rpassword = "7.4.0" -directories = "6.0.0" dirs = "6.0" chrono = { version = "0.4.44", features = ["serde"] } glob = "0.3.3" @@ -45,22 +44,17 @@ whoami = "2.1.1" owo-colors = "4.3.0" unicode-width = "0.2.2" terminal_size = "0.4.4" -once_cell = "1.21.4" zeroize = { version = "1.8.2", features = ["derive"] } secrecy = { version = "0.10.3", features = ["serde"] } rustyline = "18.0.0" crossterm = "0.29" ratatui = "0.30" regex = "1.12.3" -lazy_static = "1.5" -ctrlc = "3.5.2" -signal-hook = "0.4.4" +signal-hook = "0.3" nix = { version = "0.31", features = ["fs", "poll", "process", "signal", "term"] } -arrayvec = "0.7.6" smallvec = "1.15.1" lru = "0.17.0" uuid = { version = "1.23.1", features = ["v4"] } -fastrand = "2.4.1" tokio-util = "0.7.18" socket2 = "0.6" shell-words = "1.1.1" @@ -85,7 +79,6 @@ security-framework = "3.7.0" [dev-dependencies] tempfile = "3.27.0" mockito = "1.7.2" -once_cell = "1.21.4" tokio-test = "0.4" serial_test = "3.4" insta = "1.47" diff --git a/src/commands/interactive_signal.rs b/src/commands/interactive_signal.rs index b5627c63..ab1cab67 100644 --- a/src/commands/interactive_signal.rs +++ b/src/commands/interactive_signal.rs @@ -33,21 +33,29 @@ pub fn reset_interrupt() { INTERRUPTED.store(false, Ordering::Relaxed); } -/// Set up signal handlers for interactive mode +/// Set up signal handlers for interactive mode. +/// +/// Spawns a tokio task that waits for a Ctrl+C signal and flips both the +/// global interrupt flag and the returned shutdown flag. Callers must invoke +/// this from within a tokio runtime; without one the spawn is skipped and the +/// shutdown flag is returned unarmed (matching the prior best-effort semantics). pub fn setup_signal_handlers() -> Result> { let shutdown = Arc::new(AtomicBool::new(false)); let shutdown_clone = Arc::clone(&shutdown); - // Handle Ctrl+C - // Note: set_handler can only be called once per process, so we ignore errors in tests - if let Err(e) = ctrlc::set_handler(move || { - info!("Received Ctrl+C signal"); - INTERRUPTED.store(true, Ordering::Relaxed); - shutdown_clone.store(true, Ordering::Relaxed); - }) { - // In tests, this might already be registered - debug!("Could not set Ctrl-C handler: {}", e); - // Return the shutdown flag anyway for use in the code + if tokio::runtime::Handle::try_current().is_ok() { + tokio::spawn(async move { + match signal::ctrl_c().await { + Ok(()) => { + info!("Received Ctrl+C signal"); + INTERRUPTED.store(true, Ordering::Relaxed); + shutdown_clone.store(true, Ordering::Relaxed); + } + Err(e) => debug!("Failed to install Ctrl-C handler: {}", e), + } + }); + } else { + debug!("No tokio runtime active; Ctrl-C handler not installed"); } Ok(shutdown) diff --git a/src/config/loader.rs b/src/config/loader.rs index 43bcfa02..2781d853 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -15,7 +15,6 @@ //! Configuration loading and priority management. use anyhow::{Context, Result}; -use directories::ProjectDirs; use std::env; use std::path::{Path, PathBuf}; use tokio::fs; @@ -222,8 +221,8 @@ impl Config { .join("bssh") .join("config.yaml"); return Ok(xdg_config); - } else if let Some(proj_dirs) = ProjectDirs::from("", "", "bssh") { - let xdg_config = proj_dirs.config_dir().join("config.yaml"); + } else if let Some(config_dir) = dirs::config_dir() { + let xdg_config = config_dir.join("bssh").join("config.yaml"); return Ok(xdg_config); } diff --git a/src/executor/output_sync.rs b/src/executor/output_sync.rs index 939e4554..caf41616 100644 --- a/src/executor/output_sync.rs +++ b/src/executor/output_sync.rs @@ -15,15 +15,14 @@ //! Thread-safe output synchronization for preventing race conditions //! when multiple nodes write to stdout/stderr simultaneously. -use once_cell::sync::Lazy; use std::io::{self, Write}; -use std::sync::Mutex; +use std::sync::{LazyLock, Mutex}; /// Global stdout mutex to prevent interleaved output -static STDOUT_MUTEX: Lazy> = Lazy::new(|| Mutex::new(io::stdout())); +static STDOUT_MUTEX: LazyLock> = LazyLock::new(|| Mutex::new(io::stdout())); /// Global stderr mutex to prevent interleaved output -static STDERR_MUTEX: Lazy> = Lazy::new(|| Mutex::new(io::stderr())); +static STDERR_MUTEX: LazyLock> = LazyLock::new(|| Mutex::new(io::stderr())); /// Thread-safe println! that prevents output interleaving /// diff --git a/src/forwarding/dynamic/forwarder.rs b/src/forwarding/dynamic/forwarder.rs index 81df23b0..69f21d14 100644 --- a/src/forwarding/dynamic/forwarder.rs +++ b/src/forwarding/dynamic/forwarder.rs @@ -193,7 +193,7 @@ impl DynamicForwarder { ); // Add jitter to avoid thundering herd - let jitter = Duration::from_millis(fastrand::u64( + let jitter = Duration::from_millis(rand::random_range( 0..=retry_delay.as_millis() as u64 / 4, )); retry_delay += jitter; diff --git a/src/forwarding/local.rs b/src/forwarding/local.rs index 4c6c4225..4fb6a4a5 100644 --- a/src/forwarding/local.rs +++ b/src/forwarding/local.rs @@ -232,7 +232,7 @@ impl LocalForwarder { ); // Add jitter to avoid thundering herd - let jitter = Duration::from_millis(fastrand::u64( + let jitter = Duration::from_millis(rand::random_range( 0..=retry_delay.as_millis() as u64 / 4, )); retry_delay += jitter; diff --git a/src/forwarding/remote.rs b/src/forwarding/remote.rs index 54ca6763..68977b75 100644 --- a/src/forwarding/remote.rs +++ b/src/forwarding/remote.rs @@ -241,7 +241,7 @@ impl RemoteForwarder { ); // Add jitter to avoid thundering herd - let jitter = Duration::from_millis(fastrand::u64( + let jitter = Duration::from_millis(rand::random_range( 0..=retry_delay.as_millis() as u64 / 4, )); retry_delay += jitter; diff --git a/src/pty/terminal.rs b/src/pty/terminal.rs index 38bb2b26..46d8f0e0 100644 --- a/src/pty/terminal.rs +++ b/src/pty/terminal.rs @@ -16,7 +16,7 @@ use std::io::Write; use std::sync::{ - Arc, Mutex, + Arc, LazyLock, Mutex, atomic::{AtomicBool, Ordering}, }; @@ -26,11 +26,10 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode}, }; -use once_cell::sync::Lazy; /// Global terminal cleanup synchronization /// Ensures only one cleanup attempt happens even with multiple guards -static TERMINAL_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); +static TERMINAL_MUTEX: LazyLock> = LazyLock::new(|| Mutex::new(())); static RAW_MODE_ACTIVE: AtomicBool = AtomicBool::new(false); /// Terminal state information that needs to be preserved and restored diff --git a/src/ssh/config_cache/global.rs b/src/ssh/config_cache/global.rs index 4c910c07..ab0ae7f7 100644 --- a/src/ssh/config_cache/global.rs +++ b/src/ssh/config_cache/global.rs @@ -14,11 +14,11 @@ use super::config::CacheConfig; use super::manager::SshConfigCache; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use tracing::debug; /// Global SSH config cache instance -pub static GLOBAL_CACHE: Lazy = Lazy::new(|| { +pub static GLOBAL_CACHE: LazyLock = LazyLock::new(|| { let config = CacheConfig::from_env(); debug!( diff --git a/src/ssh/known_hosts.rs b/src/ssh/known_hosts.rs index 613a7614..be45f238 100644 --- a/src/ssh/known_hosts.rs +++ b/src/ssh/known_hosts.rs @@ -13,13 +13,12 @@ // limitations under the License. use super::tokio_client::ServerCheckMethod; -use directories::BaseDirs; use std::path::PathBuf; use std::str::FromStr; /// Get the default known_hosts file path pub fn get_default_known_hosts_path() -> Option { - BaseDirs::new().map(|dirs| dirs.home_dir().join(".ssh").join("known_hosts")) + dirs::home_dir().map(|home| home.join(".ssh").join("known_hosts")) } /// Create a ServerCheckMethod based on strict host key checking mode diff --git a/src/ssh/ssh_config/env_cache/global.rs b/src/ssh/ssh_config/env_cache/global.rs index bc3265e9..d9f661f8 100644 --- a/src/ssh/ssh_config/env_cache/global.rs +++ b/src/ssh/ssh_config/env_cache/global.rs @@ -14,15 +14,14 @@ //! Global environment cache instance management -use once_cell::sync::Lazy; +use std::sync::LazyLock; use std::time::Duration; use super::cache::EnvironmentCache; use super::config::EnvCacheConfig; -// Global environment cache instance using once_cell for thread-safe lazy initialization -/// Global environment variable cache instance -pub static GLOBAL_ENV_CACHE: Lazy = Lazy::new(|| { +/// Global environment variable cache instance (thread-safe lazy init via `LazyLock`). +pub static GLOBAL_ENV_CACHE: LazyLock = LazyLock::new(|| { let config = EnvCacheConfig { ttl: Duration::from_secs( std::env::var("BSSH_ENV_CACHE_TTL") diff --git a/src/ui/tui/progress.rs b/src/ui/tui/progress.rs index caae1c95..42049d3a 100644 --- a/src/ui/tui/progress.rs +++ b/src/ui/tui/progress.rs @@ -17,19 +17,21 @@ //! This module provides heuristics to parse progress information from command output, //! detecting patterns like "78%", "23/100", or common progress bar formats. -use lazy_static::lazy_static; use regex::Regex; +use std::sync::LazyLock; -lazy_static! { - /// Matches percentage patterns like "78%", "100.0%" - static ref PERCENT_PATTERN: Regex = Regex::new(r"(\d+(?:\.\d+)?)\s*%").unwrap(); +/// Matches percentage patterns like "78%", "100.0%" +static PERCENT_PATTERN: LazyLock = + LazyLock::new(|| Regex::new(r"(\d+(?:\.\d+)?)\s*%").unwrap()); - /// Matches fraction patterns like "23/100", "45/50" - static ref FRACTION_PATTERN: Regex = Regex::new(r"(\d+)\s*/\s*(\d+)").unwrap(); +/// Matches fraction patterns like "23/100", "45/50" +static FRACTION_PATTERN: LazyLock = + LazyLock::new(|| Regex::new(r"(\d+)\s*/\s*(\d+)").unwrap()); - /// Matches apt/dpkg progress patterns like "Reading package lists... 78%" - static ref APT_PROGRESS: Regex = Regex::new(r"(?:Reading|Building|Preparing|Unpacking|Setting up|Processing).*?(\d+)%").unwrap(); -} +/// Matches apt/dpkg progress patterns like "Reading package lists... 78%" +static APT_PROGRESS: LazyLock = LazyLock::new(|| { + Regex::new(r"(?:Reading|Building|Preparing|Unpacking|Setting up|Processing).*?(\d+)%").unwrap() +}); /// Parse progress from a text string /// @@ -38,7 +40,7 @@ lazy_static! { /// # Safety /// /// This function is safe from ReDoS attacks because: -/// - All regex patterns are pre-compiled with lazy_static +/// - All regex patterns are pre-compiled with LazyLock /// - Patterns have no catastrophic backtracking (no nested quantifiers) /// - Input is limited to reasonable line lengths /// diff --git a/src/utils/logging.rs b/src/utils/logging.rs index e7371862..c4842c20 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -14,13 +14,12 @@ use crate::ui::tui::log_buffer::LogBuffer; use crate::ui::tui::log_layer::TuiLogLayer; -use once_cell::sync::OnceCell; use std::io::IsTerminal; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use tracing_subscriber::{EnvFilter, prelude::*}; /// Global log buffer for TUI mode -static LOG_BUFFER: OnceCell>> = OnceCell::new(); +static LOG_BUFFER: OnceLock>> = OnceLock::new(); /// Create an environment filter based on verbosity level pub fn create_env_filter(verbosity: u8) -> EnvFilter {