273 lines
6.7 KiB
C
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;
|
||
|
|
}
|
||
|
|
|