Search code examples
pythonlinuxbashfile-descriptor

Writing to File descriptor 0 (STDIN) only affects terminal. Program doesn't read


I'm studying file descriptors and I'm trying to simulate an input for FD 0 (STDIN). I'm testing in a linux environment. My intention is to write, via terminal, simulating an standard input to the code

Here is my python code:

import sys
from os import getpid

print(f'Hello world! Process: { getpid() }')


for line in sys.stdin:
    print(f'Echoing: {line}')

When I try to write into the associated FD 0 in another terminal:

echo "Test" >> /proc/<pid>/fd/0

It only prints in the terminal, the program never reads. I tried to add EOF, break line, heredoc, but I still not find a solution.

Is what I'm trying possible?


Solution

  • Thanks to @Ian Aboot's answers I could find some explanation here:

    https://unix.stackexchange.com/questions/385771/writing-to-stdin-of-a-process/385782

    According to the answer of the post above:

    Accessing /proc/PID/fd/0 doesn't access file descriptor 0 of process PID, it accesses the file which PID has open on file descriptor 0. This is a subtle distinction, but it matters. A file descriptor is a connection that a process has to a file. Writing to a file descriptor writes to the file regardless of how the file has been opened.

    and

    If /proc/PID/fd/0 is a terminal, then writing to it outputs the data on a terminal. A terminal file is bidirectional: writing to it outputs the data, i.e. the terminal displays the text; reading from a terminal inputs the data, i.e. the terminal transmits user input.

    Basically I had to control the terminal process to get the input be forwarded into my process. Writing directly to the /dev/pts* didn't work.

    Redirecting the input to a fifo, for example, worked as expected. Maybe there is a way to simulate something between the terminal process and the running program itself so I'll keep the research

    EDIT

    Finally I found a solution:

    I was using echo command, so it was just writing text to the FD, instead we need to properly make the correct simulation as a device input, fake the input.

    How to get it working? We need to simulate the input in the FD.

    In the linux there is a way to simulate the terminal input, using the iocontrols (ioctl). One of the argument options is the TIOCSTI (Terminal input/output control - Simulate terminal input) that inserts a character in the input queue. Basically it simplifies the locking/input management of a given character.

    We need the CAP_SYS_ADMIN capability to be able to execute tiocsti() so I started a Python docker container with this linux capability turned on (see reference 4).

    #app/echo.py
    import sys
    from os import getpid
    
    print(f'Hello world! Process: { getpid() }')
    
    
    for line in sys.stdin:
        print(f'Echoing: {line}')
    
    
    #app/writer.py
    
    from fcntl import ioctl
    from termios import TIOCSTI
    import sys
    
    
    with open(f'/proc/{sys.argv[1]}/fd/0', 'w') as fd:
        for char in f'{sys.argv[2]}\n':
            ioctl(fd, TIOCSTI, char)
    
    version: '3'
    
    services:
      python:
        container_name: python_fd
        image: python:3.11-rc-bullseye
        cap_add:
        - CAP_SYS_ADMIN
        command:
          - /bin/sh
          - -c
          - |
              sleep 10000
        volumes:
          - ./app:/home/app
        working_dir: /home/app/
    

    Terminal 1:

    $ docker-compose up -d 
    $ docker exec -it python_fd sh
    # python echo.py
    Hello world! Process: <pid>
    

    Terminal 2:

    $ docker exec -it python_fd sh
    # python writer.py <process pid returned in the previous command> "Hello Lais"
    

    Output of Terminal 1:

    Hello Lais
    Echoing: Hello Lais
    

    References:

    https://unix.stackexchange.com/a/345572

    https://manpages.debian.org/bullseye/manpages-dev/ioctl.2.en.html

    https://man7.org/linux/man-pages/man7/capabilities.7.html

    https://github.com/torvalds/linux/blob/master/drivers/tty/tty_io.c#L2278