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_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 _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 {
|
base class Winsize extends ffi.Struct {
|
||||||
@ffi.Uint16()
|
@ffi.Uint16()
|
||||||
external int ws_row;
|
external int ws_row;
|
||||||
@@ -90,8 +96,9 @@ class PtySession {
|
|||||||
final dup2 = libc.lookupFunction<_dup2_c, _dup2_dart>('dup2');
|
final dup2 = libc.lookupFunction<_dup2_c, _dup2_dart>('dup2');
|
||||||
final execvp = libc.lookupFunction<_execvp_c, _execvp_dart>('execvp');
|
final execvp = libc.lookupFunction<_execvp_c, _execvp_dart>('execvp');
|
||||||
final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl');
|
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);
|
final masterFd = posixOpenpt(2 | 0x00000400);
|
||||||
if (masterFd < 0) {
|
if (masterFd < 0) {
|
||||||
throw OSError('Failed to open pseudo-terminal master');
|
throw OSError('Failed to open pseudo-terminal master');
|
||||||
@@ -119,25 +126,46 @@ class PtySession {
|
|||||||
env.remove('TMUX');
|
env.remove('TMUX');
|
||||||
env.remove('TMUX_PANE');
|
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();
|
final pid = fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
close(masterFd);
|
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');
|
throw OSError('Failed to fork child process');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
// --- CHILD PROCESS BRANCH ---
|
// --- CHILD PROCESS BRANCH (Strictly async-signal-safe syscalls only!) ---
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
final pathPtr = slaveName.toNativeUtf8();
|
|
||||||
final slaveFd = open(pathPtr.cast<ffi.Char>(), 2);
|
final slaveFd = open(pathPtr.cast<ffi.Char>(), 2);
|
||||||
malloc.free(pathPtr);
|
|
||||||
if (slaveFd < 0) {
|
if (slaveFd < 0) {
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TIOCSCTTY = 0x540E
|
|
||||||
ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0));
|
ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0));
|
||||||
|
|
||||||
dup2(slaveFd, 0);
|
dup2(slaveFd, 0);
|
||||||
@@ -149,25 +177,23 @@ class PtySession {
|
|||||||
close(slaveFd);
|
close(slaveFd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workingDirectory != null) {
|
if (workDirPtr != null) {
|
||||||
Directory.current = workingDirectory;
|
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);
|
execvp(exePtr.cast<ffi.Char>(), argv);
|
||||||
exit(-2);
|
exit(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PARENT PROCESS BRANCH ---
|
// --- 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._(
|
return PtySession._(
|
||||||
masterFd: masterFd,
|
masterFd: masterFd,
|
||||||
slaveName: slaveName,
|
slaveName: slaveName,
|
||||||
@@ -246,6 +272,12 @@ class PtySession {
|
|||||||
ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig),
|
ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig),
|
||||||
int Function(int pid, int sig)
|
int Function(int pid, int sig)
|
||||||
>('kill');
|
>('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:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:mam_pty/mam_pty.dart';
|
import 'package:mam_pty/mam_pty.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user