fix: 修复了部分问题

This commit is contained in:
Pan Qiancheng 2025-12-07 23:16:55 +08:00
parent 21027a79be
commit 9c5207c59d
16 changed files with 385 additions and 76 deletions

View File

@ -25,6 +25,11 @@ TEST_CLIENT = $(BUILD_DIR)/test_client
TEST_CLIENT_SRC = $(TESTS_DIR)/test_client.c TEST_CLIENT_SRC = $(TESTS_DIR)/test_client.c
TEST_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/debug.o TEST_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/debug.o
# 并发测试客户端
TEST_CONCURRENT_CLIENT = $(BUILD_DIR)/test_concurrent_client
TEST_CONCURRENT_CLIENT_SRC = $(TESTS_DIR)/test_concurrent_client.c
TEST_CONCURRENT_CLIENT_DEPS = $(BUILD_DIR)/client.o $(BUILD_DIR)/debug.o
# 如果需要开启 debug只需执行 make DEBUG=1 # 如果需要开启 debug只需执行 make DEBUG=1
ifeq ($(DEBUG),1) ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG -g CFLAGS += -DDEBUG -g
@ -40,12 +45,14 @@ ifeq ($(NO_CONFIG_CHECK),1)
CFLAGS += -DNO_CONFIG_CHECK CFLAGS += -DNO_CONFIG_CHECK
endif endif
.PHONY: all clean debug hook rebuild pre_build test_client .PHONY: all clean debug hook rebuild pre_build test_client test_concurrent_client
all: pre_build $(TARGET) $(HOOK_TARGET) all: pre_build $(TARGET) $(HOOK_TARGET)
test_client: pre_build $(TEST_CLIENT) test_client: pre_build $(TEST_CLIENT)
test_concurrent_client: pre_build $(TEST_CONCURRENT_CLIENT)
pre_build: pre_build:
ifeq ($(DEBUG),1) ifeq ($(DEBUG),1)
@echo "Building with debug flags..." @echo "Building with debug flags..."
@ -77,6 +84,10 @@ $(TEST_CLIENT): $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS)
@mkdir -p $(BUILD_DIR) @mkdir -p $(BUILD_DIR)
$(CC) -Wall -Wextra -o $@ $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS) $(CC) -Wall -Wextra -o $@ $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS)
$(TEST_CONCURRENT_CLIENT): $(TEST_CONCURRENT_CLIENT_SRC) $(TEST_CONCURRENT_CLIENT_DEPS)
@mkdir -p $(BUILD_DIR)
$(CC) -Wall -Wextra -pthread -o $@ $(TEST_CONCURRENT_CLIENT_SRC) $(TEST_CONCURRENT_CLIENT_DEPS)
clean: clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)

View File

@ -1,5 +1,4 @@
#include "client.h" #include "client.h"
#include "debug.h"
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
@ -9,10 +8,12 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include "debug.h"
#define BUFFER_SIZE 4096 #define BUFFER_SIZE 4096
// 读取完整消息 // 读取完整消息
ssize_t readMessage(int sock, char *buffer, size_t maxSize) { ssize_t readMessage(int sock, char* buffer, size_t maxSize) {
uint32_t messageLen; uint32_t messageLen;
// 先读取消息长度 // 先读取消息长度
if (read(sock, &messageLen, sizeof(messageLen)) != sizeof(messageLen)) { if (read(sock, &messageLen, sizeof(messageLen)) != sizeof(messageLen)) {
@ -35,8 +36,8 @@ ssize_t readMessage(int sock, char *buffer, size_t maxSize) {
return messageLen; return messageLen;
} }
int send_exec_params(const char *filename, char *const argv[], int seeking_solutions(const char* filename, char* const argv[],
char *const envp[], const char *logPath) { char* const envp[], const char* logPath, int* output_fd) {
char abs_path[PATH_MAX]; char abs_path[PATH_MAX];
char pwd[PATH_MAX]; char pwd[PATH_MAX];
@ -90,7 +91,7 @@ int send_exec_params(const char *filename, char *const argv[],
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
DEBUG_LOG("connect error: %s\n", strerror(errno)); DEBUG_LOG("connect error: %s\n", strerror(errno));
close(sock); close(sock);
return -1; return -1;
@ -146,8 +147,14 @@ int send_exec_params(const char *filename, char *const argv[],
strncpy(display_buffer, buffer, BUFFER_SIZE - 1); strncpy(display_buffer, buffer, BUFFER_SIZE - 1);
display_buffer[BUFFER_SIZE - 1] = '\0'; display_buffer[BUFFER_SIZE - 1] = '\0';
printf("%s", display_buffer);
fflush(stdout); // 使用指定的输出描述符,如果为NULL则使用stdout
write(*output_fd, display_buffer, strlen(display_buffer));
// 如果是标准输出,flush
if (*output_fd == STDOUT_FILENO) {
fflush(stdout);
}
} }
close(sock); close(sock);

View File

@ -16,6 +16,6 @@
#define MAX_BUF_SIZE 4096 #define MAX_BUF_SIZE 4096
// 函数声明 // 函数声明
int send_exec_params(const char *filename, char *const argv[], char *const envp[], const char *logPath); int seeking_solutions(const char *filename, char *const argv[], char *const envp[], const char *logPath, int *output_fd);
#endif // EXEC_SOCKET_H #endif // EXEC_SOCKET_H

View File

@ -22,18 +22,17 @@
#define COMMAND_NOT_FOUND "/usr/lib/command-not-found" #define COMMAND_NOT_FOUND "/usr/lib/command-not-found"
#ifdef DEBUG // 如果是debug模式在本地目录生成log方便debug // #ifdef DEBUG // 如果是debug模式在本地目录生成log方便debug
#define LOG_FILE "./logs/execve.log" // #define LOG_FILE "./logs/execve.log"
#define LOG_OUT_FILE "./logs/execve_out.log" // #define LOG_OUT_FILE "./logs/execve_out.log"
#else // #else
#define CONFIG_FILE "" #define LOG_FILE "/var/log/bash-smart/execve.log"
#define LOG_FILE "/etc/exec_hook/logs/execve.log" #define LOG_OUT_FILE "/var/log/bash-smart/execve_out.log"
#define LOG_OUT_FILE "/etc/exec_hook/logs/execve_out.log"
// #endif
#endif
#define MAX_PATH_LEN 256 #define MAX_PATH_LEN 256

View File

@ -10,6 +10,7 @@
#include "config.h" #include "config.h"
#include "debug.h" #include "debug.h"
#include "init_cleanup.h" #include "init_cleanup.h"
#include "terminal_utils.h"
#include "logging.h" #include "logging.h"
#include "pty_dup.h" #include "pty_dup.h"
#include "rules.h" #include "rules.h"
@ -238,10 +239,29 @@ int enhance_execve(const char *filename, char *const argv[],
exit(1); exit(1);
} }
write_log(filename, argv); // 检查日志权限如果没有权限则跳过日志记录和PTY设置
int log_ret = write_log(filename, argv);
if (log_ret != 0) {
DEBUG_LOG("write_log failed, no permission for log directory");
#ifdef HOOK
return orig_execve(filename, argv, envp);
#else
return execve(filename, argv, envp);
#endif
}
// Duplicate stdout and stderr to the log file // Duplicate stdout and stderr to the log file
dupIO(filename, argv, envp); int ret = dupIO(filename, argv, envp);
if (ret != 0) {
DEBUG_LOG("dupIO failed with code: %d", ret);
restore_terminal();
#ifdef HOOK
return orig_execve(filename, argv, envp);
#else
return execve(filename, argv, envp);
// return 1;
#endif
}
#ifdef HOOK #ifdef HOOK

View File

@ -1,9 +1,11 @@
#include "init_cleanup.h" #include "init_cleanup.h"
#include "exec_hook.h" #include "exec_hook.h"
#include "debug.h" #include "debug.h"
#include "pty_dup.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <sys/shm.h> #include <sys/shm.h>
#include <termios.h>
// // Constructor, executed when the library is loaded // // Constructor, executed when the library is loaded
// __attribute__((constructor)) static void initialize() { // __attribute__((constructor)) static void initialize() {
@ -15,7 +17,7 @@
// } // }
// Destructor, executed when the library is unloaded // Destructor, executed when the library is unloaded
__attribute__((destructor)) void cleanup_shared_memory() { __attribute__((destructor)) void cleanup() {
DEBUG_LOG("execve_intercept library unloaded."); DEBUG_LOG("execve_intercept library unloaded.");
// Log output paths // Log output paths
DEBUG_LOG("Log file: %s", LOG_FILE); DEBUG_LOG("Log file: %s", LOG_FILE);
@ -29,10 +31,26 @@ __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
// the lifecycle of the shared memory if deletion is required. // the lifecycle of the shared memory if deletion is required.
// 获取stderr日志文件名
const char* stdout_log = GET_LOG_FILE(child_pid, 1);
const char* stderr_log = GET_LOG_FILE(child_pid, 0);
// 如果存在日志文件且有写权限,则删除
if (access(stdout_log, F_OK) != -1 && access(stdout_log, W_OK) != -1) {
if (unlink(stdout_log) == 0) {
DEBUG_LOG("Stdout log file removed: %s", stdout_log);
}
}
if (access(stderr_log, F_OK) != -1 && access(stderr_log, W_OK) != -1) {
if (unlink(stderr_log) == 0) {
DEBUG_LOG("Stderr log file removed: %s", stderr_log);
}
}
} }

View File

@ -2,6 +2,6 @@
#define INIT_CLEANUP_H #define INIT_CLEANUP_H
// void initialize(); // void initialize();
__attribute__((destructor)) void cleanup_shared_memory(); __attribute__((destructor)) void cleanup();
#endif // INIT_CLEANUP_H #endif // INIT_CLEANUP_H

View File

@ -17,7 +17,7 @@
#include "exec_hook.h" #include "exec_hook.h"
// Write log // Write log
void write_log(const char *filename, char *const argv[]) { int write_log(const char *filename, char *const argv[]) {
DEBUG_LOG("Writing exec log for command: %s", filename); DEBUG_LOG("Writing exec log for command: %s", filename);
time_t now; time_t now;
time(&now); time(&now);
@ -27,39 +27,34 @@ void write_log(const char *filename, char *const argv[]) {
strdup(LOG_FILE); // Duplicate string as dirname might modify it strdup(LOG_FILE); // Duplicate string as dirname might modify it
if (!log_file_path) { if (!log_file_path) {
perror("strdup failed"); perror("strdup failed");
return; return -1;
} }
char *log_dir = dirname(log_file_path); char *log_dir = dirname(log_file_path);
if (!log_dir) { if (!log_dir) {
perror("dirname failed"); perror("dirname failed");
free(log_file_path); free(log_file_path);
return; return -1;
} }
// Check if the directory exists and is writable // Check if the directory exists and is writable
if (access(log_dir, W_OK) != 0) { if (access(log_dir, F_OK) != 0) {
if (errno == ENOENT) { DEBUG_LOG("Log directory '%s' does not exist", log_dir);
DEBUG_LOG("Log directory '%s' does not exist, creating it...", free(log_file_path);
log_dir); return -1;
// Create the directory with appropriate permissions (e.g., 0755)
if (mkdir(log_dir, 0755) == -1) {
perror("mkdir failed");
free(log_file_path);
return;
}
DEBUG_LOG("Log directory '%s' created successfully.", log_dir);
} else {
perror("access failed for log directory");
free(log_file_path);
return;
}
} }
if (access(log_dir, W_OK) != 0) {
DEBUG_LOG("Log directory '%s' is not writable", log_dir);
free(log_file_path);
return -1;
}
free(log_file_path); // Free the duplicated string free(log_file_path); // Free the duplicated string
FILE *log = fopen(LOG_FILE, "a"); FILE *log = fopen(LOG_FILE, "a");
if (!log) { if (!log) {
perror("fopen failed for log file"); perror("fopen failed for log file");
return; return -1;
} }
fprintf(log, "[%s] Command: %s\n", ctime(&now), filename); fprintf(log, "[%s] Command: %s\n", ctime(&now), filename);
@ -69,6 +64,7 @@ void write_log(const char *filename, char *const argv[]) {
} }
fclose(log); fclose(log);
return 0;
} }
// // 全局变量记录子进程 PID // // 全局变量记录子进程 PID

View File

@ -1,7 +1,7 @@
#ifndef LOGGING_H #ifndef LOGGING_H
#define LOGGING_H #define LOGGING_H
void write_log(const char *filename, char *const argv[]); int write_log(const char *filename, char *const argv[]);
void duplicate_output_to_log(); void duplicate_output_to_log();
#endif // LOGGING_H #endif // LOGGING_H

View File

@ -8,7 +8,7 @@
#include "signal_handlers.h" #include "signal_handlers.h"
#include "terminal_utils.h" #include "terminal_utils.h"
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; static int pty_master_fd = -1;
@ -66,7 +66,7 @@ void handle_sigwinch(int sig) {
// exit(1); // exit(1);
// } // }
void dupIO(const char *filename, char *const argv[], char *const envp[]) { int dupIO(const char* filename, char* const argv[], char* const envp[]) {
pid_t pid; pid_t pid;
int master; int master;
int stderr_pipe[2]; int stderr_pipe[2];
@ -79,7 +79,7 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
// 创建stderr的pipe // 创建stderr的pipe
if (pipe(stderr_pipe) < 0) { if (pipe(stderr_pipe) < 0) {
perror("pipe failed"); perror("pipe failed");
exit(1); return -1;
} }
signal(SIGINT, handle_sigint); signal(SIGINT, handle_sigint);
@ -88,7 +88,9 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) < 0) { if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) < 0) {
perror("ioctl TIOCGWINSZ failed"); perror("ioctl TIOCGWINSZ failed");
exit(1); close(stderr_pipe[0]);
close(stderr_pipe[1]);
return -1;
} }
pid = forkpty(&master, NULL, &term, &win); pid = forkpty(&master, NULL, &term, &win);
@ -101,7 +103,9 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
if (pid < 0) { if (pid < 0) {
perror("forkpty failed"); perror("forkpty failed");
exit(1); close(stderr_pipe[0]);
close(stderr_pipe[1]);
return -1;
} else if (pid == 0) { } else if (pid == 0) {
// 子进程 // 子进程
DEBUG_LOG("Child process ready."); DEBUG_LOG("Child process ready.");
@ -113,11 +117,12 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
// 重定向stderr到pipe写端 // 重定向stderr到pipe写端
if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) { if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
perror("dup2 failed"); perror("dup2 failed");
exit(1); close(stderr_pipe[1]);
return -1;
} }
close(stderr_pipe[1]); close(stderr_pipe[1]);
return; return 0;
} }
// 父进程 // 父进程
@ -128,12 +133,22 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK); fcntl(stderr_pipe[0], F_SETFL, O_NONBLOCK);
// 获取stderr日志文件名 // 获取stderr日志文件名
const char *stdout_log = GET_LOG_FILE(child_pid, 1); const char* stdout_log = GET_LOG_FILE(child_pid, 1);
const char *stderr_log = GET_LOG_FILE(child_pid, 0); const char* stderr_log = GET_LOG_FILE(child_pid, 0);
DEBUG_LOG("Ready to handle IO"); DEBUG_LOG("Ready to handle IO");
handle_io(master, stderr_pipe[0], stdout_log, int ret = handle_io(master, stderr_pipe[0], stdout_log,
stderr_log); // 需要修改handle_io函数签名传入stderr_pipe读端 stderr_log); // 需要修改handle_io函数签名传入stderr_pipe读端
if (ret != 0) {
DEBUG_LOG("handle_io returned error: %d", ret);
return ret;
}
// 恢复终端设置
restore_terminal();
int stdout_fd = STDOUT_FILENO;
int exit_code = 1;
// 打印子进程状态 // 打印子进程状态
if (child_status != -1) { if (child_status != -1) {
if (WIFEXITED(child_status)) { if (WIFEXITED(child_status)) {
@ -142,20 +157,20 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
code); code);
if (code != 0) { if (code != 0) {
int success = int success =
send_exec_params(filename, argv, envp, stderr_log); seeking_solutions(filename, argv, envp, stderr_log, &stdout_fd);
if (success != 0) { if (success != 0) {
fprintf(stderr, "向服务器请求解决方案失败! \n"); fprintf(stderr, "向服务器请求解决方案失败! \n");
} }
} }
exit(code); exit_code = code;
} else if (WIFSIGNALED(child_status)) { } else if (WIFSIGNALED(child_status)) {
DEBUG_LOG("\nChild process terminated abnormally by signal %d\n", DEBUG_LOG("\nChild process terminated abnormally by signal %d\n",
WTERMSIG(child_status)); WTERMSIG(child_status));
int success = send_exec_params(filename, argv, envp, stderr_log); int success = seeking_solutions(filename, argv, envp, stderr_log, &stdout_fd);
if (success != 0) { if (success != 0) {
fprintf(stderr, "向服务器请求解决方案失败! \n"); fprintf(stderr, "向服务器请求解决方案失败! \n");
} }
exit(128 + WTERMSIG(child_status)); exit_code = 128 + WTERMSIG(child_status);
} else if (WIFSTOPPED(child_status)) { } else if (WIFSTOPPED(child_status)) {
DEBUG_LOG("\nChild process stopped by signal %d\n", DEBUG_LOG("\nChild process stopped by signal %d\n",
WSTOPSIG(child_status)); WSTOPSIG(child_status));
@ -165,5 +180,5 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) {
close(master); close(master);
close(stderr_pipe[0]); close(stderr_pipe[0]);
exit(1); exit(exit_code);
} }

View File

@ -17,11 +17,11 @@
#define BUFFER_SIZE 1024 #define BUFFER_SIZE 1024
extern FILE *log_file; extern FILE* log_file;
extern pid_t child_pid; extern pid_t child_pid;
extern int child_status; extern int child_status;
void dupIO(const char *filename, char *const argv[], char *const envp[]); int dupIO(const char* filename, char* const argv[], char* const envp[]);
void print_child_status(void); void print_child_status(void);
#endif #endif

View File

@ -3,6 +3,12 @@
#include "config.h" #include "config.h"
#include "debug.h" #include "debug.h"
#include "pty_dup.h" #include "pty_dup.h"
#include <libgen.h>
#include <sys/stat.h>
// 全局变量保存原始终端设置
struct termios orig_term;
int term_saved = 0;
void setup_termios(struct termios *term) { void setup_termios(struct termios *term) {
// 初始化终端设置 // 初始化终端设置
@ -48,15 +54,16 @@ void setup_termios(struct termios *term) {
cfsetospeed(term, B38400); cfsetospeed(term, B38400);
} }
void handle_io(int master_fd, int stderr_fd, const char *stdout_log, int handle_io(int master_fd, int stderr_fd, const char *stdout_log,
const char *stderr_log) { const char *stderr_log) {
struct termios orig_term, raw_term; struct termios raw_term;
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
struct pollfd fds[3]; // 增加一个pollfd用于stderr struct pollfd fds[3]; // 增加一个pollfd用于stderr
// 保存原始终端设置 // 保存原始终端设置
DEBUG_LOG("Saving original config."); DEBUG_LOG("Saving original config.");
tcgetattr(STDIN_FILENO, &orig_term); tcgetattr(STDIN_FILENO, &orig_term);
term_saved = 1;
// 设置原始模式 // 设置原始模式
DEBUG_LOG("Setting origin mode."); DEBUG_LOG("Setting origin mode.");
@ -77,13 +84,30 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
fds[2].fd = stderr_fd; // 新增stderr的fd fds[2].fd = stderr_fd; // 新增stderr的fd
fds[2].events = POLLIN; fds[2].events = POLLIN;
// 打开两个日志文件 // 检查日志目录权限,如果没有权限就直接返回
char *stdout_log_copy = strdup(stdout_log);
if (!stdout_log_copy) {
return -1;
}
char *log_dir = dirname(stdout_log_copy);
// 检查目录是否存在以及是否有写权限
if (access(log_dir, F_OK) != 0 || access(log_dir, W_OK) != 0) {
free(stdout_log_copy);
return -1;
}
free(stdout_log_copy);
// 打开日志文件
int stdout_log_fd = open(stdout_log, O_WRONLY | O_CREAT | O_APPEND, 0644); int stdout_log_fd = open(stdout_log, O_WRONLY | O_CREAT | O_APPEND, 0644);
int stderr_log_fd = open(stderr_log, O_WRONLY | O_CREAT | O_APPEND, 0644); int stderr_log_fd = open(stderr_log, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (stdout_log_fd == -1 || stderr_log_fd == -1) { if (stdout_log_fd == -1 || stderr_log_fd == -1) {
perror("Failed to open log file"); if (stdout_log_fd >= 0) close(stdout_log_fd);
return; if (stderr_log_fd >= 0) close(stderr_log_fd);
return -1;
} }
while (1) { while (1) {
@ -209,7 +233,14 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log,
// 在恢复终端设置之前清除可能的未完成输出 // 在恢复终端设置之前清除可能的未完成输出
tcflush(STDIN_FILENO, TCIFLUSH); tcflush(STDIN_FILENO, TCIFLUSH);
// 恢复终端设置 return 0;
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
} }
void restore_terminal() {
if (term_saved) {
DEBUG_LOG("Restoring terminal settings.");
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
term_saved = 0;
}
}

View File

@ -3,8 +3,10 @@
#ifndef TERMINAL_UTILS_H #ifndef TERMINAL_UTILS_H
#define TERMINAL_UTILS_H #define TERMINAL_UTILS_H
void setup_termios(struct termios *term); void setup_termios(struct termios* term);
void handle_io(int master_fd, int stderr_fd, const char *stdout_log, int handle_io(int master_fd, int stderr_fd, const char* stdout_log,
const char *stderr_log); const char* stderr_log);
void restore_terminal();
#endif #endif

BIN
tests/makefault Executable file

Binary file not shown.

View File

@ -55,7 +55,7 @@ int main(int argc, char *argv[]) {
printf("--- 服务端响应 ---\n"); printf("--- 服务端响应 ---\n");
// 调用客户端函数 // 调用客户端函数
int result = send_exec_params(filename, test_argv, test_envp, log_path); int result = seeking_solutions(filename, test_argv, test_envp, log_path, &STDOUT_FILENO);
printf("\n--- 响应结束 ---\n"); printf("\n--- 响应结束 ---\n");

View File

@ -0,0 +1,210 @@
#include "../src/client.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
/**
* - 访
*
* :
* ./test_concurrent_client <log_file_path> <线> [ms]
*
* :
* ./test_concurrent_client /tmp/test_error.log 10 # 10线
* ./test_concurrent_client /tmp/test_error.log 20 100 # 20线,100ms
*/
// 线程参数结构
typedef struct {
int thread_id;
const char *log_path;
int delay_ms; // 启动前延迟(毫秒)
int success; // 执行结果
} thread_args_t;
// 统计信息
typedef struct {
int total;
int success;
int failed;
pthread_mutex_t mutex;
} stats_t;
static stats_t g_stats = {0, 0, 0, PTHREAD_MUTEX_INITIALIZER};
/**
* 线
*/
void *worker_thread(void *arg) {
thread_args_t *args = (thread_args_t *)arg;
// 如果设置了延迟,先等待
if (args->delay_ms > 0) {
usleep(args->delay_ms * 1000);
}
// 模拟不同的命令和参数
char *commands[] = {
"/usr/bin/python3",
"/usr/bin/ls",
"/bin/bash",
"/usr/bin/grep"
};
const char *filename = commands[args->thread_id % 4];
// 构造测试参数
char arg1[64], arg2[64];
snprintf(arg1, sizeof(arg1), "arg1_thread_%d", args->thread_id);
snprintf(arg2, sizeof(arg2), "arg2_thread_%d", args->thread_id);
char *test_argv[] = {
"command",
arg1,
arg2,
NULL
};
char *test_envp[] = {
"PATH=/usr/bin:/bin",
"HOME=/home/test",
"THREAD_ID=test",
NULL
};
printf("[线程 %d] 开始发送请求: %s\n", args->thread_id, filename);
// 调用客户端函数
int result = seeking_solutions(filename, test_argv, test_envp, args->log_path, &STDOUT_FILENO);
args->success = (result == 0);
// 更新统计信息
pthread_mutex_lock(&g_stats.mutex);
if (result == 0) {
g_stats.success++;
printf("[线程 %d] ✓ 成功\n", args->thread_id);
} else {
g_stats.failed++;
printf("[线程 %d] ✗ 失败 (返回值: %d)\n", args->thread_id, result);
}
pthread_mutex_unlock(&g_stats.mutex);
return NULL;
}
/**
* 使
*/
void print_usage(const char *prog_name) {
fprintf(stderr, "用法: %s <log_file_path> <线程数> [延迟ms]\n", prog_name);
fprintf(stderr, "\n参数说明:\n");
fprintf(stderr, " log_file_path - 日志文件路径\n");
fprintf(stderr, " 线程数 - 并发线程数量 (1-1000)\n");
fprintf(stderr, " 延迟ms - 可选,每个线程启动前的延迟(毫秒)\n");
fprintf(stderr, "\n示例:\n");
fprintf(stderr, " %s /tmp/test.log 10 # 10个并发线程\n", prog_name);
fprintf(stderr, " %s /tmp/test.log 50 100 # 50个线程,每个延迟100ms\n", prog_name);
}
int main(int argc, char *argv[]) {
if (argc < 3) {
print_usage(argv[0]);
return 1;
}
const char *log_path = argv[1];
int num_threads = atoi(argv[2]);
int delay_ms = (argc >= 4) ? atoi(argv[3]) : 0;
// 验证参数
if (num_threads <= 0 || num_threads > 1000) {
fprintf(stderr, "错误: 线程数必须在 1-1000 之间\n");
return 1;
}
printf("====== 并发测试开始 ======\n");
printf("socket地址: %s\n", SOCKET_PATH);
printf("日志路径: %s\n", log_path);
printf("并发线程数: %d\n", num_threads);
printf("启动延迟: %d ms\n", delay_ms);
printf("========================\n\n");
// 分配线程资源
pthread_t *threads = malloc(sizeof(pthread_t) * num_threads);
thread_args_t *args = malloc(sizeof(thread_args_t) * num_threads);
if (!threads || !args) {
fprintf(stderr, "错误: 内存分配失败\n");
free(threads);
free(args);
return 1;
}
// 初始化统计信息
g_stats.total = num_threads;
g_stats.success = 0;
g_stats.failed = 0;
// 记录开始时间
struct timespec start_time, end_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
// 创建并启动所有线程
printf("创建 %d 个线程...\n\n", num_threads);
for (int i = 0; i < num_threads; i++) {
args[i].thread_id = i;
args[i].log_path = log_path;
args[i].delay_ms = delay_ms;
args[i].success = 0;
if (pthread_create(&threads[i], NULL, worker_thread, &args[i]) != 0) {
fprintf(stderr, "错误: 创建线程 %d 失败\n", i);
// 继续创建其他线程
}
}
// 等待所有线程完成
printf("等待所有线程完成...\n\n");
for (int i = 0; i < num_threads; i++) {
pthread_join(threads[i], NULL);
}
// 记录结束时间
clock_gettime(CLOCK_MONOTONIC, &end_time);
// 计算耗时
double elapsed = (end_time.tv_sec - start_time.tv_sec) +
(end_time.tv_nsec - start_time.tv_nsec) / 1e9;
// 打印统计结果
printf("\n====== 测试结果统计 ======\n");
printf("总线程数: %d\n", g_stats.total);
printf("成功: %d (%.1f%%)\n", g_stats.success,
g_stats.total > 0 ? (g_stats.success * 100.0 / g_stats.total) : 0);
printf("失败: %d (%.1f%%)\n", g_stats.failed,
g_stats.total > 0 ? (g_stats.failed * 100.0 / g_stats.total) : 0);
printf("总耗时: %.3f 秒\n", elapsed);
printf("平均每个请求: %.3f 毫秒\n",
g_stats.total > 0 ? (elapsed * 1000 / g_stats.total) : 0);
printf("吞吐量: %.1f 请求/秒\n",
elapsed > 0 ? (g_stats.total / elapsed) : 0);
printf("========================\n");
// 清理资源
free(threads);
free(args);
pthread_mutex_destroy(&g_stats.mutex);
// 返回结果
if (g_stats.failed > 0) {
printf("\n✗ 测试完成,但有 %d 个请求失败\n", g_stats.failed);
return 1;
} else {
printf("\n✓ 所有测试通过!\n");
return 0;
}
}