0

As per the manual of popen, which says that[emphasis mine]:

The popen() function opens a process by creating a pipe, forking, and invoking the shell. Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only.

As per the statements above, there is no way to both write and read to the file stream returned by popen. So I take it for granted that the stdin of current process would not influence the behaviour of the shell command which is passed as parameter for popen().

But it's really out of my expectation when running the program below.

Here is the code snippet code:

//filename pipe_cmd.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int ret = 0;
    FILE* file = popen("ssh -t jhone@192.168.1.51  bash -c 'top' ", "r");

    int fd = fileno(file);
    char buffer[1024*100];
    
    if(NULL != file)
    {
        while (true) {
            memset(buffer, '\0', sizeof(buffer));
            int ret_read = read(fd, buffer, sizeof(buffer));
            
            if (ret_read > 0) {
              printf("%s", buffer);
            } else if (0 == ret_read) {
              ret = 0;
              break;
            } else {
              ret = -1;
              break;
            }
        }
    }
    
    return ret;
}

The code snippet is compiled by gcc pipe_cmd.c -o pipe_cmd.

When program named pipe_cmd is running, I can see the all the information about the processes on the remote computer, which is as expected.

But when I hit h key, I can see such help message on my local terminal, which is really out of my expectation. Here is what I see:

Help for Interactive Commands - procps-ng version 3.3.9
Window 1:Def: Cumulative mode Off.  System: Delay 0.1 secs; Secure mode Off.

  Z,B,E,e   Global: 'Z' colors; 'B' bold; 'E'/'e' summary/task memory scale
  l,t,m     Toggle Summary: 'l' load avg; 't' task/cpu stats; 'm' memory info
  0,1,2,3,I Toggle: '0' zeros; '1/2/3' cpus or numa node views; 'I' Irix mode
  f,F,X     Fields: 'f'/'F' add/remove/order/sort; 'X' increase fixed-width

  L,&,<,> . Locate: 'L'/'&' find/again; Move sort column: '<'/'>' left/right
  R,H,V,J . Toggle: 'R' Sort; 'H' Threads; 'V' Forest view; 'J' Num justify
  c,i,S,j . Toggle: 'c' Cmd name/line; 'i' Idle; 'S' Time; 'j' Str justify
  x,y     . Toggle highlights: 'x' sort field; 'y' running tasks
  z,b     . Toggle: 'z' color/mono; 'b' bold/reverse (only if 'x' or 'y')
  u,U,o,O . Filter by: 'u'/'U' effective/any user; 'o'/'O' other criteria
  n,#,^O  . Set: 'n'/'#' max tasks displayed; Show: Ctrl+'O' other filter(s)
  C,...   . Toggle scroll coordinates msg for: up,down,left,right,home,end

  k,r       Manipulate tasks: 'k' kill; 'r' renice
  d or s    Set update interval
  W,Y       Write configuration file 'W'; Inspect other output 'Y'
  q         Quit
          ( commands shown with '.' require a visible task display window ) 
Press 'h' or '?' for help with Windows,

What a surprise! The aforementioned behaviour looks like the top command is directly called in my local bash, but it's really called by popen.

I am really confused now. Could somebody shed some light on this matter?

Note:

The IP address(i.e. 192.168.1.51) in the code snippet is my local computer's IP. But I don't think it's the reason why the program should work in this way.

I will try to run this program on another computer right now.

UPDATED:

Some phenomena when this program runs on another computer whose IP address is different without any modification for the aforementioned code snippet.

How to solve this problem? I don't want the stdin of current process influence the behaviour of the shell command. One method that I know is to use pipe & fork and close the stdin. Any better way to achieve this goal with popen?

John
  • 2,963
  • 11
  • 33
  • `popen` connects the stdout to your process. But stdin is unchanged. So the child process is reading stdin just as it normally would. From the [popen manual](https://www.man7.org/linux/man-pages/man3/popen.3.html): "*the command's standard input is the same as that of the process that called popen().*" – kaylum Jun 15 '22 at 06:34
  • 'Current progress'? Do yo mean current *process?* – user207421 Jun 15 '22 at 06:54
  • @kaylum Any reference? I have carefully read the manual, but I really don't see such saying, indeed. And what's more, how to solve this problem? I don't want the `stdin` of current process influence the behaviour of the shell command. One method that I know is to use pipe and close the `stdin`. Any better way to achieve this goal when still uses `popen`? – John Jun 15 '22 at 06:56
  • I added the reference to my first comment already. – kaylum Jun 15 '22 at 07:00
  • if you need to use stdin and stdout (stderr) with your sub process, you should probably use fork and bind in/out accordingly – OznOg Jun 15 '22 at 07:03
  • @kaylum 1. If I understand you correctly, the `stdin` is shared by the process that called `popen()` and the command itself. 2. If that is right, neither the process that called `open()` nor the command itself is guaranteed to read complete data which is sent to the `stdin` because they compete with each other. If I miss something, please let me know. – John Jun 15 '22 at 07:10
  • "*because they compete with each other*". Yes that is the case. If you run two programs concurrently that both read from stdin then yes they will compete. That is the nature of concurrent processes. It is up to the coder to prevent that if that is the desired behaviour. That is, yes you need the parent process to redirect the stdin of the child process if that is the behaviour you want. – kaylum Jun 15 '22 at 07:19
  • @kaylum If I understand you correctly, you intend to say the parent process need to ***redirect*** the stdin ***to*** the child process. As far as I can see, it seems this goal could not be achieved by using `popen`, because the manual says the stdin is shared between them and it seems no way to redirect the `stdin`. If I miss something, please let me know. – John Jun 15 '22 at 07:27

1 Answers1

2

From the same manual:

reading from the stream reads the command's standard output, and the command's standard input is the same as that of the process that called popen()

The command you are executing is ssh. It tries to read from the same terminal you are running your program from. This creates a race, but since the program doesn't read anything, ssh always wins. Then ssh passes your input to the remote program which interprets it.

You can redirect the standard input of the command being executed with normal shell syntax

FILE* file = popen("... < /dev/null", "r");

but this will not work with top and other interactive commands that expect their standard input to be a terminal.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you for the clarification. 1. If I understand you correctly, the `stdin` is shared by the process that called `popen()` and the command itself. 2. If that is right, neither the process that called `open()` nor the command itself is guaranteed to read complete data which is sent to the `stdin` because they compete with each other. If I miss something, please let me know. – John Jun 15 '22 at 07:11
  • This is exactly correct. – n. m. could be an AI Jun 15 '22 at 07:18
  • ***this will not work with top and other interactive commands that expect their standard input to be a terminal.*** If I understand you correctly, it seems this goal could not be achieved by using `popen`, because the manual says the `stdin` is shared between processes and it ***seems no way to redirect the `stdin`*** (i.e. for `top`). If I miss something, please let me know. – John Jun 15 '22 at 07:49
  • @John `top` wants a terminal and refuses to work without one. There is no way to make it not so. You need a terminal, and you need to make it the standard input of `top` somehow. If you redirect the standard input to say `dev/null`, with `popen` or otherwise, `top` will not work. The only way to make `top` not read from your terminal is to redirect it to *another* terminal. – n. m. could be an AI Jun 15 '22 at 08:01
  • ***The only way to make top not read from your terminal is to give it another terminal.*** How to achieve this goal? – John Jun 15 '22 at 08:03
  • Read about pseudoterminals. You need to create a master-slave pair with `openpty`, give the slave part to your interactive program, and leave the master part alone. You probably want to use `fork/exec` with it, but if you really want to, you can use `popen` and shell redirection like this: `sprintf(cmd, "yourcommand < %s", ptsname(master)); file = popen(cmd, "r");` – n. m. could be an AI Jun 15 '22 at 08:14