amduat-api/notes/asl_capture.c
2026-01-17 00:19:49 +01:00

273 lines
6.7 KiB
C

/*
* asl_capture.c
*
* Deterministic execution capture with optional PTY support.
*
* PIPE mode: strict stdin/stdout/stderr separation
* PTY mode: interactive, single combined stream
*/
#include "asl_capture.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* PTY support is optional and explicitly enabled */
#ifdef ASL_ENABLE_PTY
#define _GNU_SOURCE
#include <pty.h>
#endif
/* ------------------------------------------------------------------------- */
/* Utilities */
/* ------------------------------------------------------------------------- */
static void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags >= 0)
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
/* ------------------------------------------------------------------------- */
/* PIPE mode implementation */
/* ------------------------------------------------------------------------- */
static pid_t spawn_pipe(
char **argv,
int *child_stdin,
int *child_stdout,
int *child_stderr
) {
int in_p[2], out_p[2], err_p[2];
if (pipe(in_p) < 0) return -1;
if (pipe(out_p) < 0) return -1;
if (pipe(err_p) < 0) return -1;
pid_t pid = fork();
if (pid < 0) return -1;
if (pid == 0) {
/* child */
dup2(in_p[0], STDIN_FILENO);
dup2(out_p[1], STDOUT_FILENO);
dup2(err_p[1], STDERR_FILENO);
close(in_p[1]);
close(out_p[0]);
close(err_p[0]);
execvp(argv[0], argv);
perror("execvp");
_exit(127);
}
/* parent */
close(in_p[0]);
close(out_p[1]);
close(err_p[1]);
*child_stdin = in_p[1];
*child_stdout = out_p[0];
*child_stderr = err_p[0];
set_nonblocking(*child_stdout);
set_nonblocking(*child_stderr);
return pid;
}
static void pump_pipe(
int child_stdin,
int child_stdout,
int child_stderr
) {
char buf[8192];
int in_open = 1, out_open = 1, err_open = 1;
while (in_open || out_open || err_open) {
fd_set rfds;
FD_ZERO(&rfds);
if (in_open)
FD_SET(STDIN_FILENO, &rfds);
if (out_open)
FD_SET(child_stdout, &rfds);
if (err_open)
FD_SET(child_stderr, &rfds);
int maxfd = child_stdout > child_stderr
? child_stdout
: child_stderr;
if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR)
continue;
break;
}
/* stdin -> child stdin */
if (in_open && FD_ISSET(STDIN_FILENO, &rfds)) {
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n <= 0) {
close(child_stdin);
in_open = 0;
} else {
write(child_stdin, buf, n);
}
}
/* child stdout */
if (out_open && FD_ISSET(child_stdout, &rfds)) {
ssize_t n = read(child_stdout, buf, sizeof(buf));
if (n <= 0) {
close(child_stdout);
out_open = 0;
} else {
/* placeholder for ASL stdout artifact */
write(STDOUT_FILENO, buf, n);
}
}
/* child stderr */
if (err_open && FD_ISSET(child_stderr, &rfds)) {
ssize_t n = read(child_stderr, buf, sizeof(buf));
if (n <= 0) {
close(child_stderr);
err_open = 0;
} else {
/* placeholder for ASL stderr artifact */
write(STDERR_FILENO, buf, n);
}
}
}
}
/* ------------------------------------------------------------------------- */
/* PTY mode implementation */
/* ------------------------------------------------------------------------- */
#ifdef ASL_ENABLE_PTY
static pid_t spawn_pty(
char **argv,
int *pty_master_fd
) {
int master_fd;
pid_t pid = forkpty(&master_fd, NULL, NULL, NULL);
if (pid < 0)
return -1;
if (pid == 0) {
execvp(argv[0], argv);
perror("execvp");
_exit(127);
}
set_nonblocking(master_fd);
*pty_master_fd = master_fd;
return pid;
}
static void pump_pty(int pty_master) {
char buf[8192];
int open = 1;
while (open) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
FD_SET(pty_master, &rfds);
int maxfd = pty_master;
if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR)
continue;
break;
}
/* stdin -> PTY */
if (FD_ISSET(STDIN_FILENO, &rfds)) {
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0) {
write(pty_master, buf, n);
}
}
/* PTY -> stdout (combined stream) */
if (FD_ISSET(pty_master, &rfds)) {
ssize_t n = read(pty_master, buf, sizeof(buf));
if (n <= 0) {
close(pty_master);
open = 0;
} else {
/* placeholder for ASL combined output artifact */
write(STDOUT_FILENO, buf, n);
}
}
}
}
#endif /* ASL_ENABLE_PTY */
/* ------------------------------------------------------------------------- */
/* Public entry point */
/* ------------------------------------------------------------------------- */
int asl_capture_run(
asl_capture_mode_t mode,
char **argv,
asl_capture_result_t *result
) {
pid_t pid;
int status;
if (!argv || !argv[0] || !result)
return -1;
if (mode == ASL_CAPTURE_PTY) {
#ifndef ASL_ENABLE_PTY
fprintf(stderr, "asl-capture: PTY support not enabled at build time\n");
return -1;
#else
int pty_master;
pid = spawn_pty(argv, &pty_master);
if (pid < 0)
return -1;
pump_pty(pty_master);
#endif
} else {
int in_fd, out_fd, err_fd;
pid = spawn_pipe(argv, &in_fd, &out_fd, &err_fd);
if (pid < 0)
return -1;
pump_pipe(in_fd, out_fd, err_fd);
}
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
result->exit_code = WEXITSTATUS(status);
result->term_signal = 0;
} else if (WIFSIGNALED(status)) {
result->exit_code = 128;
result->term_signal = WTERMSIG(status);
} else {
result->exit_code = 128;
result->term_signal = 0;
}
return 0;
}