3

I reversed statements in if/else, corrected now.


I am reading a code snippet from Advanced Programming in the UNIX® Environment:

The program tests its standard input to see whether it is capable of seeking.

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void){

    if(lseek(STDIN_FILENO,0, SEEK_CUR) == -1)
        printf("cannot seek\n");
    else{
        printf("seek ok\n");
    }
}

I compile and run it (under Ubuntu 18.04.2 LTS) but don't understand the following behaviors.

//1
$ ./a.out 
cannot seek

//2
$ ./a.out < /etc/passwd
seek OK

//3
$ cat < /etc/passwd | ./a.out
cannot seek

//4
$ ./a.out < /var/spool/cron/FIFO
cannot seek

Why //1 is cannot seek? Empty stdin should be able to seek I think. Is it because stdin has not been opened yet? Because I heard that normally stdin, stdout and stderr are opened when a program starts to run.

Why //2 is OK and //3 is not? I think they are the same.

3
  • I just found out that Unix C API calls is ontopic here :). Commented Feb 23, 2019 at 13:22
  • I can't reproduce your seek OK with the given program on OpenBSD (cat file | ./a.out returns seek OK though). There's something wonky about the code. The terminal device (which is attached to the a.out process when no redirection is done) is not seekable. Commented Feb 23, 2019 at 13:31
  • //1 doesn't have an "empty stdin", but a stdin connected to the tty. If you want an "empty" stdin, test it with ./a.out <&-. Notice that the if/else are reversed in that code snippet ;-) (and that that's not a reliable way to test whether a file is seekable). Commented Feb 23, 2019 at 13:51

1 Answer 1

4

//1 ./a.out:

If you do no redirection of stdin (no pipe and no <), stdin is inherited from the parent process. As you run a.out interactively in a shell, it inherits the terminal device that gets your keyboard input as stdin.

Terminal devices aren't usually seekable because they represent user interaction, but according to the POSIX standard lseek may return success and simply do nothing. On Linux lseek fails with an ESPIPE.

//2 ./a.out < /etc/passwd:

Here stdin is redirected to an open file. As /etc/passwd should be a regular file, it is seekable.

//3 cat < /etc/passwd | ./a.out:

Here you start two processes (cat and ./a.out) and connect them with a pipe.

cat (without other arguments) reads it stdin (/etc/passwd) and copies it to its stdout (the pipe connecting to ./a.out). This is not the same case as //2. From the perspective of ./a.out the stdin cannot seek because it is only a pipe connecting to another process.

//4 ./a.out < /var/spool/cron/FIFO:

Here you have a named pipe or similar special file. This case is similar to //3. You have an unidirectional connection to another process. And these are not seekable.

13
  • Hmm... Is any of this Linux-specific? I get cannot seek on the first two and seek OK on the last two on OpenBSD. Maybe I should ask a separate question about that, but if there's a portability issue with the code, it would be good to point this out. Commented Feb 23, 2019 at 13:50
  • AFAIK this is not Linux-specific. There were some proposals for „seekable pipes“ but these don't seem to be implemented anywhere by default. But as @Uncle Billy already mentioned, the if-condition is reversed in the code snipped in the question. Commented Feb 23, 2019 at 13:58
  • 2
    @Kusalananda that code is not portable, but in addition to that, it's printing exactly the reverse ("seek OK" when the lseek() failed and returned -1). Commented Feb 23, 2019 at 13:59
  • 1
    @UncleBilly You're partly right. unix.com/man-page/posix/3P/lseek »The behavior of lseek() on devices which are incapable of seeking is implementation-defined. The value of the file offset associated with such a device is undefined.« and »Errors: ESPIPE The fildes argument is associated with a pipe, FIFO, or socket.« So lseek on pipes needs to fail with an error, but it isn't defined for terminal devices etc. Commented Feb 23, 2019 at 14:13
  • 1
    @Rick when you do < /etc/passwd, /etc/passwd is stdin. You seem to have bought into the idea that stdin is something magic -- it's not. It's simply a file descriptor like any other else. You can replicate the redirection in your program with fd = open("/etc/passwd", O_RDONLY); dup2(fd, 0); close(fd) (error checking ommitted) -- that's basically what bash does. Commented Feb 25, 2019 at 7:49

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.