capture output on linux

This commit is contained in:
Nikita Smith
2026-06-02 12:10:07 -07:00
committed by Ryan Fleury
parent 05fc085dc6
commit 60a38247af
+155 -26
View File
@@ -295,7 +295,6 @@ t_cwd_path(void)
Temp scratch = scratch_begin(0, 0);
String8 cwd = get_current_path(scratch.arena);
cwd = str8_chop_last_slash(cwd);
cwd = str8f(scratch.arena, "%S", cwd);
MemoryCopyStr8(path, cwd);
path[cwd.size] = 0;
scratch_end(scratch);
@@ -325,7 +324,7 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
B32 is_ok = 0;
// clean up global state
arena_pop_to(g_output_arena, 0);
arena_clear(g_output_arena);
MemoryZeroStruct(&g_output);
g_last_exit_code = max_U64;
@@ -383,12 +382,36 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
for EachElement(i, captures_win32) { read_capture_handles[i] = (File){ (U64)captures_win32[i].read_pipe_handle }; }
for EachElement(i, captures_win32) { write_capture_handles[i] = (File){ (U64)captures_win32[i].write_pipe_handle }; }
#else
# error NotImplemented
typedef struct {
int fds[2];
String8List *parts;
B32 is_live;
struct pollfd *poll_fd;
} LinuxCapture;
LinuxCapture captures_linux[2] = {0};
for EachElement(i, captures_linux) {
if (pipe2(captures_linux[i].fds, 0) != 0) {
fprintf(stderr, "ERROR: failed to create pipe for output capture\n");
goto exit;
}
captures_linux[i].is_live = 1;
}
captures_linux[0].parts = &stdout_parts;
captures_linux[1].parts = &stderr_parts;
File read_capture_handles[2] = {0}, write_capture_handles[2] = {0};
read_capture_handles[0].u64[0] = captures_linux[0].fds[0];
read_capture_handles[1].u64[0] = captures_linux[1].fds[0];
write_capture_handles[0].u64[0] = captures_linux[0].fds[1];
write_capture_handles[1].u64[0] = captures_linux[1].fds[1];
#endif
// Build Launch Options
ProcessLaunchParams launch_opts = {
.path = g_wdir,
.inherit_env = 1,
.consoleless = 1,
.env = env,
.stdout_file = write_capture_handles[stdout_idx],
.stderr_file = write_capture_handles[stderr_idx],
@@ -425,41 +448,41 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
wait_handles[wait_handle_count++] = (HANDLE)process_handle.u64[0];
}
for EachElement(i, captures_win32) {
while (captures_win32[i].state == Win32CaptureState_Null) {
for EachElement(capture_idx, captures_win32) {
while (captures_win32[capture_idx].state == Win32CaptureState_Null) {
// init overlapped so when child writes to capture buffer this event is signaled
AssertAlways(ResetEvent(captures_win32[i].event));
MemoryZeroStruct(&captures_win32[i].overlapped);
captures_win32[i].overlapped.hEvent = captures_win32[i].event;
AssertAlways(ResetEvent(captures_win32[capture_idx].event));
MemoryZeroStruct(&captures_win32[capture_idx].overlapped);
captures_win32[capture_idx].overlapped.hEvent = captures_win32[capture_idx].event;
// begin overlapped read
DWORD read_size = 0;
if (ReadFile(captures_win32[i].read_pipe_handle, captures_win32[i].buffer, captures_win32[i].buffer_size, &read_size, &captures_win32[i].overlapped)) {
if (ReadFile(captures_win32[capture_idx].read_pipe_handle, captures_win32[capture_idx].buffer, captures_win32[capture_idx].buffer_size, &read_size, &captures_win32[capture_idx].overlapped)) {
if (read_size > 0) {
String8 string = str8(captures_win32[i].buffer, read_size);
String8 string = str8(captures_win32[capture_idx].buffer, read_size);
String8 string_copy = str8_copy(scratch.arena, string);
str8_list_push(scratch.arena, captures_win32[i].parts, string_copy);
str8_list_push(scratch.arena, captures_win32[capture_idx].parts, string_copy);
} else {
captures_win32[i].state = Win32CaptureState_EOF;
captures_win32[capture_idx].state = Win32CaptureState_EOF;
}
} else {
DWORD error = GetLastError();
if (error == ERROR_IO_PENDING) {
captures_win32[i].state = Win32CaptureState_Pending;
captures_win32[capture_idx].state = Win32CaptureState_Pending;
} else {
AssertAlways(error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE);
captures_win32[i].state = Win32CaptureState_EOF;
captures_win32[capture_idx].state = Win32CaptureState_EOF;
}
break;
}
}
if (captures_win32[i].state == Win32CaptureState_Pending) {
if (captures_win32[capture_idx].state == Win32CaptureState_Pending) {
// event now should signal whenever pipe has data to read
captures_win32[i].wait_idx = wait_handle_count++;
wait_handles[captures_win32[i].wait_idx] = captures_win32[i].event;
captures_win32[capture_idx].wait_idx = wait_handle_count++;
wait_handles[captures_win32[capture_idx].wait_idx] = captures_win32[capture_idx].event;
} else {
captures_win32[i].wait_idx = max_U64;
captures_win32[capture_idx].wait_idx = max_U64;
}
}
@@ -469,14 +492,10 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
// compute wait time
DWORD wait_ms = INFINITE;
if (timeout_us != max_U64) {
U64 now_us = os_now_microseconds();
U64 now_us = now_time_us();
wait_ms = now_us < endt_us ? ClampTop((endt_us - now_us + 999) / 1000, max_U32-1) : 0;
}
if (wait_ms == 0) {
DebugBreakProcess((HANDLE)process_handle.u64[0]);
}
// wait on process and read pipes
DWORD wait_result = WaitForMultipleObjects(wait_handle_count, wait_handles, 0, wait_ms);
@@ -565,7 +584,117 @@ t_invoke_env(String8 exe_path, String8 cmdline, String8List env, U64 timeout_us)
}
}
#elif OS_LINUX
# error NotImplemented
// close handle so read(2) does not block
for EachElement(i, write_capture_handles) {
close((int)write_capture_handles[i].u64[0]);
MemoryZeroStruct(&write_capture_handles[i]);
}
pid_t pid = (pid_t)process_handle.u64[0];
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
fprintf(stderr, "ERROR: failed to translate pid(%d) to pidfd\n", pid);
goto exit;
}
B32 is_process_live = 1;
U64 endt_us = ENDT_US(timeout_us);
U64 read_buffer_default_size = MB(1);
U64 read_buffer_size = read_buffer_default_size;
U8 *read_buffer = push_array(scratch.arena, U8, read_buffer_default_size);
for (;;) {
struct pollfd fds[3] = {0};
int nfds = 0;
// append process
if (is_process_live) {
fds[nfds++] = (struct pollfd){ .fd = pidfd, .events = POLLIN | POLLHUP };
}
// append pipes
for EachElement(i, captures_linux) {
if (captures_linux[i].is_live) {
captures_linux[i].poll_fd = &fds[nfds];
fds[nfds++] = (struct pollfd){ .fd = captures_linux[i].fds[0], .events = POLLIN | POLLHUP };
}
}
// exit if there are no more handles to poll
if (nfds == 0) { break; }
// compute wait time
int wait_ms = -1;
if (timeout_us != max_U64) {
U64 now_us = now_time_us();
wait_ms = now_us < endt_us ? ClampTop((endt_us - now_us + 999) / 1000, max_U32-1) : 0;
}
// wait for kernel to signal any of the wait handles
int poll_result = poll(fds, nfds, wait_ms);
if (poll_result < 0) {
fprintf(stderr, "ERROR: poll failed with errono %d\n", errno);
break;
}
if (poll_result == 0) {
fprintf(stderr, "WARNING: poll timeout\n");
break;
}
// handle case where process exited while waiting for a signal
if (is_process_live) {
if (fds[0].revents & POLLIN) {
// reap process
int status;
if (waitpid(pid, &status, 0) >= 0) {
g_last_exit_code = WEXITSTATUS(status);
} else {
fprintf(stderr, "ERROR: failed to reap process %d\n", pid);
}
// signal on process fd means exit
is_process_live = 0;
}
}
for EachElement(i, captures_linux) {
if (captures_linux[i].is_live) {
if (captures_linux[i].poll_fd->revents & POLLIN) {
for (;;) {
if (read_buffer_size == 0) {
read_buffer_size = read_buffer_default_size;
read_buffer = push_array(scratch.arena, U8, read_buffer_default_size);
}
ssize_t read_size = OS_LNX_RETRY_ON_EINTR(read(captures_linux[i].poll_fd->fd, read_buffer, read_buffer_size));
if (read_size > 0) {
str8_list_push(scratch.arena, captures_linux[i].parts, str8(read_buffer, read_size));
read_buffer += read_size;
read_buffer_size -= read_size;
} else if (read_size == 0) {
captures_linux[i].is_live = 0;
break;
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// no more data in the pipe
} else {
fprintf(stderr, "ERROR: failed to read pipe, errno %d\n", errno);
captures_linux[i].is_live = 0;
}
break;
}
}
}
if (captures_linux[i].poll_fd->revents & POLLHUP) {
captures_linux[i].is_live = 0;
}
}
}
}
// close process handle
if (close(pidfd) < 0) {
fprintf(stderr, "ERROR: failed to close process handle %d\n", pidfd);
}
#endif
// update output global
@@ -1062,9 +1191,9 @@ t_entry_point(CmdLine *cmdline)
}
// run test
U64 run_start_time = os_now_microseconds();
U64 run_start_time = now_time_us();
T_RunResult result = t_run(g_torture_tests[target_idx].r, g_torture_tests[target_idx].user_data);
U64 run_end_time = os_now_microseconds();
U64 run_end_time = now_time_us();
// print result
if (result.status == T_RunStatus_Pass) {
fprintf(stdout, "\x1b[32m" "%s" "\x1b[0m", t_string_from_result(result.status));