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
.
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:
rlfe
keeps the entered line, but removes the echo'ed input from the child's output before displaying itrlwrap
removes the entered line and lets it be replaced by the echoWhatever approach you use, you have to know whether your input has been (in rlfe
s case) or will be (in rlwrap
s 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)