fix(ui): implement async-signal-safe fork process layout and waitpid child zombie reaping for PTY
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -44,6 +44,12 @@ typedef _read_dart = int Function(int fd, ffi.Pointer<ffi.Void> buf, int count);
|
||||
typedef _write_c = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer<ffi.Void> buf, ffi.IntPtr count);
|
||||
typedef _write_dart = int Function(int fd, ffi.Pointer<ffi.Void> buf, int count);
|
||||
|
||||
typedef _waitpid_c = ffi.Int32 Function(ffi.Int32 pid, ffi.Pointer<ffi.Int32> status, ffi.Int32 options);
|
||||
typedef _waitpid_dart = int Function(int pid, ffi.Pointer<ffi.Int32> status, int options);
|
||||
|
||||
typedef _chdir_c = ffi.Int32 Function(ffi.Pointer<ffi.Char> path);
|
||||
typedef _chdir_dart = int Function(ffi.Pointer<ffi.Char> 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<ffi.Pointer<ffi.Char>>(argsCount + 2);
|
||||
argv[0] = exePtr.cast<ffi.Char>();
|
||||
|
||||
final allocatedArgs = <ffi.Pointer<Utf8>>[];
|
||||
for (var i = 0; i < argsCount; i++) {
|
||||
final ptr = arguments[i].toNativeUtf8();
|
||||
allocatedArgs.add(ptr);
|
||||
argv[i + 1] = ptr.cast<ffi.Char>();
|
||||
}
|
||||
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<ffi.Char>(), 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<ffi.Char>());
|
||||
}
|
||||
|
||||
final exePtr = executable.toNativeUtf8();
|
||||
final argsCount = arguments.length;
|
||||
final argv = malloc<ffi.Pointer<ffi.Char>>(argsCount + 2);
|
||||
argv[0] = exePtr.cast<ffi.Char>();
|
||||
|
||||
for (var i = 0; i < argsCount; i++) {
|
||||
argv[i + 1] = arguments[i].toNativeUtf8().cast<ffi.Char>();
|
||||
}
|
||||
argv[argsCount + 1] = ffi.Pointer.fromAddress(0);
|
||||
|
||||
execvp(exePtr.cast<ffi.Char>(), 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<ffi.Int32>();
|
||||
waitpid(childPid, statusPtr, 1);
|
||||
calloc.free(statusPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user