Search code examples
c++pipeforktty

Open TTY to use with execlp and dup


I am trying to create a minimal code to use pipe/fork/execlp. So far so good, I am using execlp with bash -c, so if I do.

echo asd |./a.out cat 
> asd

So it is working as expected. But if I try to use anything that needs a TTY, it does not work. Like ./a.out vim, I get "Vim: Warning: Input is not from a terminal" And the vim that was open does not works as expected.

I tried to find on the internet an example on how to open a TTY, the only one that I found was: http://www.danlj.org/lad/src/minopen.c

My Code, so far is:

#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);
    }
}

I tried to keep the minimal necessary code to make it work. Is there any way to implement TTY on this code, I know that does not seems to be such trivial task. Can someone help me with that?

I also tried to open the tty and dup it, but no luck so far.


Solution

  • Try to use pseudo terminal. You can use opentty. For your purpose you can use forkpty which combines pty with fork. I've created a small example for you. About the same as your program, just it works. I've kept it simple, so I don't handle the terminal control characters.

    #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;
    }
    

    For compiling use -lutil . While running a new tty device appears in /dev/pts . vim accepts it as a terminal.