3

I'm writing a program to create a pty, then fork and execute an ssh command with the slave side of the pty as its stdin. The full source code is here.

using namespace std;
#include <iostream>
#include <unistd.h>
#include <fcntl.h>

int main() {

    int fd = posix_openpt(O_RDWR);
    grantpt(fd);
    unlockpt(fd);

    pid_t pid = fork();

    if (pid == 0) { //slave

        freopen(ptsname(fd), "r", stdin);

        execlp("ssh", "ssh", "user@192.168.11.40", NULL);

    } else { //master

        FILE *f = fdopen(fd, "w");
        string buf;

        while (true) {

            getline(cin, buf);

            if (!cin) {
                break;
            }

            fprintf(f, "%s\n", buf.c_str());

        }

    }

}

After executing this program and inputting just echo hello (and a newline), the child command re-sends my input before its own output, thus duplicating my input line:

~ $ echo hello
echo hello #duplication
hello

~ $ 

I think this is due to the fact that a pty behaves almost the same as a normal terminal. If I add freopen("log.txt", "w", stdout);" and input the same command, I get just

echo hello #This is printed because I typed it.

and the contents of log.txt is this:

~ $ echo hello #I think this is printed because a pty simulates input.
hello                             

~ $

How can I avoid the duplication?


Is that realizable?

I know it is somehow realizable, but don't know how to. In fact, the rlwrap command behaves the same as my program, except that it doesn't have any duplication:

~/somedir $ rlwrap ssh user@192.168.11.40
~ $ echo hello
hello

~ $

I'm reading the source code of rlwrap now, but haven't yet understood its implementation.


Supplement

As suggested in this question (To me, not the answer but the OP was helpful.), unsetting the ECHO terminal flag disables the double echoing. In my case, adding this snippets to the slave block solved the problem.

termios terminal_attribute;
int fd_slave = fileno(fopen(ptsname(fd_master), "r"));
tcgetattr(fd_slave, &terminal_attribute);
terminal_attribute.c_lflag &= ~ECHO;
tcsetattr(fd_slave, TCSANOW, &terminal_attribute);

It should be noted that this is not what rlwrap does. As far as I tested rlwrap <command> never duplicates its input line for any <command> However, my program echoes twice for some <command>s. For example,

~ $ echo hello
hello #no duplication

~ $ /usr/bin/wolfram
Mathematica 12.0.1 Kernel for Linux ARM (32-bit)
Copyright 1988-2019 Wolfram Research, Inc.

In[1]:= 3 + 4                                                                   
        3 + 4 #duplication (my program makes this while `rlwrap` doesn't)

Out[1]= 7

In[2]:=    

Is this because the <command> (ssh when I run wolfram remotely) re-enables echoing? Anyway, I should keep reading the source code of rlwrap.

Hans Lub
  • 5,513
  • 1
  • 23
  • 43
ynn
  • 3,386
  • 2
  • 19
  • 42
  • Does `rlwrap` *create* a pseudoterminal, or just use its terminal interface to drive a pipe? – Davis Herring Nov 23 '19 at 18:49
  • 1
    (`rlwrap` author here:) As you already oberved, after the child has called `exec` the terminal flags of the slave side are not under your control anymore, and the child may (and often will) re-enable echo. Both `rlwrap` and `rlfe` solve the problem in their own (different) ways: either remove your own input before displaying the child's output, or do _not_ display the first line of this output. I'm voting to re-open the question in order to give a proper answer – Hans Lub Nov 25 '19 at 08:26
  • @HansLub Wow. I'm really glad to get a comment from the original author. I tried to *remove your own input before displaying the child's output* before I posted this question, but not succeeded maybe due to [this](https://unix.stackexchange.com/questions/420424/why-does-ssh-t-option-adds-cr-lf-in-redirected-output). By the way, finally I found there is `-rawterm` option for `wolfram` which avoids the command's re-enabling echoing. – ynn Nov 25 '19 at 12:23
  • @HansLub The reason why I'm trying to write a program which is similar to `rlwrap` is `rlwrap` doesn't work with `ssh -t /usr/bin/wolfram`. Now I implemented `rl` ([GitHub](https://github.com/your-diary/Rl)) which has really less functionality than `rlwrap` but works with the command. `rlwrap` works with `ssh /usr/bin/wolfram` (here, not with `-t`) but, in my case, `-t` is needed to send a signal to `/usr/bin/wolfram` through `ssh` (c.f. [this post](https://stackoverflow.com/questions/57015738/signal-handler-not-working-on-process-opened-via-pipe)). – ynn Nov 25 '19 at 12:27
  • `wolfram` (without the `-rawterm`) includes control characters in its prompt, which confuses `rlwrap`. Using `-rawterm` to prevent this, and the `-a` (or `--always-readline`) `rlwrap` option (to make `rlwrap` use `readline` even when `wolfram` puts its teminal in raw mode will make `rlwrap` usable. For me, `rlwrap -a -pRed ssh -t localhost /usr/bin/wolfram -rawterm` seems to work well at first sight. That said, `rlwrap` was designed for simple console programs without line editing and history. `wolfram` hardly fits this description. – Hans Lub Nov 25 '19 at 13:56
  • @HansLub Thank you. `-a` and `-rawterm` also worked on my environment. I should have seen the manpage (or [this issue](https://github.com/hanslub42/rlwrap/issues/74)) of `rlwrap` to find the existence of `-a` option. I've learned much in writing my original `rl` though. – ynn Nov 25 '19 at 14:17

1 Answers1

1

As you already observed, after the child has called exec() the terminal flags of the slave side are not under your control anymore, and the child may (and often will) re-enable echo. This means that is is not of much use to change the terminal flags in the child before calling exec.

Both rlwrap and rlfe solve the problem in their own (different) ways:

Whatever approach you use, you have to know whether your input has been (in rlfes case) or will be (in rlwraps case) echoed back. rlwrap, at least, does this by not closing the pty's slave end in the parent process, and then watching its terminal settings (in this case, the ECHO bit in its c_lflag) to know whether the slave will echo or not.

All this is rather cumbersome, of course. The rlfe approach is probably easier, as it doesn't require the use of the readline library, and you could simply strcmp() the received output with the input you just sent (which will only go wrong in the improbable case of a cat command that disables echo on its input)

Hans Lub
  • 5,513
  • 1
  • 23
  • 43
  • 1
    I'll remember this awesome and technical implementation: *rlwrap, at least, does this by not closing the pty's slave end in the parent process, and then watching its terminal settings* – ynn Nov 25 '19 at 16:10