A file stream is defined to be:
fully buffered if and only if it can be determined not to refer to an interactive device
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. When bash
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:
The setvbuf()
function may be used after the stream pointed to by stream is associated with an open file but before any other operation [...] is performed on the stream.
Thus adding a suitable call before the getchar()
:
#include <stdio.h>
#include <stdlib.h>
int main() {
setvbuf(stdin, NULL, _IONBF, 0 );
getchar();
system("/bin/bash -i");
return 0;
}
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 use read
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
If the only action performed by one of the processes is one of the exec
functions [...], the handle is never accessed in that process.
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:
If the stream is open with a mode that allows reading and the underlying open file description refers to a device that is capable of seeking, the application shall either perform an fflush()
, or the stream shall be closed.
Calling fflush()
on a read stream will mean that:
the file offset of the underlying open file description shall be set to the file position of the stream
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:
If any previous active handle has been used by a function that explicitly changed the file offset, except as required above for the first handle, the application shall perform an lseek()
or fseek()
(as appropriate to the type of handle) to an appropriate location.
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 both fork() > 0
and fork() == 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:
If the only further action to be performed on any handle to this open file descriptor is to close it, no action need be taken.
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 calling fork()
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. Use setbuf
/setvbuf
instead. If possible, I'd even use popen
or similar to set up the process with the necessary filtering explicitly, rather than relying on the vagaries of stream and file descriptor interactions.
-c
without a command?