I have a file input:
$ cat input
1echo 12345
and I have the following program
1st version
#include <stdio.h>
#include <stdlib.h>
int main() {
system("/bin/bash -i");
return 0;
}
Now If I run it,
$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit
Everything works as expected.
Now I want to ignore the 1st character of the file input, so I make a call to getchar()
before invoking system()
.
2nd version:
#include <stdio.h>
#include <stdlib.h>
int main() {
getchar();
system("/bin/bash -i");
return 0;
}
Surprisingly, the bash exits instantly like there is no input.
$ gcc -o program program.c
$ ./program < input
$ exit
Question why bash is not receiving the input ?
NOTE
I tried some stuff and I figured out that forking a new child for the main process solves the problem:
3rd Version
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
getchar();
if (fork() > 0) {
system("/bin/bash -i");
wait(NULL);
}
return 0;
}
$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit
OS Ubuntu 16.04 64bit, gcc 5.4
Best Answer
A file stream is defined to be:
Since you're redirecting into standard input, stdin is non-interactive and so it's buffered.
getchar
is a stream function, and will cause the buffer to be filled from the stream, consuming those bytes, and then return a single byte to you.system
just runs fork-exec, so the subprocess inherits all your open file descriptors as-is. Whenbash
tries to read from its standard input, it will find that it's already at the end of file because all the content has been read by your parent process already.In your case, you want to consume only that single byte before handing over to the child process, so:
Thus adding a suitable call before the
getchar()
:will do what you want, by setting
stdin
to be unbuffered (_IONBF
).getchar
will cause only a single byte to be read, and the rest of the input will be available to the subprocess. It might be better to useread
instead, avoiding the whole streams interface, in this case.POSIX mandates certain behaviours when the handle can be accessed from both processes after a fork, but explicitly notes that
which means that
system()
doesn't (have to) do anything special with it, since it's just fork-exec.This is probably what your
fork
workaround is hitting. If the handle is accessed on both sides, then for the first one:Calling
fflush()
on a read stream will mean that:so the descriptor position should be reset back to 1 byte, the same as the stream's, and the subsequent subprocess will get its standard input starting from that point.
Additionally, for the second (child's) handle:
and I suppose "an appropriate location" might be the same (though it isn't further specified). The
getchar()
call "explicitly changed the file offset", so this case should apply. The intention of the passage is that working in either branch of the fork should have the same effect, so bothfork() > 0
andfork() == 0
should work the same. Since nothing actually happens in this branch, though, it's arguable that none of these rules should be used at all for either parent or child.The exact result is probably platform-dependent - at least, exactly what counts as "can ever be accessed" isn't directly specified, nor which handle is first and second. There is also an earlier, overriding case for the parent process:
which arguably applies for your program since it just terminates afterwards. If it does then all the remaining cases, including the
fflush()
, should be skipped, and the behaviour you're seeing would be a deviation from the specification. It's arguable that callingfork()
constitutes performing an action on the handle, but not explicit or obvious, so I wouldn't trust that. There's also enough "either" and "or" in the requirements that a lot of variation seems allowable.For multiple reasons I think the behaviour you're seeing may be a bug, or at least a generous interpretation of the specification. My overall reading is that, since in every case one branch of the
fork
doesn't do anything, none of these rules should have been applied and the descriptor position should have been ignored where it was. I can't be definitive about that, but it seems like the most straightforward reading.I wouldn't rely on the
fork
technique working. Your third version doesn't work for me here. Usesetbuf
/setvbuf
instead. If possible, I'd even usepopen
or similar to set up the process with the necessary filtering explicitly, rather than relying on the vagaries of stream and file descriptor interactions.