// ctty-run: gibt dem Kindprozess ein Controlling-Terminal auf dem PTY-Slave. // Usage: ctty-run [args...] // stdin/stdout zeigen dabei bereits auf den Slave (vom Parent gesetzt), // stderr bleibt unberührt (Eltern-Pipe für garantierte Fehlerausgabe). // // Warum Double-Fork: Apples Process startet Kinder in eigener Prozessgruppe, // daher schlägt setsid() im direkten Kind mit EPERM fehl. Der Wächter forkt // einmal weiter (Enkel = kein Leader mehr), wartet auf ihn und übernimmt // dessen Exit-Code — Lifecycle/Exit-Codes bleiben für den Parent korrekt. // Der Enkel: setsid() -> Slave einmal öffnen (wird dadurch Ctty) -> exec. // Ohne Ctty scheitern ssh-Passwort- und Yes/No-Prompts still (/dev/tty ENXIO). #include #include #include #include #include #include #include int main(int argc, char **argv) { if (argc < 3) { dprintf(STDERR_FILENO, "usage: ctty-run [args...]\n"); return 2; } pid_t pid = fork(); if (pid < 0) { dprintf(STDERR_FILENO, "ctty-run: fork: %s\n", strerror(errno)); return 1; } if (pid > 0) { /* Wächter: keine FDs offen halten (sonst nie EOF), aufs Kind warten. */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); int st = 0; while (waitpid(pid, &st, 0) < 0 && errno == EINTR) ; if (WIFEXITED(st)) return WEXITSTATUS(st); return 128; } /* Enkel: eigene Session; danach Slave-Open = Ctty. */ if (setsid() < 0) { dprintf(STDERR_FILENO, "ctty-run: setsid: %s (weiter ohne Ctty)\n", strerror(errno)); } int fd = open(argv[1], O_RDWR); if (fd < 0) { dprintf(STDERR_FILENO, "ctty-run: open slave: %s\n", strerror(errno)); _exit(1); } close(fd); /* Ctty bleibt der Session erhalten */ execvp(argv[2], &argv[2]); dprintf(STDERR_FILENO, "ctty-run: execvp: %s\n", strerror(errno)); _exit(127); }