【问题标题】:Open TTY to use with execlp and dup打开 TTY 以与 execlp 和 dup 一起使用
【发布时间】:2016-06-03 14:00:51
【问题描述】:

我正在尝试创建一个最小的代码来使用 pipe/fork/execlp。 到目前为止一切顺利,我正在使用带有 bash -c 的 execlp,所以如果我这样做的话。

echo asd |./a.out cat 
> asd

所以它按预期工作。 但是,如果我尝试使用任何需要 TTY 的东西,它就不起作用。 像 ./a.out vim 一样,我得到“Vim:警告:输入不是来自终端” 并且打开的 vim 没有按预期工作。

我试图在互联网上找到一个关于如何打开 TTY 的示例,我找到的唯一一个是: http://www.danlj.org/lad/src/minopen.c

到目前为止,我的代码是:

#include <iostream>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>

typedef struct pCon{
    int fout[2];
    int fin[2];
    int fd[2];
    int pid1, pid2;
} connectionManager;

std::string command = "";

/*
 * Implementation
 */
void childFork(connectionManager *cm);

int main(int argc, char *argv[]) {
    int size;

    if(argc < 2) exit(1);
    else command = argv[1];

    connectionManager *cm = new connectionManager;
    pipe(cm->fd);
    if((cm->pid1 = fork()) == -1)exit(1);
    if (cm->pid1 == 0)
    {
        const unsigned int RCVBUFSIZE = 2000;
        char echoString[RCVBUFSIZE];

        while((size = read(fileno(stdin),echoString,RCVBUFSIZE)) > 0)
            write(cm->fd[1], echoString, size);
        close(cm->fd[1]);
     }
    else
        childFork(cm);
  return 0;
}


void childFork(connectionManager *cm){
    char *buffer = new char[2000];
    int size;
    close(cm->fd[1]);
    dup2(cm->fd[0], 0);
    close(cm->fd[0]);
    pipe(cm->fout);

    if((cm->pid2 = fork()) == -1)exit(1);
    if (cm->pid2 == 0)
    {
        close(cm->fout[0]);
        int returnCode = execlp("bash", "bash", "-c", command.c_str(), NULL);
        if(returnCode!=0)
            std::cerr << "Error starting the bash program" << std::endl;
    }
    else
    {
        close(cm->fout[1]);
        while((size = read(cm->fout[0], buffer, 2000 )) > 0 )
            write(fileno(stdout), buffer, size);
    }
}

我尝试保留最少的必要代码以使其正常工作。 有没有办法在这段代码上实现 TTY,我知道这似乎不是那么简单的任务。 有人可以帮我吗?

我也尝试打开 tty 并复制它,但到目前为止没有运气。

【问题讨论】:

  • 你打算将分叉进程的哪个 i/o 通道连接到 TTY 屏幕和 TTY 键盘(通常是 std::cout 和 std::cin)?
  • @DOUGLASO.MOEN 我想我两者都需要,在那个代码中,我希望最终能够毫无问题地打开类似 VIM 的东西。
  • 检查这个问题:superuser.com/questions/336016/…,所以你可以参考xargs的源代码来检查它是如何使用-o选项来操作stdin的。
  • 因为您的 tty 的标准输入和标准输出会转到终端进行用户交互,所以可以考虑使用选项 --command=STRING 启动 gnome-terminal(或替代品)。 STRING 标识您选择的编辑器和任何参数(文件名)。在 gnome-terminal 命令中,您还可以指定几何图形、窗口标题和工作目录。您的代码 sn-p 显示 fork,这很好,或者您可以使用 exec 系列之一,我喜欢 popen。
  • 嗯,我的问题可以用 ssh localhost "bash" 重现,它不分配 TTY,但 ssh localhost dows 工作。这个程序到底是一个“ssh”,我去掉了很多代码。 SSH 如何实现 TTY,是否需要另一个程序来实现。关于popen,我不能使用,因为你只能重定向标准输入或标准输出。上面的这个场景只是一个例子,我希望能够使用像 tar -zcvf - Folder | 这样的东西。 ./a.out "tar -xvf -"。到目前为止效果很好,但如果我需要一个终端,一切都会停止工作

标签: c++ pipe fork tty


【解决方案1】:

尝试使用伪终端。你可以使用opentty。为了您的目的,您可以使用结合了 pty 和 fork 的 forkpty。我为你创建了一个小例子。与您的程序大致相同,只是它可以工作。我保持简单,所以我不处理终端控制字符。

#include <pty.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>

int main(int argc, char *argv[])
{
    if (argc<1) return 1;

    int master;
    pid_t pid = forkpty(&master, NULL, NULL, NULL);    // opentty + login_tty + fork

    if (pid < 0) {
        return 1; // fork with pseudo terminal failed
    }

    else if (pid == 0) {   // child
        char *args[] = { argv[1], argv[2], NULL };  // prg + 1 argument

        execvp(argv[1], args);  // run the program given in first param
    }

    else {   // parent
        struct termios tios;
        tcgetattr(master, &tios);
        tios.c_lflag &= ~(ECHO | ECHONL);
        tcsetattr(master, TCSAFLUSH, &tios);

        while(1) {
            fd_set read_fd, write_fd, err_fd;

            FD_ZERO(&read_fd);
            FD_ZERO(&write_fd);
            FD_ZERO(&err_fd);
            FD_SET(master, &read_fd);
            FD_SET(STDIN_FILENO, &read_fd);

            select(master+1, &read_fd, &write_fd, &err_fd, NULL);

            if (FD_ISSET(master, &read_fd))
            {
                char ch;
                int c;
                if (c=read(master, &ch, 1) != -1)    // read from program
                    write(STDOUT_FILENO, &ch, c);    // write to tty
                else
                    break;    // exit when end of communication channel with program
            }

            if (FD_ISSET(STDIN_FILENO, &read_fd))
            {
                char ch;
                int c=read(STDIN_FILENO, &ch, 1);   // read from tty
                write(master, &ch, c);              // write to program
            }
        }
    }
    return 0;
}

编译使用 -lutil 。 运行新的 tty 设备时,会出现在 /dev/pts 中。 vim 接受它作为终端。

【讨论】:

  • 很好,它有效。只是一些问题,如果有一个网站或一个地方可以让我从中得到这些答案,那也很棒。 1. 使用 echo asd | 等示例./a.out cat 不会关闭程序,我是否应该在 STDIN_FILENO 上实现特殊代码? vim 不会调整终端区域的大小,这是预期的。最后,我如何实现对特殊终端字符的处理,是否有我有列表的地方以及应该如何实现这些列表,是否每个都有特殊的场景?
  • 你指的是哪个答案?回声 asd | ./a.out cat 不应该关闭程序。如果你启动 cat,然后输入 asd,它不会关闭程序 cat。如果您按 Enter 键,然后按 CTRL-d,它将关闭。所以回显 asd^M^D | ./a.out cat 应该做你在这里的意思。 ^M^D 是这样写的: CTRL-v,ENTER,CTRL-v,CTRL-d 。是的,在正常情况下,vim 会向终端询问其参数。您可以为伪终端设置窗口大小。例如。结构 winsize winp = {10,10}; pid_t pid = forkpty(&master, NULL, NULL, &winp);
  • 当然,如果你愿意,你可以实现特殊字符的使用。只需查找 ansi、vt100、vt220、xterm 等文档。不会是一个地方,因为终端类型比较多。
  • 如果你做 echo asd | cat 它会写“asd”关闭程序。只是 cat 不会关闭它。但我会调查的。
猜你喜欢
  • 2014-12-23
  • 2014-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2012-07-26
  • 2011-06-13
相关资源
最近更新 更多