diff --git a/Makefile b/Makefile index d1a6990..fc7b587 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,11 @@ TEST_CLIENT = $(BUILD_DIR)/test_client TEST_CLIENT_SRC = $(TESTS_DIR)/test_client.c 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 ifeq ($(DEBUG),1) CFLAGS += -DDEBUG -g @@ -40,12 +45,14 @@ ifeq ($(NO_CONFIG_CHECK),1) CFLAGS += -DNO_CONFIG_CHECK 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) test_client: pre_build $(TEST_CLIENT) +test_concurrent_client: pre_build $(TEST_CONCURRENT_CLIENT) + pre_build: ifeq ($(DEBUG),1) @echo "Building with debug flags..." @@ -77,6 +84,10 @@ $(TEST_CLIENT): $(TEST_CLIENT_SRC) $(TEST_CLIENT_DEPS) @mkdir -p $(BUILD_DIR) $(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: rm -rf $(BUILD_DIR) diff --git a/src/client.c b/src/client.c index 7be8aa7..62b2ca3 100644 --- a/src/client.c +++ b/src/client.c @@ -1,5 +1,4 @@ #include "client.h" -#include "debug.h" #include #include @@ -9,10 +8,12 @@ #include #include +#include "debug.h" + #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; // 先读取消息长度 if (read(sock, &messageLen, sizeof(messageLen)) != sizeof(messageLen)) { @@ -35,8 +36,8 @@ ssize_t readMessage(int sock, char *buffer, size_t maxSize) { return messageLen; } -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) { char abs_path[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); 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)); close(sock); return -1; @@ -146,8 +147,14 @@ int send_exec_params(const char *filename, char *const argv[], strncpy(display_buffer, buffer, BUFFER_SIZE - 1); 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); diff --git a/src/client.h b/src/client.h index be6cab8..8472b98 100644 --- a/src/client.h +++ b/src/client.h @@ -16,6 +16,6 @@ #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 diff --git a/src/exec_hook.h b/src/exec_hook.h index a154769..e13869b 100644 --- a/src/exec_hook.h +++ b/src/exec_hook.h @@ -22,18 +22,17 @@ #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_OUT_FILE "./logs/execve_out.log" +// #define LOG_FILE "./logs/execve.log" +// #define LOG_OUT_FILE "./logs/execve_out.log" -#else +// #else -#define CONFIG_FILE "" -#define LOG_FILE "/etc/exec_hook/logs/execve.log" -#define LOG_OUT_FILE "/etc/exec_hook/logs/execve_out.log" - -#endif +#define LOG_FILE "/var/log/bash-smart/execve.log" +#define LOG_OUT_FILE "/var/log/bash-smart/execve_out.log" + +// #endif #define MAX_PATH_LEN 256 diff --git a/src/execve_interceptor.c b/src/execve_interceptor.c index 180de05..15d14ae 100644 --- a/src/execve_interceptor.c +++ b/src/execve_interceptor.c @@ -10,6 +10,7 @@ #include "config.h" #include "debug.h" #include "init_cleanup.h" +#include "terminal_utils.h" #include "logging.h" #include "pty_dup.h" #include "rules.h" @@ -238,10 +239,29 @@ int enhance_execve(const char *filename, char *const argv[], 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 - 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 diff --git a/src/init_cleanup.c b/src/init_cleanup.c index 7e22821..5c177d4 100644 --- a/src/init_cleanup.c +++ b/src/init_cleanup.c @@ -1,9 +1,11 @@ #include "init_cleanup.h" #include "exec_hook.h" #include "debug.h" +#include "pty_dup.h" #include #include #include +#include // // Constructor, executed when the library is loaded // __attribute__((constructor)) static void initialize() { @@ -15,7 +17,7 @@ // } // Destructor, executed when the library is unloaded -__attribute__((destructor)) void cleanup_shared_memory() { +__attribute__((destructor)) void cleanup() { DEBUG_LOG("execve_intercept library unloaded."); // Log output paths DEBUG_LOG("Log file: %s", LOG_FILE); @@ -29,10 +31,26 @@ __attribute__((destructor)) void cleanup_shared_memory() { // } // shared_config = NULL; // } -#ifdef DEBUG - print_stacktrace(); -#endif +// #ifdef DEBUG +// print_stacktrace(); +// #endif // 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 // 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); + } + } } diff --git a/src/init_cleanup.h b/src/init_cleanup.h index 0dd066c..5b77a19 100644 --- a/src/init_cleanup.h +++ b/src/init_cleanup.h @@ -2,6 +2,6 @@ #define INIT_CLEANUP_H // void initialize(); -__attribute__((destructor)) void cleanup_shared_memory(); +__attribute__((destructor)) void cleanup(); #endif // INIT_CLEANUP_H \ No newline at end of file diff --git a/src/logging.c b/src/logging.c index b6a1f5d..ae4ad35 100644 --- a/src/logging.c +++ b/src/logging.c @@ -17,7 +17,7 @@ #include "exec_hook.h" // 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); time_t 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 if (!log_file_path) { perror("strdup failed"); - return; + return -1; } char *log_dir = dirname(log_file_path); if (!log_dir) { perror("dirname failed"); free(log_file_path); - return; + return -1; } // Check if the directory exists and is writable - if (access(log_dir, W_OK) != 0) { - if (errno == ENOENT) { - DEBUG_LOG("Log directory '%s' does not exist, creating it...", - log_dir); - // 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, F_OK) != 0) { + DEBUG_LOG("Log directory '%s' does not exist", log_dir); + free(log_file_path); + return -1; } + + 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 FILE *log = fopen(LOG_FILE, "a"); if (!log) { perror("fopen failed for log file"); - return; + return -1; } fprintf(log, "[%s] Command: %s\n", ctime(&now), filename); @@ -69,6 +64,7 @@ void write_log(const char *filename, char *const argv[]) { } fclose(log); + return 0; } // // 全局变量记录子进程 PID diff --git a/src/logging.h b/src/logging.h index 7daf1b0..f0bc608 100644 --- a/src/logging.h +++ b/src/logging.h @@ -1,7 +1,7 @@ #ifndef 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(); #endif // LOGGING_H \ No newline at end of file diff --git a/src/pty_dup.c b/src/pty_dup.c index 59ea1ea..e5d5968 100644 --- a/src/pty_dup.c +++ b/src/pty_dup.c @@ -8,7 +8,7 @@ #include "signal_handlers.h" #include "terminal_utils.h" -FILE *log_file = NULL; +FILE* log_file = NULL; pid_t child_pid; int child_status = -1; static int pty_master_fd = -1; @@ -66,7 +66,7 @@ void handle_sigwinch(int sig) { // 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; int master; int stderr_pipe[2]; @@ -79,7 +79,7 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) { // 创建stderr的pipe if (pipe(stderr_pipe) < 0) { perror("pipe failed"); - exit(1); + return -1; } 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) { perror("ioctl TIOCGWINSZ failed"); - exit(1); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return -1; } pid = forkpty(&master, NULL, &term, &win); @@ -101,7 +103,9 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) { if (pid < 0) { perror("forkpty failed"); - exit(1); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return -1; } else if (pid == 0) { // 子进程 DEBUG_LOG("Child process ready."); @@ -113,11 +117,12 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) { // 重定向stderr到pipe写端 if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) { perror("dup2 failed"); - exit(1); + close(stderr_pipe[1]); + return -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); // 获取stderr日志文件名 - const char *stdout_log = GET_LOG_FILE(child_pid, 1); - const char *stderr_log = GET_LOG_FILE(child_pid, 0); + const char* stdout_log = GET_LOG_FILE(child_pid, 1); + const char* stderr_log = GET_LOG_FILE(child_pid, 0); 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读端 + 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 (WIFEXITED(child_status)) { @@ -142,20 +157,20 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) { code); if (code != 0) { int success = - send_exec_params(filename, argv, envp, stderr_log); + seeking_solutions(filename, argv, envp, stderr_log, &stdout_fd); if (success != 0) { fprintf(stderr, "向服务器请求解决方案失败! \n"); } } - exit(code); + exit_code = code; } else if (WIFSIGNALED(child_status)) { DEBUG_LOG("\nChild process terminated abnormally by signal %d\n", 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) { fprintf(stderr, "向服务器请求解决方案失败! \n"); } - exit(128 + WTERMSIG(child_status)); + exit_code = 128 + WTERMSIG(child_status); } else if (WIFSTOPPED(child_status)) { DEBUG_LOG("\nChild process stopped by signal %d\n", WSTOPSIG(child_status)); @@ -165,5 +180,5 @@ void dupIO(const char *filename, char *const argv[], char *const envp[]) { close(master); close(stderr_pipe[0]); - exit(1); + exit(exit_code); } \ No newline at end of file diff --git a/src/pty_dup.h b/src/pty_dup.h index df1f950..137613d 100644 --- a/src/pty_dup.h +++ b/src/pty_dup.h @@ -17,11 +17,11 @@ #define BUFFER_SIZE 1024 -extern FILE *log_file; +extern FILE* log_file; extern pid_t child_pid; 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); #endif diff --git a/src/terminal_utils.c b/src/terminal_utils.c index b89251b..54f08c3 100644 --- a/src/terminal_utils.c +++ b/src/terminal_utils.c @@ -3,6 +3,12 @@ #include "config.h" #include "debug.h" #include "pty_dup.h" +#include +#include + +// 全局变量保存原始终端设置 +struct termios orig_term; +int term_saved = 0; void setup_termios(struct termios *term) { // 初始化终端设置 @@ -48,15 +54,16 @@ void setup_termios(struct termios *term) { 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) { - struct termios orig_term, raw_term; + struct termios raw_term; char buffer[BUFFER_SIZE]; struct pollfd fds[3]; // 增加一个pollfd用于stderr // 保存原始终端设置 DEBUG_LOG("Saving original config."); tcgetattr(STDIN_FILENO, &orig_term); + term_saved = 1; // 设置原始模式 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].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 stderr_log_fd = open(stderr_log, O_WRONLY | O_CREAT | O_APPEND, 0644); - + if (stdout_log_fd == -1 || stderr_log_fd == -1) { - perror("Failed to open log file"); - return; + if (stdout_log_fd >= 0) close(stdout_log_fd); + if (stderr_log_fd >= 0) close(stderr_log_fd); + return -1; } while (1) { @@ -209,7 +233,14 @@ void handle_io(int master_fd, int stderr_fd, const char *stdout_log, // 在恢复终端设置之前清除可能的未完成输出 tcflush(STDIN_FILENO, TCIFLUSH); - - // 恢复终端设置 - tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); + + return 0; } + +void restore_terminal() { + if (term_saved) { + DEBUG_LOG("Restoring terminal settings."); + tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); + term_saved = 0; + } +} \ No newline at end of file diff --git a/src/terminal_utils.h b/src/terminal_utils.h index 74d02d1..39f233a 100644 --- a/src/terminal_utils.h +++ b/src/terminal_utils.h @@ -3,8 +3,10 @@ #ifndef TERMINAL_UTILS_H #define TERMINAL_UTILS_H -void setup_termios(struct termios *term); -void handle_io(int master_fd, int stderr_fd, const char *stdout_log, - const char *stderr_log); +void setup_termios(struct termios* term); +int handle_io(int master_fd, int stderr_fd, const char* stdout_log, + const char* stderr_log); + +void restore_terminal(); #endif diff --git a/tests/makefault b/tests/makefault new file mode 100755 index 0000000..67d9f85 Binary files /dev/null and b/tests/makefault differ diff --git a/tests/test_client.c b/tests/test_client.c index a0cc64e..35a540e 100644 --- a/tests/test_client.c +++ b/tests/test_client.c @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) { 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"); diff --git a/tests/test_concurrent_client.c b/tests/test_concurrent_client.c new file mode 100644 index 0000000..676c3e1 --- /dev/null +++ b/tests/test_concurrent_client.c @@ -0,0 +1,210 @@ +#include "../src/client.h" +#include +#include +#include +#include +#include +#include + +/** + * 并发测试客户端 - 用于测试多个客户端并发访问服务端 + * + * 用法: + * ./test_concurrent_client <线程数> [延迟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 <线程数> [延迟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; + } +}