fix: address code review issues across codebase

- Make --quiet flag functional by threading it through execute_via_bridge()
- Fix --sort hyphen ambiguity with allow_hyphen_values in clap
- Replace deprecated atty crate with std::io::IsTerminal
- Use config.default_limit as fallback when user doesn't specify --limit
- Respect per-command --json flag in QueryOptions format detection
- Remove dead src/daemon/ module and src/ipc/transport.rs
- Deduplicate headless_script lookup (GhidraClient delegates to bridge)
- Fix stop_bridge() SIGTERM race with graceful shutdown wait loop
- Extend config set to support all config keys
- Clean up dead code warnings and remove blanket #![allow(dead_code)]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Kiselev
2026-02-05 12:07:52 -08:00
co-authored by Claude Opus 4.6
parent 2447137ffc
commit 31bbefee33
18 changed files with 71 additions and 362 deletions
-2
View File
@@ -24,7 +24,6 @@ See @AGENTS.md for agent-specific instructions.
|------|------|
| `src/ghidra/` | Bridge management, Ghidra setup/installation, Java bridge script |
| `src/ipc/` | TCP client, protocol definitions, transport helpers |
| `src/daemon/` | Thin wrapper over bridge.rs (kept for API compatibility) |
| `src/format/` | Handling output format conversion (Table, Compact, JSON, CSV, etc.) |
| `tests/` | Writing integration or unit tests |
@@ -33,7 +32,6 @@ See @AGENTS.md for agent-specific instructions.
| What | When |
|------|------|
| `CHANGELOG.md` | Reviewing version history and release notes |
| `src/daemon/README.md` | Understanding daemon wrapper and its delegation to bridge.rs |
| `src/ghidra/README.md` | Understanding bridge lifecycle, PID file sequence, TOCTOU elimination, BridgeClient adoption |
| `src/ipc/README.md` | Understanding TCP wire format, BridgeClient API, single implementation rationale |
| `tests/README.md` | Understanding test structure and conventions |
Generated
-21
View File
@@ -108,17 +108,6 @@ dependencies = [
"wait-timeout",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -754,7 +743,6 @@ version = "0.1.3"
dependencies = [
"anyhow",
"assert_cmd",
"atty",
"chrono",
"clap",
"comfy-table",
@@ -831,15 +819,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hmac"
version = "0.12.1"
-1
View File
@@ -65,7 +65,6 @@ strsim = "0.11"
# Cross-platform support
dunce = "1.0" # Windows path handling
atty = "0.2" # TTY detection
# Setup command dependencies
reqwest = { version = "0.11", features = ["json", "stream", "rustls-tls"] }
+2 -2
View File
@@ -209,7 +209,7 @@ pub struct QueryArgs {
pub offset: Option<usize>,
/// Sort by field(s) (comma-separated, prefix with - for descending)
#[arg(long)]
#[arg(long, allow_hyphen_values = true)]
pub sort: Option<String>,
/// Only return count
@@ -797,7 +797,7 @@ pub struct QueryOptions {
#[arg(long)]
pub offset: Option<usize>,
#[arg(long)]
#[arg(long, allow_hyphen_values = true)]
pub sort: Option<String>,
#[arg(long)]
-16
View File
@@ -1,5 +1,3 @@
#![allow(dead_code)]
use crate::error::{GhidraError, Result};
use serde::{Deserialize, Serialize};
use std::fs;
@@ -158,25 +156,11 @@ impl Config {
None
}
pub fn get_timeout(&self) -> u64 {
std::env::var("GHIDRA_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.or(self.timeout)
.unwrap_or(300)
}
pub fn get_default_program(&self) -> Option<String> {
std::env::var("GHIDRA_DEFAULT_PROGRAM")
.ok()
.or_else(|| self.default_program.clone())
}
pub fn get_default_project(&self) -> Option<String> {
std::env::var("GHIDRA_DEFAULT_PROJECT")
.ok()
.or_else(|| self.default_project.clone())
}
}
#[cfg(test)]
-71
View File
@@ -1,71 +0,0 @@
# Bridge Management Module (`src/daemon/`)
Thin compatibility wrapper over `src/ghidra/bridge.rs`. All logic lives in `bridge.rs`; this module re-exports functions for callers that still reference `daemon::*`.
## Why This Module Exists
Historical: the original architecture had a separate Rust daemon process. Now the "daemon" is just the long-running Java bridge process. This module was kept to avoid breaking the `daemon::` import paths used by some callers and tests.
## API Surface
| Function | Delegates To |
|----------|-------------|
| `ensure_bridge(config, mode)` | `bridge::ensure_bridge_running()` |
| `start_bridge(config, mode)` | `bridge::start_bridge()` |
| `stop_bridge(project_path)` | `bridge::stop_bridge()` |
| `get_bridge_status(project_path)` | `bridge::bridge_status()` |
| `is_bridge_running(project_path) -> Option<u16>` | `bridge::is_bridge_running()` |
`is_bridge_running` returns `Option<u16>` (the port) instead of `bool`. Callers use the returned port directly, eliminating TOCTOU races between liveness checks and port file reads.
## Architecture
See `src/ghidra/README.md` for the canonical bridge architecture documentation, including:
- Bridge lifecycle (spawn, PID write, ready signal, port file)
- Liveness detection (port file + PID alive + BridgeClient ping)
- Auto-start behavior for Import, Quick, and Analyze commands
- TOCTOU elimination via `is_bridge_running() -> Option<u16>`
## Liveness Detection
Bridge liveness is verified in `bridge::is_bridge_running()` and `bridge::bridge_status()`:
1. **Port file exists** -- `bridge-{hash}.port` in `~/.local/share/ghidra-cli/`
2. **PID alive** -- `kill(pid, 0)` succeeds on Unix
3. **TCP reachable** -- `TcpStream::connect("127.0.0.1:{port}")` succeeds (in `is_bridge_running`)
4. **Ping verified** -- `BridgeClient::new(port).ping()` returns true (in `bridge_status` and `verify_bridge`)
`is_bridge_running` uses a raw TCP probe (step 3) for speed. `bridge_status` uses BridgeClient ping (step 4) for stronger verification. The `verify_bridge()` helper in `main.rs` is called after connecting to an existing bridge to confirm it responds to commands.
## Command Flow
All commands flow through the OutputFormat formatter in `main.rs`:
1. Import/Quick produce structured `serde_json::Value` directly
2. Analyze and query commands go through `execute_via_bridge()` which returns `serde_json::Value`
3. The formatter at the end of `run_with_bridge()` applies Table, JSON, or JsonCompact formatting
4. Progress messages use `eprintln!()` (stderr), structured output uses `println!()` (stdout)
## Auto-Start Behavior
| Command | Bridge Not Running | Bridge Running |
|---------|-------------------|----------------|
| Import | Start bridge in Import mode | Import via `client.import_binary()`, switch program |
| Quick | Start bridge in Import mode, analyze | Import via running bridge, switch program, analyze |
| Analyze | Start bridge in Process mode, analyze via `execute_via_bridge` | Analyze via `execute_via_bridge` |
| Query commands | Start bridge in Process mode | Connect and query |
Quick reuses a running bridge by importing through the bridge's `import` command and switching to the new program, rather than starting a fresh bridge in Import mode.
Analyze has no special-case handler. It falls through to the generic dispatch path in `execute_via_bridge()`.
## Key Differences from Original Architecture
- No separate Rust daemon process; the Java bridge IS the persistent server
- `BridgeClient` (in `src/ipc/client.rs`) is the single TCP command implementation; bridge.rs uses raw `TcpStream` only for lightweight connect probes, not for sending commands
- `is_bridge_running()` returns `Option<u16>` (port), not `bool`
- `bridge_status()` uses `BridgeClient.ping()` instead of raw `TcpStream::connect`
- `stop_bridge()` uses `BridgeClient.shutdown()` for graceful shutdown
- Rust writes PID file immediately after spawn (before Java ready signal)
- Failed starts kill orphaned child processes and clean up stale files
-71
View File
@@ -1,71 +0,0 @@
//! Bridge management module.
//!
//! Manages the lifecycle of the Java GhidraCliBridge process.
//! The "daemon" is just the long-running Ghidra/Java bridge process -
//! there is no separate Rust daemon. The CLI connects directly to
//! the bridge via TCP.
use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::ghidra::bridge::{self, BridgeStartMode, BridgeStatus};
/// Bridge configuration (replaces old DaemonConfig).
#[allow(dead_code)]
pub struct BridgeConfig {
/// Path to the Ghidra project directory
pub project_path: PathBuf,
/// Ghidra installation directory
pub ghidra_install_dir: PathBuf,
}
/// Ensure a bridge is running for the given project.
/// If import mode, starts with the binary. If process mode, opens existing program.
/// Returns the port number for connecting.
#[allow(dead_code)]
pub fn ensure_bridge(config: &BridgeConfig, mode: BridgeStartMode) -> Result<u16> {
bridge::ensure_bridge_running(&config.project_path, &config.ghidra_install_dir, mode)
}
/// Start a new bridge for the given project.
/// Returns the port number for connecting.
#[allow(dead_code)]
pub fn start_bridge(config: &BridgeConfig, mode: BridgeStartMode) -> Result<u16> {
bridge::start_bridge(&config.project_path, &config.ghidra_install_dir, mode)
}
/// Stop the bridge for a project.
#[allow(dead_code)]
pub fn stop_bridge(project_path: &Path) -> Result<()> {
bridge::stop_bridge(project_path)
}
/// Get bridge status for a project.
#[allow(dead_code)]
pub fn get_bridge_status(project_path: &Path) -> Result<BridgeStatus> {
bridge::bridge_status(project_path)
}
/// Check if a bridge is running for a project.
/// Returns `Some(port)` if running, `None` otherwise.
#[allow(dead_code)]
pub fn is_bridge_running(project_path: &Path) -> Option<u16> {
bridge::is_bridge_running(project_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_config() {
let config = BridgeConfig {
project_path: PathBuf::from("/test/project"),
ghidra_install_dir: PathBuf::from("/opt/ghidra"),
};
assert_eq!(config.project_path, PathBuf::from("/test/project"));
assert_eq!(config.ghidra_install_dir, PathBuf::from("/opt/ghidra"));
}
}
+1 -26
View File
@@ -1,5 +1,3 @@
#![allow(dead_code)]
use thiserror::Error;
#[derive(Error, Debug)]
@@ -7,27 +5,16 @@ pub enum GhidraError {
#[error("Ghidra installation not found. Set GHIDRA_INSTALL_DIR or run 'ghidra init'")]
GhidraNotFound,
#[error("Ghidra project not found: {0}")]
ProjectNotFound(String),
#[error("Program not found: {0}")]
ProgramNotFound(String),
#[error("Failed to execute Ghidra: {0}")]
ExecutionFailed(String),
#[error("Failed to parse filter: {0}")]
FilterParseError(String),
#[error("Invalid filter expression: {0}")]
InvalidFilter(String),
#[error("Field not found: {0}")]
FieldNotFound(String),
#[error("Invalid format: {0}")]
InvalidFormat(String),
#[allow(dead_code)]
#[error("Invalid data type: {0}")]
InvalidDataType(String),
@@ -42,18 +29,6 @@ pub enum GhidraError {
#[error("YAML error: {0}")]
YamlError(#[from] serde_yaml::Error),
#[error("Command failed: {0}")]
CommandFailed(String),
#[error("Invalid address: {0}")]
InvalidAddress(String),
#[error("Analysis timeout after {0} seconds")]
Timeout(u64),
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, GhidraError>;
-24
View File
@@ -71,15 +71,7 @@ pub enum Value {
Hex(u64),
}
#[allow(dead_code)]
impl Value {
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
@@ -88,22 +80,6 @@ impl Value {
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
Value::Number(n) => Some(*n as i64),
Value::Hex(h) => Some(*h as i64),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Boolean(b) => Some(*b),
_ => None,
}
}
}
pub struct Filter {
-12
View File
@@ -4,7 +4,6 @@ use serde::Serialize;
use serde_json::Value as JsonValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum OutputFormat {
Full,
Compact,
@@ -23,7 +22,6 @@ pub enum OutputFormat {
C,
}
#[allow(dead_code)]
impl OutputFormat {
pub fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
@@ -46,16 +44,6 @@ impl OutputFormat {
}
}
pub fn is_human_friendly(&self) -> bool {
matches!(self, Self::Full | Self::Compact | Self::Table | Self::Tree)
}
pub fn is_machine_friendly(&self) -> bool {
matches!(
self,
Self::Json | Self::JsonCompact | Self::JsonStream | Self::Csv | Self::Tsv
)
}
}
pub trait Formatter {
+11 -33
View File
@@ -7,7 +7,7 @@
use std::io::{BufRead, BufReader};
use std::net::TcpStream;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::process::{Command, Stdio};
use std::time::Duration;
use anyhow::{Context, Result};
@@ -327,8 +327,16 @@ pub fn stop_bridge(project_path: &Path) -> Result<()> {
}
}
// If PID file exists, kill the process as fallback
// Wait for the process to exit gracefully, then force-kill if needed
if let Ok(Some(pid)) = read_pid_file(project_path) {
// Wait up to 3 seconds for graceful exit
for _ in 0..30 {
if !is_pid_alive(pid) {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
// If still alive after waiting, force kill
if is_pid_alive(pid) {
warn!("Killing bridge process {} as fallback", pid);
#[cfg(unix)]
@@ -388,7 +396,7 @@ pub enum BridgeStatus {
}
/// Find the analyzeHeadless script.
fn find_headless_script(ghidra_install_dir: &Path) -> Result<PathBuf> {
pub fn find_headless_script(ghidra_install_dir: &Path) -> Result<PathBuf> {
let support_dir = ghidra_install_dir.join("support");
#[cfg(unix)]
@@ -405,33 +413,3 @@ fn find_headless_script(ghidra_install_dir: &Path) -> Result<PathBuf> {
}
}
/// Convenience wrapper for wait_timeout on Child
trait ChildExt {
fn wait_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<Option<std::process::ExitStatus>>;
}
impl ChildExt for Child {
fn wait_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<Option<std::process::ExitStatus>> {
use std::thread;
use std::time::Instant;
let start = Instant::now();
loop {
match self.try_wait()? {
Some(status) => return Ok(Some(status)),
None => {
if start.elapsed() >= timeout {
return Ok(None);
}
thread::sleep(Duration::from_millis(100));
}
}
}
}
}
+2 -46
View File
@@ -1,5 +1,3 @@
#![allow(dead_code)]
pub mod bridge;
pub mod setup;
@@ -9,7 +7,6 @@ use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct GhidraClient {
config: Config,
install_dir: PathBuf,
project_dir: PathBuf,
}
@@ -25,37 +22,14 @@ impl GhidraClient {
}
Ok(Self {
config,
install_dir,
project_dir,
})
}
pub fn install_dir(&self) -> &PathBuf {
&self.install_dir
}
pub fn get_headless_script(&self) -> PathBuf {
let support_dir = self.install_dir.join("support");
#[cfg(target_os = "windows")]
{
support_dir.join("analyzeHeadless.bat")
}
#[cfg(not(target_os = "windows"))]
{
support_dir.join("analyzeHeadless")
}
}
pub fn verify_installation(&self) -> Result<()> {
let headless = self.get_headless_script();
if !headless.exists() {
return Err(GhidraError::GhidraNotFound);
}
bridge::find_headless_script(&self.install_dir)
.map_err(|_| GhidraError::GhidraNotFound)?;
Ok(())
}
@@ -82,24 +56,6 @@ impl GhidraClient {
Ok(())
}
fn get_scripts_dir(&self) -> Result<PathBuf> {
let config_dir = dirs::config_dir().ok_or_else(|| {
GhidraError::ConfigError("Could not determine config directory".to_string())
})?;
let scripts_dir = config_dir.join("ghidra-cli").join("scripts");
if !scripts_dir.exists() {
std::fs::create_dir_all(&scripts_dir)?;
}
Ok(scripts_dir)
}
pub fn get_install_dir(&self) -> &Path {
&self.install_dir
}
pub fn get_project_dir(&self) -> &Path {
&self.project_dir
}
+1
View File
@@ -8,6 +8,7 @@ use std::path::{Path, PathBuf};
/// GitHub release asset information
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct GithubAsset {
name: String,
browser_download_url: String,
-1
View File
@@ -8,7 +8,6 @@ Single TCP implementation for CLI-to-bridge communication. All command sending g
|------|---------|
| `client.rs` | `BridgeClient` -- the canonical client for all bridge commands |
| `protocol.rs` | `BridgeRequest` / `BridgeResponse` wire format structs |
| `transport.rs` | Utility: `port_reachable()` TCP probe |
## BridgeClient
-1
View File
@@ -5,4 +5,3 @@
pub mod client;
pub mod protocol;
pub mod transport;
-14
View File
@@ -1,14 +0,0 @@
//! Transport helpers for bridge TCP communication.
//!
//! The Java bridge uses newline-delimited JSON over TCP.
//! This module provides minimal transport utilities.
use std::net::TcpStream;
/// Check if a TCP port is reachable on localhost.
#[allow(dead_code)]
pub fn port_reachable(port: u16) -> bool {
TcpStream::connect(format!("127.0.0.1:{}", port))
.map(|_| true)
.unwrap_or(false)
}
+48 -20
View File
@@ -1,6 +1,5 @@
mod cli;
mod config;
mod daemon;
mod error;
mod filter;
mod format;
@@ -17,6 +16,7 @@ use ghidra::bridge::{self, BridgeStartMode, BridgeStatus};
use ghidra::GhidraClient;
use ipc::client::BridgeClient;
use query::Query;
use std::io::IsTerminal;
use std::path::PathBuf;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
@@ -443,7 +443,9 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
// Switch to the newly imported program
client.open_program(&program_name)?;
eprintln!("Successfully imported as: {}", program_name);
if !cli.quiet {
eprintln!("Successfully imported as: {}", program_name);
}
json!({
"command": "import",
"program": program_name,
@@ -452,7 +454,9 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
})
} else {
// No bridge running - start one in import mode
eprintln!("Starting Ghidra bridge...");
if !cli.quiet {
eprintln!("Starting Ghidra bridge...");
}
let port = bridge::ensure_bridge_running(
&project_path,
&ghidra_install_dir,
@@ -468,7 +472,9 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
.unwrap_or("unknown")
.to_string()
});
eprintln!("Successfully imported as: {}", program_name);
if !cli.quiet {
eprintln!("Successfully imported as: {}", program_name);
}
json!({
"command": "import",
"program": program_name,
@@ -496,13 +502,17 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
BridgeStartMode::Project
};
eprintln!("Starting Ghidra bridge...");
if !cli.quiet {
eprintln!("Starting Ghidra bridge...");
}
let port = bridge::ensure_bridge_running(
&project_path,
&ghidra_install_dir,
mode,
)?;
eprintln!("Bridge ready.");
if !cli.quiet {
eprintln!("Bridge ready.");
}
BridgeClient::new(port)
};
@@ -521,12 +531,14 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
}
}
execute_via_bridge(&client, &cli.command)?
execute_via_bridge(&client, &cli.command, cli.quiet, config.default_limit)?
}
};
// Check for .NET decompilation and warn
check_dotnet_decompile_warning(&cli.command, &result);
if !cli.quiet {
check_dotnet_decompile_warning(&cli.command, &result);
}
// Determine output format: explicit -o flag > --json/--pretty > TTY detection
let opts = extract_query_options(&cli.command);
@@ -542,10 +554,10 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
fmt
} else if cli.pretty {
OutputFormat::Json
} else if cli.json {
} else if cli.json || opts.as_ref().map_or(false, |o| o.json) {
OutputFormat::JsonCompact
} else {
auto_detect_format(atty::is(atty::Stream::Stdout))
auto_detect_format(std::io::stdout().is_terminal())
};
// Unwrap bridge response envelopes before formatting
@@ -574,15 +586,21 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
fn execute_via_bridge(
client: &BridgeClient,
command: &Commands,
quiet: bool,
default_limit: Option<usize>,
) -> anyhow::Result<serde_json::Value> {
use serde_json::json;
match command {
// Analyze shares the generic dispatch path with all query commands
Commands::Analyze(_) => {
eprintln!("Analyzing...");
if !quiet {
eprintln!("Analyzing...");
}
let result = client.analyze()?;
eprintln!("Analysis complete!");
if !quiet {
eprintln!("Analysis complete!");
}
Ok(json!({
"command": "analyze",
"status": "success",
@@ -590,8 +608,8 @@ fn execute_via_bridge(
}))
}
Commands::Query(args) => match args.data_type.as_str() {
"functions" => client.list_functions(args.limit, None),
"strings" => client.list_strings(args.limit),
"functions" => client.list_functions(args.limit.or(default_limit), None),
"strings" => client.list_strings(args.limit.or(default_limit)),
"imports" => client.list_imports(),
"exports" => client.list_exports(),
"memory" => client.memory_map(),
@@ -602,7 +620,7 @@ fn execute_via_bridge(
use cli::FunctionCommands;
match cmd {
FunctionCommands::List(opts) => {
client.list_functions(opts.limit, None)
client.list_functions(opts.limit.or(default_limit), None)
}
FunctionCommands::Decompile(args) => client.decompile(args.target.clone()),
FunctionCommands::Get(args) => {
@@ -636,7 +654,7 @@ fn execute_via_bridge(
Commands::Strings(cmd) => {
use cli::StringsCommands;
match cmd {
StringsCommands::List(opts) => client.list_strings(opts.limit),
StringsCommands::List(opts) => client.list_strings(opts.limit.or(default_limit)),
StringsCommands::Refs(args) => client.xrefs_to(args.string.clone()),
}
}
@@ -672,9 +690,9 @@ fn execute_via_bridge(
DumpCommands::Imports(_) => client.list_imports(),
DumpCommands::Exports(_) => client.list_exports(),
DumpCommands::Functions(opts) => {
client.list_functions(opts.limit, None)
client.list_functions(opts.limit.or(default_limit), None)
}
DumpCommands::Strings(opts) => client.list_strings(opts.limit),
DumpCommands::Strings(opts) => client.list_strings(opts.limit.or(default_limit)),
}
}
Commands::Summary(_) => client.program_info(),
@@ -725,7 +743,7 @@ fn execute_via_bridge(
Commands::Type(cmd) => {
use cli::TypeCommands;
match cmd {
TypeCommands::List(opts) => client.type_list(opts.limit),
TypeCommands::List(opts) => client.type_list(opts.limit.or(default_limit)),
TypeCommands::Get(args) => client.type_get(&args.name),
TypeCommands::Create(args) => client.type_create(&args.definition),
TypeCommands::Apply(args) => client.type_apply(&args.address, &args.type_name),
@@ -745,7 +763,7 @@ fn execute_via_bridge(
Commands::Graph(cmd) => {
use cli::GraphCommands;
match cmd {
GraphCommands::Calls(opts) => client.graph_calls(opts.limit),
GraphCommands::Calls(opts) => client.graph_calls(opts.limit.or(default_limit)),
GraphCommands::Callers(args) => client.graph_callers(&args.function, args.depth),
GraphCommands::Callees(args) => client.graph_callees(&args.function, args.depth),
GraphCommands::Export(args) => client.graph_export(&args.format),
@@ -1134,6 +1152,16 @@ fn handle_config_command(cmd: cli::ConfigCommands) -> anyhow::Result<()> {
})?;
config.timeout = Some(timeout);
}
"ghidra_install_dir" => config.ghidra_install_dir = Some(PathBuf::from(value)),
"ghidra_project_dir" => config.ghidra_project_dir = Some(PathBuf::from(value)),
"default_program" => config.default_program = Some(value),
"default_project" => config.default_project = Some(value),
"default_limit" => {
let limit: usize = value.parse().map_err(|_| {
GhidraError::ConfigError("Invalid limit value".to_string())
})?;
config.default_limit = Some(limit);
}
_ => {
anyhow::bail!("Unknown config key: {}", key);
}
+6 -1
View File
@@ -63,8 +63,8 @@ pub struct Query {
pub count_only: bool,
}
#[allow(dead_code)]
impl Query {
#[allow(dead_code)]
pub fn new(data_type: DataType) -> Self {
Self {
data_type,
@@ -114,26 +114,31 @@ impl Query {
}))
}
#[allow(dead_code)]
pub fn with_filter(mut self, filter: Filter) -> Self {
self.filter = Some(filter);
self
}
#[allow(dead_code)]
pub fn with_format(mut self, format: OutputFormat) -> Self {
self.format = format;
self
}
#[allow(dead_code)]
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
#[allow(dead_code)]
pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
#[allow(dead_code)]
pub fn count_only(mut self) -> Self {
self.count_only = true;
self