Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perform action if input is redirected

Tags:

c

linux

I want to know how I should go about performing an action in a C program if my input was redirected. For example, say I have my compiled program "prog" and I redirect an input "input.txt" to it (I do ./prog < input.txt).

How do I detect this in code?

like image 689
Guilherme Costa Avatar asked Nov 05 '14 00:11

Guilherme Costa


1 Answers

You can't, in general, tell if input has been redirected; but you can distinguish based on what type of file stdin is. If there has been no redirection, it will be a terminal; or it could have been set up as a pipe cat foo | ./prog, or a redirection from a regular file (like your example), or a redirection from one of a number of types of special files (./prog </dev/sda1 redirecting it from a block special file, etc).

So, if you want to determine if stdin is a terminal (TTY), you can use isatty:

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

int main(int argc, char **argv) {
    if (isatty(STDIN_FILENO))
        printf("stdin is a tty\n");
    else
        printf("stdin is not a tty\n");

    return 0;
}

If you want to distinguish between other cases (like a pipe, block special file, etc), you can use fstat to extract some more file type information. Note that this doesn't actually tell you whether it's a terminal, you still need isatty for that (which is a wrapper around an ioctl that fetches information about a terminal, at least on Linux). You can add the following to the above program (along with #include <sys/stat.h>) to get extra information about what kind of file stdin is.

if (fstat(STDIN_FILENO, &sb) == 0) {
    if (S_ISBLK(sb.st_mode))
        printf("stdin is a block special file\n");
    else if (S_ISCHR(sb.st_mode))
        printf("stdin is a character special file\n");
    else if (S_ISDIR(sb.st_mode))
        printf("stdin is a directory\n");
    else if (S_ISFIFO(sb.st_mode))
        printf("stdin is a FIFO (pipe)\n");
    else if (S_ISREG(sb.st_mode))
        printf("stdin is a regular file\n");
    else if (S_ISLNK(sb.st_mode))
        printf("stdin is a symlink\n");
    else if (S_ISSOCK(sb.st_mode))
        printf("stdin is a socket\n");
} else {
    printf("failed to stat stdin\n");
}

Note that you will never see a symlink from a redirection, as the shell will have already dereferenced the symlink before opening the file on behalf of your program; and I couldn't get Bash to open a Unix domain socket either. But all of the rest are possible, including, surprisingly, a directory.

$ ./isatty 
stdin is a tty
stdin is a character special file
$ ./isatty < ./isatty
stdin is not a tty
stdin is a regular file
$ sudo sh -c './isatty < /dev/sda'
stdin is not a tty
stdin is a block special file
$ sudo sh -c './isatty < /dev/console'
stdin is a tty
stdin is a character special file
$ cat isatty | ./isatty 
stdin is not a tty
stdin is a FIFO (pipe)
$ mkfifo fifo
$ echo > fifo &  # Need to do this or else opening the fifo for read will block
[1] 27931
$ ./isatty < fifo
stdin is not a tty
stdin is a FIFO (pipe)
[1]+  Done                    echo > fifo
$ ./isatty < .
stdin is not a tty
stdin is a directory
$ socat /dev/null UNIX-LISTEN:./unix-socket &
[1] 28044
$ ./isatty < ./unix-socket 
bash: ./unix-socket: No such device or address
$ kill $!
[1]+  Exit 143                socat /dev/null UNIX-LISTEN:./unix-socket
$ ln -s isatty symlink
$ ./isatty < symlink
stdin is not a tty
stdin is a regular file
$ ln -s no-such-file broken-link
$ ./isatty < broken-link 
bash: broken-link: No such file or directory
like image 66
Brian Campbell Avatar answered Oct 10 '22 10:10

Brian Campbell