From 9c5207c59dcec1b27a224459941a175fd5668cfb Mon Sep 17 00:00:00 2001 From: "QCQCQC@wsl" <1220204124@zust.edu.cn> Date: Sun, 7 Dec 2025 23:16:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E9=83=A8?= =?UTF-8?q?=E5=88=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 13 +- src/client.c | 21 ++-- src/client.h | 2 +- src/exec_hook.h | 17 ++- src/execve_interceptor.c | 24 +++- src/init_cleanup.c | 26 +++- src/init_cleanup.h | 2 +- src/logging.c | 36 +++--- src/logging.h | 2 +- src/pty_dup.c | 45 ++++--- src/pty_dup.h | 4 +- src/terminal_utils.c | 49 ++++++-- src/terminal_utils.h | 8 +- tests/makefault | Bin 0 -> 16840 bytes tests/test_client.c | 2 +- tests/test_concurrent_client.c | 210 +++++++++++++++++++++++++++++++++ 16 files changed, 385 insertions(+), 76 deletions(-) create mode 100755 tests/makefault create mode 100644 tests/test_concurrent_client.c 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 0000000000000000000000000000000000000000..67d9f851183f4e680568c244c74fa609b5d2fce6 GIT binary patch literal 16840 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 literal 0 HcmV?d00001 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; + } +}