// retoor #define _GNU_SOURCE #include "bash_executor.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_TIMEOUT 300 void r_process_result_free(r_process_result_t *res) { if (!res) return; free(res->output); free(res->log_path); free(res); } static char *get_log_path(int pid) { char *path = NULL; if (asprintf(&path, "/tmp/r_process_%d.log", pid) == -1) return NULL; return path; } r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds, bool async) { if (!command) return NULL; r_process_result_t *res = calloc(1, sizeof(r_process_result_t)); if (!res) return NULL; if (timeout_seconds <= 0) timeout_seconds = DEFAULT_TIMEOUT; char tmp_script[] = "/tmp/r_bash_XXXXXX.sh"; int script_fd = mkstemps(tmp_script, 3); if (script_fd == -1) { char *msg = strdup("Error: failed to create temp script"); res->output = msg ? msg : NULL; return res; } dprintf(script_fd, "%s\n", command); close(script_fd); int pipe_fds[2]; if (pipe(pipe_fds) == -1) { unlink(tmp_script); char *msg = strdup("Error: pipe failed"); res->output = msg ? msg : NULL; return res; } pid_t pid = fork(); if (pid == -1) { close(pipe_fds[0]); close(pipe_fds[1]); unlink(tmp_script); char *msg = strdup("Error: fork failed"); res->output = msg ? msg : NULL; return res; } if (pid == 0) { // Child setsid(); // New session to prevent signals to parent close(pipe_fds[0]); // Setup log file for child char *log_p = get_log_path(getpid()); int log_fd = open(log_p, O_WRONLY | O_CREAT | O_TRUNC, 0644); free(log_p); if (log_fd != -1) { dup2(log_fd, STDOUT_FILENO); dup2(log_fd, STDERR_FILENO); close(log_fd); } else { dup2(pipe_fds[1], STDOUT_FILENO); dup2(pipe_fds[1], STDERR_FILENO); } // Also pipe back to parent if possible (redundant but safe for short commands) // Actually, let's just use log file for everything. close(pipe_fds[1]); char *args[] = {"bash", tmp_script, NULL}; execvp("bash", args); exit(1); } // Parent res->pid = pid; res->log_path = get_log_path(pid); res->is_running = true; close(pipe_fds[1]); close(pipe_fds[0]); if (async) { res->output = strdup("Process started in background."); usleep(100000); // Give child time to start unlink(tmp_script); return res; } // Wait for timeout time_t start_time = time(NULL); long last_read_pos = 0; while (true) { int status; pid_t ret = waitpid(pid, &status, WNOHANG); // Read new content from log file and print to stdout for the user FILE *f_tail = fopen(res->log_path, "r"); if (f_tail) { fseek(f_tail, last_read_pos, SEEK_SET); char tail_buf[4096]; while (fgets(tail_buf, sizeof(tail_buf), f_tail)) { fprintf(stdout, "[%d]\t %s", pid, tail_buf); fflush(stdout); } last_read_pos = ftell(f_tail); fclose(f_tail); } if (ret == pid) { res->is_running = false; res->exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1; break; } else if (ret == -1) { res->is_running = false; break; } if (time(NULL) - start_time >= timeout_seconds) { res->timed_out = true; break; } usleep(50000); // 100ms -> 50ms for better responsiveness } // Read log file for output FILE *log_f = fopen(res->log_path, "r"); if (log_f) { fseek(log_f, 0, SEEK_END); long size = ftell(log_f); rewind(log_f); if (size >= 0) { res->output = malloc((size_t)size + 1); if (res->output) { size_t rs = fread(res->output, 1, (size_t)size, log_f); res->output[rs] = '\0'; } } fclose(log_f); } if (!res->output) res->output = strdup(""); unlink(tmp_script); return res; } char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) { (void)interactive; r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false); if (!res) return NULL; char *out = res->output ? strdup(res->output) : strdup(""); r_process_result_free(res); return out; }