使用伪终端运行bash报ioctl错误与无作业控制问题求助
问题背景
为给后续项目搭建技术基础,开发一款通过伪终端(pty)运行bash的程序,核心逻辑为透传所有接收到的数据。
即便已正确传递termios配置,运行时仍然抛出如下报错:
bash: cannot set terminal process group (8751): Inappropriate ioctl for device bash: no job control in this shell
以下是问题对应的完整实现代码,代码暂未实现SIGNAL处理逻辑:
#define _XOPEN_SOURCE 700 #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/poll.h> #include <pty.h> #include "libtmt/tmt.h" #include <ncurses.h> typedef struct App { int pty_fd; pid_t pid; } App; App *App_new(char **argv) { App *self = malloc(sizeof(App)); int slave_pty_fd, master_pty_fd; struct termios terminal_settings; tcgetattr(STDERR_FILENO, &terminal_settings); struct winsize terminal_size = { .ws_row = 24, .ws_col = 80, }; int ret = openpty(&master_pty_fd, &slave_pty_fd, NULL, &terminal_settings, &terminal_size); if (ret < 0) { perror("openpty"); exit(1); } self->pty_fd = master_pty_fd; self->pid = fork(); if (self->pid < 0) { perror("fork"); exit(1); } if (self->pid == 0) { close(master_pty_fd); if (dup2(slave_pty_fd, STDIN_FILENO) != STDIN_FILENO) { perror("dup2"); exit(1); } if (dup2(slave_pty_fd, STDOUT_FILENO) != STDOUT_FILENO) { perror("dup2"); exit(1); } if (dup2(slave_pty_fd, STDERR_FILENO) != STDERR_FILENO) { perror("dup2"); exit(1); } if (execvp(argv[0], argv) < 0) { perror("execvp"); exit(1); } } close(slave_pty_fd); return self; } int main() { int ret; char *argv[] = { "/bin/bash", NULL, }; App *app = App_new(argv); while (TRUE) { struct pollfd fds[2] = { { .fd = app->pty_fd, .events = POLLIN }, { .fd = STDIN_FILENO, .events = POLLIN }, }; ret = poll(fds, 2, -1); if (ret < 0) { perror("poll"); exit(1); } if (fds[0].revents & POLLIN) { char buf[1024]; ret = read(app->pty_fd, buf, sizeof(buf)); if (ret < 0) { perror("read"); exit(1); } if (ret == 0) { printf("0 read but poll said it was okay\n"); exit(0); } write(STDOUT_FILENO, buf, ret); } if (fds[1].revents & POLLIN) { char buf[1024]; ret = read(STDIN_FILENO, buf, sizeof(buf)); if (ret < 0) { perror("read"); exit(1); } if (ret == 0) { printf("0 read but poll said it was okay\n"); exit(0); } write(app->pty_fd, buf, ret); } } }
报错原因
报错的核心原因是子进程(运行bash的进程)没有将伪终端从设备设置为自己的控制终端,也没有创建独立的会话,导致bash调用tcsetpgrp设置终端进程组时权限不足,无法开启作业控制。
现有子进程逻辑仅完成了fd重定向,缺少两个关键步骤:
- 未调用
setsid()创建新会话,子进程仍然依附在父进程的会话中,没有独立的进程组权限 - 未显式将伪终端从设备设置为子进程的控制终端,bash无法对该fd执行终端相关的ioctl操作
修复方案
修改子进程分支的逻辑,在重定向fd前后补充会话创建、控制终端设置的逻辑,修改后的子进程代码如下:
if (self->pid == 0) { close(master_pty_fd); // 创建新会话,成为会话首进程 setsid(); if (dup2(slave_pty_fd, STDIN_FILENO) != STDIN_FILENO) { perror("dup2"); exit(1); } if (dup2(slave_pty_fd, STDOUT_FILENO) != STDOUT_FILENO) { perror("dup2"); exit(1); } if (dup2(slave_pty_fd, STDERR_FILENO) != STDERR_FILENO) { perror("dup2"); exit(1); } // 将伪终端从设备设置为当前进程的控制终端 ioctl(slave_pty_fd, TIOCSCTTY, 0); // 重定向完成后关闭多余的从设备fd close(slave_pty_fd); if (execvp(argv[0], argv) < 0) { perror("execvp"); exit(1); } }
补充说明:
- 使用
TIOCSCTTY宏需要提前引入头文件<sys/ioctl.h> - 部分系统上
openpty返回的从设备fd不会自动成为子进程控制终端,必须显式调用对应ioctl完成设置 - 后续补充信号处理逻辑时,还需要将父进程的标准输入设置为raw模式,避免宿主终端拦截信号、做行缓冲,导致输入输出异常
内容的提问来源于stack exchange,提问作者Rillian Grant
相关产品推荐
相关产品推荐

