diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_core/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= b/.agents/skills/multi-agent-mux-ui/packages/mam_core/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= index df8f8a2..b60dfb0 100644 Binary files a/.agents/skills/multi-agent-mux-ui/packages/mam_core/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= and b/.agents/skills/multi-agent-mux-ui/packages/mam_core/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= differ diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= index af0147c..73440f4 100644 Binary files a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= and b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= differ diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart index 11788b2..dbdcd05 100644 --- a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart +++ b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/lib/src/pty_session.dart @@ -44,6 +44,12 @@ typedef _read_dart = int Function(int fd, ffi.Pointer buf, int count); typedef _write_c = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer buf, ffi.IntPtr count); typedef _write_dart = int Function(int fd, ffi.Pointer buf, int count); +typedef _waitpid_c = ffi.Int32 Function(ffi.Int32 pid, ffi.Pointer status, ffi.Int32 options); +typedef _waitpid_dart = int Function(int pid, ffi.Pointer status, int options); + +typedef _chdir_c = ffi.Int32 Function(ffi.Pointer path); +typedef _chdir_dart = int Function(ffi.Pointer path); + base class Winsize extends ffi.Struct { @ffi.Uint16() external int ws_row; @@ -90,8 +96,9 @@ class PtySession { final dup2 = libc.lookupFunction<_dup2_c, _dup2_dart>('dup2'); final execvp = libc.lookupFunction<_execvp_c, _execvp_dart>('execvp'); final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl'); + final chdir = libc.lookupFunction<_chdir_c, _chdir_dart>('chdir'); - // 1. Open master PTY (O_RDWR = 2, O_NOCTTY = 0x00000400) + // 1. Prepare master PTY final masterFd = posixOpenpt(2 | 0x00000400); if (masterFd < 0) { throw OSError('Failed to open pseudo-terminal master'); @@ -119,25 +126,46 @@ class PtySession { env.remove('TMUX'); env.remove('TMUX_PANE'); - // 3. Fork child process + // 3. Pre-allocate ALL structures on the heap before fork() for async-signal-safety (ยง6.7) + final pathPtr = slaveName.toNativeUtf8(); + final exePtr = executable.toNativeUtf8(); + final workDirPtr = workingDirectory?.toNativeUtf8(); + + final argsCount = arguments.length; + final argv = malloc>(argsCount + 2); + argv[0] = exePtr.cast(); + + final allocatedArgs = >[]; + for (var i = 0; i < argsCount; i++) { + final ptr = arguments[i].toNativeUtf8(); + allocatedArgs.add(ptr); + argv[i + 1] = ptr.cast(); + } + argv[argsCount + 1] = ffi.Pointer.fromAddress(0); + + // 4. Fork child process final pid = fork(); if (pid < 0) { close(masterFd); + malloc.free(pathPtr); + malloc.free(exePtr); + if (workDirPtr != null) malloc.free(workDirPtr); + malloc.free(argv); + for (final p in allocatedArgs) { + malloc.free(p); + } throw OSError('Failed to fork child process'); } if (pid == 0) { - // --- CHILD PROCESS BRANCH --- + // --- CHILD PROCESS BRANCH (Strictly async-signal-safe syscalls only!) --- setsid(); - final pathPtr = slaveName.toNativeUtf8(); final slaveFd = open(pathPtr.cast(), 2); - malloc.free(pathPtr); if (slaveFd < 0) { exit(-1); } - // TIOCSCTTY = 0x540E ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0)); dup2(slaveFd, 0); @@ -149,25 +177,23 @@ class PtySession { close(slaveFd); } - if (workingDirectory != null) { - Directory.current = workingDirectory; + if (workDirPtr != null) { + chdir(workDirPtr.cast()); } - final exePtr = executable.toNativeUtf8(); - final argsCount = arguments.length; - final argv = malloc>(argsCount + 2); - argv[0] = exePtr.cast(); - - for (var i = 0; i < argsCount; i++) { - argv[i + 1] = arguments[i].toNativeUtf8().cast(); - } - argv[argsCount + 1] = ffi.Pointer.fromAddress(0); - execvp(exePtr.cast(), argv); exit(-2); } // --- PARENT PROCESS BRANCH --- + malloc.free(pathPtr); + malloc.free(exePtr); + if (workDirPtr != null) malloc.free(workDirPtr); + malloc.free(argv); + for (final p in allocatedArgs) { + malloc.free(p); + } + return PtySession._( masterFd: masterFd, slaveName: slaveName, @@ -246,6 +272,12 @@ class PtySession { ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig), int Function(int pid, int sig) >('kill'); - kill(childPid, 15); + kill(childPid, 15); // SIGTERM = 15 + + // Reap child zombie process (non-blocking WNOHANG = 1) + final waitpid = libc.lookupFunction<_waitpid_c, _waitpid_dart>('waitpid'); + final statusPtr = calloc(); + waitpid(childPid, statusPtr, 1); + calloc.free(statusPtr); } } diff --git a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart index 5ca63a1..e0460b5 100644 --- a/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart +++ b/.agents/skills/multi-agent-mux-ui/packages/mam_pty/test/pty_runtime_test.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:mam_pty/mam_pty.dart'; import 'package:test/test.dart';