floss-cli 0.1.2

在 Rust 中以子进程方式调用 FLARE FLOSS CLI,并可选解析 -j JSON 输出
Documentation
# floss-cli 使用文档

本文覆盖本库全部公开 API/用法,面向需要在 Rust 中调用 FLOSS CLI 的场景。

## 1. 安装

```toml
[dependencies]
floss-cli = "0.1.2"
```

> 本库基于 tokio 异步运行时;你的调用代码需要处于 tokio runtime 中。

## 2. 运行前提

- 你需要系统中可执行的 FLOSS CLI:`floss`/`floss.exe`,或可用的 `python -m floss`- 本库只封装 CLI,不依赖 FLOSS 的 Python API。

## 3. 创建 FlossCli(入口)

### 3.1 自动探测(推荐)

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    Ok(())
}
```

### 3.2 手动指定

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let _cli = FlossCli::new("floss");
    let _cli = FlossCli::python_module("python"); // python -m floss
    Ok(())
}
```

### 3.3 环境变量覆盖(优先级从高到低)

1. `FLOSS_EXE`:直接指定可执行文件路径
2. `FLOSS_PYTHON` + `FLOSS_PYTHON_ARGS`:指定 Python 解释器与额外参数
3. `PATH` 中的 `floss`/`floss.exe`
4. 默认 Python 候选(Windows:`python/python3/py -3`;其他:`python3/python`
`FLOSS_PYTHON_ARGS` 规则:
- 以空白分隔
- 支持 `'``"` 包裹包含空格的参数
- 仅支持 `"` 作为双引号内转义,其它转义不支持

## 4. 构建一次调用(FlossCommand)

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;

    let cmd = cli
        .command()
        .arg("--only")
        .args(["static", "decoded"])
        .sample("malware.exe");

    let line = cmd.command_line();
    println!("{}", line); // program + args
    Ok(())
}
```

- `.arg(...)` / `.args(...)`:原样透传参数
- `.sample(...)`:自动插入 `--`,避免样本路径被当成参数
- `.command_line()`:返回 `program + args`,用于日志/审计

## 5. 执行模式(全部异步)

### 5.1 标准执行

- `run().await`:捕获 stdout/stderr,检查退出码(非 0 返回错误)
- `run_allow_exit_codes([1, 2]).await`:允许指定退出码视为成功
- `run_raw().await`:捕获 stdout/stderr,不检查退出码
- `run_raw_limited(max_bytes).await`:限制 stdout/stderr 捕获字节数

### 5.2 JSON 解析

- `run_json::<T>().await`:自动补 `-j/--json`(若未传入),反序列化为 `T`
- `run_results().await`:解析为内置 `ResultDocument`

### 5.3 直连终端(适合长时间分析)

- `run_inherit().await`:stdout/stderr 直连终端,不检查退出码
- `run_inherit_checked().await`:直连终端,非 0 退出码报错

### 5.4 子进程托管

- `spawn()`:stdout/stderr 直连终端,返回 `tokio::process::Child`
- `spawn_piped()`:stdout/stderr 为 piped,返回 `Child`

> `spawn/spawn_piped` 不处理超时,也不绑定 Job Object;由调用方自行管理子进程。

## 6. 输出结构与处理

### 6.1 FlossOutput

`run/run_raw/run_allow_exit_codes` 返回 `FlossOutput`:
- `program` / `args` / `status`
- `stdout: Vec<u8>` / `stderr: Vec<u8>`

字符串辅助方法:
- `stdout_lossy()` / `stderr_lossy()`:容错 UTF-8
- `stdout_string()` / `stderr_string()`:严格 UTF-8

### 6.2 FlossOutputLimited

`run_raw_limited` 返回 `FlossOutputLimited`,包含:
- `stdout_truncated` / `stderr_truncated` 截断标记

### 6.3 JSON 结果结构

内置类型:`ResultDocument`(模块 `floss_cli::results`)。
- `decoding_function_scores` 的 key 支持十进制与 `0x` 十六进制地址字符串

## 7. 超时与进程树终止

- `FlossCli::with_timeout(...)``FlossCommand::with_timeout(...)` 设置超时
- 超时后会尝试终止进程树(Best-effort):
  - Windows:优先 Job Object(`KILL_ON_JOB_CLOSE`),失败回退 `taskkill`/`kill`
  - Unix:先 `SIGTERM`(等待 100ms),再升级 `SIGKILL`
- 终止失败会返回 `FlossError::TimedOutKillFailed`

## 8. 常见用法示例

### 8.1 解析 JSON 结果

```rust
use floss_cli::{FlossCli, Result, ResultDocument};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    let doc: ResultDocument = cli
        .command()
        .sample("malware.exe")
        .run_results()
        .await?;

    println!("decoded strings: {}", doc.strings.decoded_strings.len());
    Ok(())
}
```

### 8.2 允许非 0 退出码

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    let out = cli.command().sample("malware.exe").run_allow_exit_codes([1]).await?;
    println!("status: {}", out.status);
    Ok(())
}
```

### 8.3 直连终端(长时间分析)

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    cli.command().arg("-H").run_inherit().await?;
    Ok(())
}
```

### 8.4 输出限量防止占满内存

```rust
use floss_cli::{FlossCli, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    let out = cli.command().sample("malware.exe").run_raw_limited(1024 * 1024).await?;
    println!("stdout truncated: {}", out.stdout_truncated);
    Ok(())
}
```

### 8.5 spawn_piped + 自行读取

```rust
use floss_cli::{FlossCli, Result};
use tokio::io::AsyncReadExt;

#[tokio::main]
async fn main() -> Result<()> {
    let cli = FlossCli::detect().await?;
    let mut child = cli.command().sample("malware.exe").spawn_piped()?;

    let mut stdout = Vec::new();
    if let Some(mut reader) = child.stdout.take() {
        reader.read_to_end(&mut stdout).await?;
    }

    let status = child.wait().await?;
    println!("status: {}", status);
    Ok(())
}
```

## 9. 错误类型与可观测性

`FlossError` 主要变体:
- `AutoDetectFailed`:自动探测失败
- `Io`:进程启动/IO 错误
- `Json`:JSON 反序列化失败
- `NonZeroExit`:非 0 退出码(包含 stdout/stderr)
- `TimedOut` / `TimedOutKillFailed`:超时及终止失败
- `Utf8`:严格 UTF-8 解码失败

`NonZeroExit`/`TimedOut` 会包含 `command`(program + args),便于定位实际执行路径。

## 10. 兼容性说明

- Windows:优先 Job Object 管理进程树;失败回退 `taskkill`/`kill`
- Unix:优先 `SIGTERM``SIGKILL`
- `spawn/spawn_piped` 不接管超时与进程树终止