fix: 修复LD_PRELOAD bash -c执行时无法回显的bug, 修复窗口大小改变时无法同步子进程的bug
This commit is contained in:
parent
3be4ba6e4b
commit
cf33cff0a5
|
|
@ -1,2 +1,3 @@
|
||||||
build/
|
build/
|
||||||
output.txt
|
output.txt
|
||||||
|
logs/
|
||||||
8
Makefile
8
Makefile
|
|
@ -29,6 +29,11 @@ ifeq ($(HOOK),1)
|
||||||
CFLAGS += -DHOOK
|
CFLAGS += -DHOOK
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# 如果跳过检查,只需执行 make NO_CONFIG_CHECK=1
|
||||||
|
ifeq ($(NO_CONFIG_CHECK),1)
|
||||||
|
CFLAGS += -DNO_CONFIG_CHECK
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all clean debug hook rebuild pre_build
|
.PHONY: all clean debug hook rebuild pre_build
|
||||||
|
|
||||||
all: pre_build $(TARGET) $(HOOK_TARGET)
|
all: pre_build $(TARGET) $(HOOK_TARGET)
|
||||||
|
|
@ -40,6 +45,9 @@ endif
|
||||||
ifeq ($(HOOK),1)
|
ifeq ($(HOOK),1)
|
||||||
@echo "Building with hook flags..."
|
@echo "Building with hook flags..."
|
||||||
endif
|
endif
|
||||||
|
ifeq ($(NO_CONFIG_CHECK),1)
|
||||||
|
@echo "Building with NO_CONFIG_CHECK defined..."
|
||||||
|
endif
|
||||||
|
|
||||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
||||||
@mkdir -p $(BUILD_DIR)
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
|
|
||||||
304
README.md
304
README.md
|
|
@ -1,131 +1,233 @@
|
||||||
# execve 拦截器
|
# Execve Hook Project
|
||||||
|
|
||||||
## 简介
|
本项目是一个基于 `LD_PRELOAD` 机制的系统调用拦截库,主要用于监控和控制 Linux 系统中的命令执行(`execve`)。它能够拦截命令执行请求,检查配置规则,记录执行日志,甚至通过 PTY(伪终端)捕获命令的输入输出。
|
||||||
|
|
||||||
本项目是一个通过动态链接库预加载 (LD\_PRELOAD) 方式实现的 `execve` 系统调用拦截器。它可以根据预定义的规则,在程序执行指定的命令时进行警告、阻止或记录操作。
|
## 核心逻辑概述
|
||||||
|
|
||||||
**主要功能:**
|
整个 C 代码库的逻辑可以分为以下几个核心模块:
|
||||||
|
|
||||||
* **命令拦截与告警:** 当执行配置中指定的命令时,可以显示警告信息并询问用户是否继续执行。
|
### 1. `execve` 拦截器 (`src/execve_interceptor.c`)
|
||||||
* **命令阻止:** 可以完全阻止执行配置中指定的命令。
|
|
||||||
* **参数匹配:** 支持基于命令参数进行更细粒度的规则匹配。
|
|
||||||
* **配置热加载:** 能够检测到配置文件修改并自动重新加载规则。
|
|
||||||
* **日志记录:** 记录所有通过 `execve` 执行的命令及其参数。
|
|
||||||
* **输出重定向与错误检测:** 将执行命令的标准输出和错误输出记录到日志文件,并能在检测到错误时发出提示。
|
|
||||||
* **仅拦截终端 Shell:** 默认只在终端 Shell (如 bash, zsh, fish, sh) 中拦截 `execve` 调用,避免影响其他程序。
|
|
||||||
* **功能开关:** 可以通过配置文件中的 `enabled` 字段全局启用或禁用拦截功能。
|
|
||||||
|
|
||||||
## 安装
|
这是项目的核心入口。
|
||||||
|
|
||||||
1. **前提条件:**
|
- **拦截机制**: 通过 `LD_PRELOAD` 预加载动态库,覆盖系统默认的 `execve` 函数。
|
||||||
* 安装 `gcc` 等编译工具。
|
- **主要流程**:
|
||||||
* 安装 `json-c` 库 (开发版本)。在 Debian/Ubuntu 上可以使用 `sudo apt-get install libjson-c-dev` 安装,在 CentOS/RHEL 上可以使用 `sudo yum install json-c-devel` 安装。
|
1. **环境清理**: 在调用前移除 `LD_PRELOAD` 环境变量,防止对子进程造成非预期的递归影响。
|
||||||
|
2. **配置加载**: 调用 `config.c` 从共享内存中加载配置。
|
||||||
|
3. **规则匹配**: 使用 `rules.c` 检查当前执行的命令是否命中特定规则。
|
||||||
|
4. **行为决策**: 根据规则决定是否需要拦截、记录日志或通过 PTY 执行。
|
||||||
|
5. **执行原函数**: 最终调用原始的 `execve` 系统调用执行目标程序。
|
||||||
|
|
||||||
2. **编译:**
|
```mermaid
|
||||||
```bash
|
sequenceDiagram
|
||||||
gcc -shared -fPIC execve_interceptor.c -o execve_interceptor.so -ldl -ljson-c
|
participant User as User/Shell
|
||||||
```
|
participant Hook as execve_interceptor
|
||||||
* 确保你的代码文件名为 `execve_interceptor.c`。
|
participant Config as Config (Shm)
|
||||||
|
participant Rules as Rules Engine
|
||||||
|
participant PTY as PTY/IO Handler
|
||||||
|
participant Sys as Original execve
|
||||||
|
|
||||||
3. **创建配置和日志目录 (如果不存在):**
|
User->>Hook: execve(filename, argv, envp)
|
||||||
```bash
|
Hook->>Hook: Remove LD_PRELOAD
|
||||||
mkdir -p config logs
|
Hook->>Hook: dlsym(RTLD_NEXT, "execve")
|
||||||
|
|
||||||
|
Hook->>Config: load_config()
|
||||||
|
Config-->>Hook: ConfigData*
|
||||||
|
|
||||||
|
alt Not Terminal Shell or Config Disabled
|
||||||
|
Hook->>Sys: orig_execve(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
loop Check Rules
|
||||||
|
Hook->>Rules: args_match(argv, rule)
|
||||||
|
Rules-->>Hook: Match Result
|
||||||
|
end
|
||||||
|
|
||||||
|
alt Rule Type: SKIP
|
||||||
|
Hook->>Sys: orig_execve(...)
|
||||||
|
else Rule Type: WARN
|
||||||
|
Hook->>User: Print Warning
|
||||||
|
User->>Hook: Confirm (Y/N)
|
||||||
|
alt No
|
||||||
|
Hook->>User: Exit
|
||||||
|
end
|
||||||
|
else Rule Type: ERROR
|
||||||
|
Hook->>User: Print Error
|
||||||
|
Hook->>User: Exit
|
||||||
|
else Rule Type: LOG/DEFAULT (Match Found)
|
||||||
|
Hook->>PTY: dupIO(filename, argv, envp)
|
||||||
|
PTY->>PTY: forkpty()
|
||||||
|
par Child Process
|
||||||
|
PTY->>Sys: orig_execve(...)
|
||||||
|
and Parent Process
|
||||||
|
PTY->>PTY: handle_io() (Capture Output)
|
||||||
|
PTY->>User: Forward Output
|
||||||
|
end
|
||||||
|
else No Match
|
||||||
|
Hook->>Sys: orig_execve(...)
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **创建配置文件:**
|
### 2. 配置管理 (`src/config.c`)
|
||||||
* 在 `config` 目录下创建 `execve_rules.json` 文件,并按照以下格式配置规则:
|
|
||||||
|
|
||||||
```json
|
- **共享内存**: 配置数据不直接从文件读取,而是从共享内存(Shared Memory)中加载。
|
||||||
{
|
- **机制**: 这意味着有一个外部进程(通常是配套的 Go 服务)负责解析配置文件(如 JSON/YAML)并将结构化数据写入共享内存,C 代码只需高效读取即可。
|
||||||
"enabled": true,
|
|
||||||
"rules": [
|
### 3. 规则引擎 (`src/rules.c`)
|
||||||
{
|
|
||||||
"cmd": "nvidia-smi",
|
- **功能**: 负责解析和匹配命令执行规则。
|
||||||
"type": "warn",
|
- **匹配逻辑**: 检查命令名称(`filename`)以及参数列表(`argv`)是否符合预定义的条件。
|
||||||
"msg": "在沐曦环境下请执行mx-smi"
|
|
||||||
},
|
### 4. I/O 捕获与 PTY (`src/pty_dup.c`)
|
||||||
{
|
|
||||||
"cmd": "rm",
|
- **目的**: 为了完整记录交互式命令的输入输出(Session Recording)。
|
||||||
"type": "error",
|
- **实现**:
|
||||||
"msg": "Error: rm command is forbidden"
|
- 使用 `forkpty` 创建一个新的伪终端会话。
|
||||||
},
|
- 在子进程中执行目标命令。
|
||||||
{
|
- 父进程负责在主终端和伪终端之间转发数据(`stdin`, `stdout`, `stderr`)。
|
||||||
"cmd": "pip",
|
- 单独处理 `stderr` 管道以区分标准输出和错误输出。
|
||||||
"type": "warn",
|
- 处理信号(如 `SIGINT`, `SIGCHLD`)以确保子进程正确退出。
|
||||||
"msg": "使用pip安装torch时,请注意使用厂家支持版本",
|
|
||||||
"args": ["install", "torch"]
|
```mermaid
|
||||||
}
|
flowchart TD
|
||||||
]
|
A[dupIO Called] --> B{forkpty}
|
||||||
}
|
B -- Error --> C[Exit]
|
||||||
|
B -- Child Process --> D[Setup Signals]
|
||||||
|
D --> E[Close Pipe Read End]
|
||||||
|
E --> F[Dup2 Stderr -> Pipe Write End]
|
||||||
|
F --> G[Return to execve_interceptor]
|
||||||
|
G --> H[Call orig_execve]
|
||||||
|
|
||||||
|
B -- Parent Process --> I[Close Pipe Write End]
|
||||||
|
I --> J[Get Log File Paths]
|
||||||
|
J --> K[handle_io Loop]
|
||||||
|
|
||||||
|
subgraph IO_Loop [IO Handling Loop]
|
||||||
|
L{Select/Poll}
|
||||||
|
L -- Stdin Data --> M[Write to Master PTY]
|
||||||
|
L -- Master PTY Data --> N[Read Output]
|
||||||
|
N --> O[Write to Stdout]
|
||||||
|
N --> P[Write to Log File]
|
||||||
|
L -- Stderr Pipe Data --> Q[Read Stderr]
|
||||||
|
Q --> R[Write to Stderr]
|
||||||
|
Q --> S[Write to Log File]
|
||||||
|
end
|
||||||
|
|
||||||
|
K --> IO_Loop
|
||||||
|
IO_Loop -- Child Exit --> T[Check Exit Status]
|
||||||
|
T -- Error Code --> U[Send Params to Socket]
|
||||||
|
T -- Success --> V[Exit Parent]
|
||||||
```
|
```
|
||||||
|
|
||||||
* `enabled`: 布尔值,用于启用或禁用整个拦截器功能。
|
### 5. Write 调用拦截 (`src/hook_write.c`)
|
||||||
* `rules`: 一个 JSON 数组,包含多个规则对象。
|
|
||||||
* `cmd`: 要拦截的命令名称。
|
|
||||||
* `type`: 拦截类型,可以是 `"warn"` (警告) 或 `"error"` (阻止)。
|
|
||||||
* `msg`: 当规则匹配时显示的消息。
|
|
||||||
* `args` (可选): 一个字符串数组,指定需要匹配的命令参数。只有当所有指定的参数都存在时,规则才匹配。
|
|
||||||
|
|
||||||
## 配置
|
这是一个独立的模块(编译为 `hook_write.so`),用于更细粒度的输出捕获。
|
||||||
|
|
||||||
* **`config/execve_rules.json`:** 这是主要的配置文件,用于定义拦截规则。你可以根据需要添加、修改或删除规则。
|
- **拦截对象**: `write`, `writev`, `fwrite`, `puts`, `printf` 等标准输出函数。
|
||||||
* **`logs/execve.log`:** 记录所有通过 `execve` 执行的命令及其参数。
|
- **功能**: 将进程写入 `stdout` 或 `stderr` 的内容同时写入到一个指定的日志文件中。
|
||||||
* **`logs/execve_out.log`:** 记录被拦截命令的标准输出和错误输出。
|
- **用途**: 即使不使用 PTY,也能捕获程序的输出内容。
|
||||||
|
|
||||||
## 使用
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
要使用该拦截器,你需要通过 `LD_PRELOAD` 环境变量在执行命令前加载编译好的动态链接库。
|
A[Application] -->|Calls write/printf| B(Hook Function)
|
||||||
|
B --> C{Log FD Open?}
|
||||||
**示例:**
|
C -- Yes --> D[Write to Log File]
|
||||||
|
C -- No --> E[Skip Log]
|
||||||
```bash
|
D --> F[Call Original Function]
|
||||||
export LD_PRELOAD=$(pwd)/execve_interceptor.so
|
E --> F
|
||||||
nvidia-smi
|
F --> G[Output to Terminal]
|
||||||
rm test.txt
|
|
||||||
pip install torch
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 前置依赖:
|
### 6. 进程间通信与错误上报 (`src/client.c`)
|
||||||
|
|
||||||
- jsonc:
|
- **方式**: Unix Domain Socket (`/etc/exec_hook/exec.sock`)。
|
||||||
|
- **作用**: 将拦截到的执行信息(文件名、参数、环境变量、日志路径等)发送给后端服务(Go Service)。
|
||||||
|
- **流程**: 在 `execve` 拦截阶段,如果需要上报,会通过此模块连接 Socket 并发送数据。
|
||||||
|
|
||||||
```bash
|
#### 错误上报机制
|
||||||
# Ubuntu/Debian
|
|
||||||
sudo apt-get install libjson-c-dev
|
|
||||||
|
|
||||||
# CentOS/RHEL
|
当被拦截的命令执行失败(退出码非 0 或被信号终止)时,系统会触发错误上报流程。
|
||||||
sudo yum install json-c-devel
|
|
||||||
|
|
||||||
|
1. **执行与捕获**: `pty_dup.c` 中的 `handle_io` 循环负责实时捕获子进程的 `stdout` 和 `stderr`。
|
||||||
|
2. **日志记录**: 捕获到的 `stderr` 内容会被实时写入到特定的日志文件中(由 `GET_LOG_FILE` 宏定义路径)。
|
||||||
|
3. **触发上报**:
|
||||||
|
- 父进程等待子进程退出。
|
||||||
|
- 检查子进程退出状态 (`child_status`)。
|
||||||
|
- 如果 `WIFEXITED` 且退出码非 0,或者 `WIFSIGNALED`(被信号终止),则调用 `send_exec_params`。
|
||||||
|
4. **发送数据**: `send_exec_params` 将以下信息打包发送给 Go 服务:
|
||||||
|
- **Filename**: 执行的命令名称。
|
||||||
|
- **CWD**: 当前工作目录。
|
||||||
|
- **Argv**: 完整的参数列表。
|
||||||
|
- **Envp**: 完整的环境变量列表。
|
||||||
|
- **Log Path**: 记录了错误输出的日志文件绝对路径。
|
||||||
|
5. **等待响应**: 客户端等待服务端返回处理结果(如 AI 分析建议),并将其打印到终端。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Parent as Parent Process
|
||||||
|
participant Child as Child Process (Cmd)
|
||||||
|
participant Log as Log File (Stderr)
|
||||||
|
participant Socket as Unix Socket
|
||||||
|
participant Server as Go Service
|
||||||
|
|
||||||
|
Parent->>Child: forkpty() & exec()
|
||||||
|
loop Execution
|
||||||
|
Child->>Parent: Stderr Output (Pipe)
|
||||||
|
Parent->>Log: Write Stderr Content
|
||||||
|
end
|
||||||
|
Child-->>Parent: Exit (Status != 0)
|
||||||
|
|
||||||
|
Parent->>Parent: Check Exit Code
|
||||||
|
|
||||||
|
alt Exit Code != 0
|
||||||
|
Parent->>Socket: Connect
|
||||||
|
Parent->>Socket: Send Filename
|
||||||
|
Parent->>Socket: Send CWD
|
||||||
|
Parent->>Socket: Send Argv & Envp
|
||||||
|
Parent->>Socket: Send Log Path (Error Log)
|
||||||
|
|
||||||
|
Socket->>Server: Forward Request
|
||||||
|
Server->>Server: Analyze Error (Read Log)
|
||||||
|
Server-->>Socket: Response (Suggestion)
|
||||||
|
Socket-->>Parent: Receive Response
|
||||||
|
Parent->>Parent: Print Suggestion
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## 编译:
|
#### 通信协议格式
|
||||||
|
|
||||||
- cd execve_hook && make
|
数据通过 Socket 顺序发送,格式如下(均为二进制流):
|
||||||
|
|
||||||
## 测试:
|
| 字段 | 类型 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `filename_len` | `size_t` | 文件名长度 |
|
||||||
|
| `filename` | `char[]` | 文件名内容 |
|
||||||
|
| `pwd_len` | `size_t` | 当前工作目录长度 |
|
||||||
|
| `pwd` | `char[]` | 当前工作目录内容 |
|
||||||
|
| `argc` | `int` | 参数个数 |
|
||||||
|
| `arg_len` | `size_t` | (循环) 参数长度 |
|
||||||
|
| `arg_str` | `char[]` | (循环) 参数内容 |
|
||||||
|
| `envc` | `int` | 环境变量个数 |
|
||||||
|
| `env_len` | `size_t` | (循环) 环境变量长度 |
|
||||||
|
| `env_str` | `char[]` | (循环) 环境变量内容 |
|
||||||
|
| `log_path_len` | `size_t` | 日志路径长度 |
|
||||||
|
| `log_path` | `char[]` | 日志路径内容 |
|
||||||
|
|
||||||
- ./test_bash.sh
|
## 编译构建
|
||||||
|
|
||||||
## 配置文件格式:
|
项目使用 `Makefile` 进行管理。
|
||||||
|
|
||||||
```json
|
- **默认构建**: `make` (生成 `intercept.so`)
|
||||||
[
|
- **开启 Hook 功能**: `make HOOK=1` (定义 `HOOK` 宏,启用拦截逻辑)
|
||||||
{
|
- **清理**: `make clean`
|
||||||
"cmd": "nvidia-smi", // 将匹配的命令
|
|
||||||
"type": "warn", // 如果为warn,则会提示是否继续执行
|
|
||||||
"msg": "在沐曦环境下请执行mx-smi" // 提示信息
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cmd": "rm", // 匹配的命令
|
|
||||||
"type": "error", // 如果为error,则会拦截命令执行
|
|
||||||
"msg": "Error: rm command is forbidden" // 提示信息
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cmd": "pip", // 匹配的命令
|
|
||||||
"type": "warn",
|
|
||||||
"msg": "使用pip安装torch时,请注意使用厂家支持版本",
|
|
||||||
"args": ["install", "torch"] //当存在args,则这里的参数必须全部存在
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
```
|
## 目录结构说明
|
||||||
|
|
||||||
|
- `src/`: 源代码目录
|
||||||
|
- `execve_interceptor.c`: `execve` 拦截主逻辑
|
||||||
|
- `hook_write.c`: `write` 系列函数拦截逻辑
|
||||||
|
- `config.c`: 共享内存配置加载
|
||||||
|
- `pty_dup.c`: 伪终端处理
|
||||||
|
- `client.c`: Socket 通信客户端
|
||||||
|
- `rules.c`: 规则匹配逻辑
|
||||||
|
- `build/`: 编译输出目录
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ int enhance_execve(const char *filename, char *const argv[],
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NO_CONFIG_CHECK
|
||||||
// Current configuration information
|
// Current configuration information
|
||||||
DEBUG_LOG("Current Config rule count : %d", shared_config->rule_count);
|
DEBUG_LOG("Current Config rule count : %d", shared_config->rule_count);
|
||||||
|
|
||||||
|
|
@ -216,6 +217,27 @@ int enhance_execve(const char *filename, char *const argv[],
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
DEBUG_LOG("NO_CONFIG_CHECK defined, skipping config checks.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 只有当标准输入和标准输出都是终端时,才启用 PTY
|
||||||
|
// 1. 如果 stdin 不是终端(如管道输入),不需要 PTY
|
||||||
|
// 2. 如果 stdout 不是终端(如重定向到文件),使用 PTY 会导致输出包含颜色代码等控制字符,破坏文件内容
|
||||||
|
if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
|
||||||
|
DEBUG_LOG("Not a tty (stdin or stdout), skipping PTY setup.");
|
||||||
|
#ifdef HOOK
|
||||||
|
if (orig_execve) {
|
||||||
|
orig_execve(filename, argv, envp);
|
||||||
|
perror("orig_execve failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
execvp(filename, argv);
|
||||||
|
perror("execvp failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
write_log(filename, argv);
|
write_log(filename, argv);
|
||||||
|
|
||||||
// Duplicate stdout and stderr to the log file
|
// Duplicate stdout and stderr to the log file
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ __attribute__((destructor)) void cleanup_shared_memory() {
|
||||||
// shared_config = NULL;
|
// shared_config = NULL;
|
||||||
// }
|
// }
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// print_stacktrace();
|
print_stacktrace();
|
||||||
#endif
|
#endif
|
||||||
// Note: We don't delete the shared memory segment here, as it might be
|
// Note: We don't delete the shared memory segment here, as it might be
|
||||||
// used by other processes. A separate mechanism would be needed to manage
|
// used by other processes. A separate mechanism would be needed to manage
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,17 @@
|
||||||
FILE *log_file = NULL;
|
FILE *log_file = NULL;
|
||||||
pid_t child_pid;
|
pid_t child_pid;
|
||||||
int child_status = -1;
|
int child_status = -1;
|
||||||
|
static int pty_master_fd = -1;
|
||||||
|
|
||||||
|
void handle_sigwinch(int sig) {
|
||||||
|
(void)sig;
|
||||||
|
struct winsize win;
|
||||||
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) != -1) {
|
||||||
|
if (pty_master_fd != -1) {
|
||||||
|
ioctl(pty_master_fd, TIOCSWINSZ, &win);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// void dupIO() {
|
// void dupIO() {
|
||||||
// pid_t pid;
|
// pid_t pid;
|
||||||
|
|
@ -73,6 +84,7 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
|
||||||
|
|
||||||
signal(SIGINT, handle_sigint);
|
signal(SIGINT, handle_sigint);
|
||||||
signal(SIGCHLD, handle_sigchld);
|
signal(SIGCHLD, handle_sigchld);
|
||||||
|
signal(SIGWINCH, handle_sigwinch);
|
||||||
|
|
||||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) < 0) {
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) < 0) {
|
||||||
perror("ioctl TIOCGWINSZ failed");
|
perror("ioctl TIOCGWINSZ failed");
|
||||||
|
|
@ -83,6 +95,10 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
|
||||||
DEBUG_LOG("forkpty result is: %d.", pid);
|
DEBUG_LOG("forkpty result is: %d.", pid);
|
||||||
child_pid = pid;
|
child_pid = pid;
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
pty_master_fd = master;
|
||||||
|
}
|
||||||
|
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
perror("forkpty failed");
|
perror("forkpty failed");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,31 @@ void setup_termios(struct termios *term) {
|
||||||
// 控制模式标志
|
// 控制模式标志
|
||||||
term->c_cflag = CS8 | CREAD; // 8位字符,启用接收器
|
term->c_cflag = CS8 | CREAD; // 8位字符,启用接收器
|
||||||
|
|
||||||
// 本地模式标志 - 禁用特殊输入处理
|
// 本地模式标志 - 启用规范模式、信号处理和回显
|
||||||
term->c_lflag = ISIG | IEXTEN; // 仅保留信号处理和扩展功能
|
term->c_lflag = ICANON | ECHO | ISIG | IEXTEN; // 启用规范模式、回显、信号处理和扩展功能
|
||||||
|
|
||||||
// 控制字符
|
// 控制字符
|
||||||
term->c_cc[VINTR] = 0x03; // Ctrl-C
|
term->c_cc[VINTR] = 0x03; // Ctrl-C (中断)
|
||||||
term->c_cc[VQUIT] = 0x1c; // Ctrl-反斜杠
|
term->c_cc[VQUIT] = 0x1c; // Ctrl-\ (退出)
|
||||||
term->c_cc[VERASE] = 0x7f; // Backspace
|
term->c_cc[VERASE] = 0x7f; // Backspace (擦除)
|
||||||
term->c_cc[VKILL] = 0x15; // Ctrl-U
|
term->c_cc[VKILL] = 0x15; // Ctrl-U (清除行)
|
||||||
term->c_cc[VEOF] = 0x04; // Ctrl-D
|
term->c_cc[VEOF] = 0x04; // Ctrl-D (文件结束)
|
||||||
term->c_cc[VTIME] = 0;
|
term->c_cc[VEOL] = 0; // 行结束字符
|
||||||
term->c_cc[VMIN] = 1;
|
term->c_cc[VEOL2] = 0; // 行结束字符2
|
||||||
term->c_cc[VSWTC] = 0;
|
term->c_cc[VSTART] = 0x11; // Ctrl-Q (开始)
|
||||||
term->c_cc[VSTART] = 0x11; // Ctrl-Q
|
term->c_cc[VSTOP] = 0x13; // Ctrl-S (停止)
|
||||||
term->c_cc[VSTOP] = 0x13; // Ctrl-S
|
term->c_cc[VSUSP] = 0x1a; // Ctrl-Z (暂停)
|
||||||
term->c_cc[VSUSP] = 0x1a; // Ctrl-Z
|
// term->c_cc[VDSUSP] = 0x19; // Ctrl-Y (延迟暂停)
|
||||||
term->c_cc[VEOL] = 0;
|
term->c_cc[VREPRINT] = 0x12; // Ctrl-R (重打印)
|
||||||
term->c_cc[VREPRINT] = 0x12; // Ctrl-R
|
term->c_cc[VDISCARD] = 0x0f; // Ctrl-O (丢弃)
|
||||||
term->c_cc[VDISCARD] = 0x0f; // Ctrl-O
|
term->c_cc[VWERASE] = 0x17; // Ctrl-W (擦除词)
|
||||||
term->c_cc[VWERASE] = 0x17; // Ctrl-W
|
term->c_cc[VLNEXT] = 0x16; // Ctrl-V (字面值下一个)
|
||||||
term->c_cc[VLNEXT] = 0x16; // Ctrl-V
|
term->c_cc[VMIN] = 1; // 规范模式:最少读取1个字符
|
||||||
term->c_cc[VEOL2] = 0;
|
term->c_cc[VTIME] = 0; // 规范模式:无超时
|
||||||
|
|
||||||
|
#ifdef VSWTC
|
||||||
|
term->c_cc[VSWTC] = 0; // 切换字符(通常未使用)
|
||||||
|
#endif
|
||||||
|
|
||||||
// 设置输入输出速度
|
// 设置输入输出速度
|
||||||
cfsetispeed(term, B38400);
|
cfsetispeed(term, B38400);
|
||||||
|
|
@ -83,7 +87,7 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
DEBUG_LOG("poll.....");
|
// DEBUG_LOG("poll.....");
|
||||||
int ret = poll(fds, 3, 100); // 修改为监控3个fd
|
int ret = poll(fds, 3, 100); // 修改为监控3个fd
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR) continue;
|
||||||
|
|
@ -92,7 +96,7 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先处理 PTY 输出,确保缓冲区中的数据被完全读出
|
// 优先处理 PTY 输出,确保缓冲区中的数据被完全读出
|
||||||
DEBUG_LOG("Handling pty output...");
|
// DEBUG_LOG("Handling pty output...");
|
||||||
if (fds[1].revents & (POLLIN | POLLHUP)) {
|
if (fds[1].revents & (POLLIN | POLLHUP)) {
|
||||||
ssize_t n = read(master_fd, buffer, sizeof(buffer));
|
ssize_t n = read(master_fd, buffer, sizeof(buffer));
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
|
|
@ -104,7 +108,7 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
|
|
||||||
// 处理stderr
|
// 处理stderr
|
||||||
// 处理stderr
|
// 处理stderr
|
||||||
DEBUG_LOG("Handling stderr output...");
|
// DEBUG_LOG("Handling stderr output...");
|
||||||
if (fds[2].revents & (POLLIN | POLLHUP)) {
|
if (fds[2].revents & (POLLIN | POLLHUP)) {
|
||||||
ssize_t n = read(stderr_fd, buffer, sizeof(buffer));
|
ssize_t n = read(stderr_fd, buffer, sizeof(buffer));
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
|
|
@ -123,9 +127,9 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输出带颜色的错误信息
|
// 输出带颜色的错误信息
|
||||||
write(STDERR_FILENO, "\033[31m", 5); // 设置红色
|
// write(STDERR_FILENO, "\033[31m", 5); // 设置红色
|
||||||
write(STDERR_FILENO, converted, conv_len);
|
write(STDERR_FILENO, converted, conv_len);
|
||||||
write(STDERR_FILENO, "\033[0m", 4); // 重置颜色
|
// write(STDERR_FILENO, "\033[0m", 4); // 重置颜色
|
||||||
|
|
||||||
// 写入日志
|
// 写入日志
|
||||||
write(stderr_log_fd, converted, conv_len);
|
write(stderr_log_fd, converted, conv_len);
|
||||||
|
|
@ -133,12 +137,12 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查子进程状态
|
// 检查子进程状态
|
||||||
DEBUG_LOG("Checking child status: %d", child_status);
|
// DEBUG_LOG("Checking child status: %d", child_status);
|
||||||
if (child_status != -1) {
|
if (child_status != -1) {
|
||||||
// 再次尝试读取可能残留的输出
|
// 再次尝试读取可能残留的输出
|
||||||
while (1) {
|
while (1) {
|
||||||
ssize_t n = read(master_fd, buffer, sizeof(buffer));
|
ssize_t n = read(master_fd, buffer, sizeof(buffer));
|
||||||
DEBUG_LOG("Read n is: %ld", n);
|
// DEBUG_LOG("Read n is: %ld", n);
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
// 读取错误处理
|
// 读取错误处理
|
||||||
break;
|
break;
|
||||||
|
|
@ -164,7 +168,7 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_LOG("fflush.");
|
// DEBUG_LOG("fflush.");
|
||||||
// 确保所有输出都已刷新
|
// 确保所有输出都已刷新
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
@ -173,7 +177,7 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理标准输入
|
// 处理标准输入
|
||||||
DEBUG_LOG("Handling stdin");
|
// DEBUG_LOG("Handling stdin");
|
||||||
if (fds[0].revents & POLLIN) {
|
if (fds[0].revents & POLLIN) {
|
||||||
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
|
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
|
||||||
if (n <= 0) break;
|
if (n <= 0) break;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue