Home > other >  bash input redirection breaks stdin
bash input redirection breaks stdin

Time:03-13

I have a program that looks like this:

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

int enable_keyboard() {
    struct termios new_term_attr;

    tcgetattr(fileno(stdin), &new_term_attr);
    new_term_attr.c_lflag &= ~(ECHO|ICANON);
    new_term_attr.c_cc[VTIME] = 0;
    new_term_attr.c_cc[VMIN] = 0;
    return tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);
}

int main() {
    errno = 0;
    unsigned char field[H][W];

    fill (field);
    char c = enable_keyboard();;
    while(1) {
        read(0, &c, 1);
        printf("%d ", c);
    }
}

It reads a single char from stdin and displays it infinitely(in order to check whether enable_keybord works fine).
The problem is that when I run this program with input redirection(line ./a.out < test.txt) it breaks all the input and errno is set to be 25.

Also tcsetattr returns -201 when it should return 0 or -1.

I tried to clear stdin with scanf before tcsetattr and also disabling tcsetattr completely but it turns out input redirection hangs all the input completely.

Without input redirection everything works perfectly fine so I guess maybe bash does something with stdin so it freezes dead in program.

Any ideas how to fix the problem?

CodePudding user response:

errno 25 = "Inappropriate ioctl for device"; you are performing terminal operations on a file. You do not check the return from tcgetattr(). On error, it sets errno and that is set to ENOTTY if the file descriptor does not represent a terminal. So:

int enable_keyboard() 
{
    struct termios new_term_attr;
    int status = -1 ;

    if( tcgetattr(fileno(stdin), &new_term_attr) == 0 )
    {
        new_term_attr.c_lflag &= ~(ECHO|ICANON);
        new_term_attr.c_cc[VTIME] = 0;
        new_term_attr.c_cc[VMIN] = 0;
        status = tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);
    }

    return status ;
}

That will not solve your "hanging" issue however - as you mentioned, your program loops indefinitely - the while loop does not terminate on EOF. The problem is that EOF is not explicitly indicated by read() - rather it returns zero - which for terminal input simply mean no characters. In that case:

char c = 0 ;
bool is_file = enable_keyboard() != 0 ;
bool terminate = false ;

while( !terminate ) 
{
    terminate = read(0, &c, 1) == 0 && is_file ;
    printf("%d ", c);
}

Putting that all together:

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <stdbool.h>

int enable_keyboard() 
{
    struct termios new_term_attr;
    int status = -1 ;

    if( tcgetattr(fileno(stdin), &new_term_attr) == 0 )
    {
        new_term_attr.c_lflag &= ~(ECHO|ICANON);
        new_term_attr.c_cc[VTIME] = 0;
        new_term_attr.c_cc[VMIN] = 0;
        status = tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);
    }

    return status ;
}

int main() 
{
    errno = 0;

    char c = 0 ;
    bool is_file = enable_keyboard() != 0 ;
    bool terminate = false ;
    
    while( !terminate ) 
    {
        terminate = read(0, &c, 1) == 0 && is_file ;
        printf("%d ", c);
    }

    return 0 ;
}
  • Related