2026-01-28 19:34:39 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
|
#include "bash_executor.h"
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <sys/stat.h>
|
2026-01-29 06:01:05 +01:00
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <poll.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#define DEFAULT_TIMEOUT 300
|
2026-01-29 06:54:10 +01:00
|
|
|
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;
|
2026-01-28 19:34:39 +01:00
|
|
|
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
|
2026-01-29 06:01:05 +01:00
|
|
|
int script_fd = mkstemps(tmp_script, 3);
|
|
|
|
|
if (script_fd == -1) {
|
2026-04-03 10:42:43 +02:00
|
|
|
char *msg = strdup("Error: failed to create temp script");
|
|
|
|
|
res->output = msg ? msg : NULL;
|
2026-01-29 06:54:10 +01:00
|
|
|
return res;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
dprintf(script_fd, "%s\n", command);
|
|
|
|
|
close(script_fd);
|
|
|
|
|
int pipe_fds[2];
|
|
|
|
|
if (pipe(pipe_fds) == -1) {
|
2026-01-28 19:34:39 +01:00
|
|
|
unlink(tmp_script);
|
2026-04-03 10:42:43 +02:00
|
|
|
char *msg = strdup("Error: pipe failed");
|
|
|
|
|
res->output = msg ? msg : NULL;
|
2026-01-29 06:54:10 +01:00
|
|
|
return res;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
close(pipe_fds[0]);
|
|
|
|
|
close(pipe_fds[1]);
|
|
|
|
|
unlink(tmp_script);
|
2026-04-03 10:42:43 +02:00
|
|
|
char *msg = strdup("Error: fork failed");
|
|
|
|
|
res->output = msg ? msg : NULL;
|
2026-01-29 06:54:10 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
// Child
|
|
|
|
|
setsid(); // New session to prevent signals to parent
|
|
|
|
|
close(pipe_fds[0]);
|
2026-01-29 06:01:05 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
// 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);
|
2026-01-29 06:01:05 +01:00
|
|
|
} else {
|
2026-01-29 06:54:10 +01:00
|
|
|
dup2(pipe_fds[1], STDOUT_FILENO);
|
|
|
|
|
dup2(pipe_fds[1], STDERR_FILENO);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
|
|
|
|
|
// 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);
|
2026-01-29 06:01:05 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
// 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);
|
2026-01-29 06:01:05 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
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;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
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';
|
2026-01-29 06:01:05 +01:00
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
fclose(log_f);
|
2026-01-29 06:01:05 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
if (!res->output) res->output = strdup("");
|
2026-01-28 19:34:39 +01:00
|
|
|
unlink(tmp_script);
|
2026-01-29 06:54:10 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
|
2026-04-03 10:42:43 +02:00
|
|
|
(void)interactive;
|
2026-01-29 06:54:10 +01:00
|
|
|
r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false);
|
2026-04-03 10:42:43 +02:00
|
|
|
if (!res) return NULL;
|
|
|
|
|
char *out = res->output ? strdup(res->output) : strdup("");
|
2026-01-29 06:54:10 +01:00
|
|
|
r_process_result_free(res);
|
|
|
|
|
return out;
|
|
|
|
|
}
|