Bash reopen tty on simple program

196 views Asked by At
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char buf[512];
    fgets(buf, 512, stdin);
    system("/bin/sh");
}

Compile with cc main.c

I would like a one-line command that makes this program run ls without it waiting for user input.

# This does not work - it prints nothing
(echo ; echo ls) | ./a.out

# This does work if you type ls manually
(echo ; cat) | ./a.out

I'm wondering:

  • Why doesn't the first example work?
  • What command would make the program run ls, without changing the source?

My question is shell and OS-agnostic but I would like it to work at least on bash 4.

Edit:

While testing out the answers, I found out that this works.

(python -c "print ''" ; echo ls) | ./a.out

Using strace:

$ (python -c "print ''" ; echo ls) | strace ./a.out
...
read(0, "\n", 4096)
...

This also works:

(echo ; sleep 0.1; echo ls) | ./a.out

It seems like the buffering is ignored. Is this due to the race condition?

1

There are 1 answers

4
that other guy On BEST ANSWER

strace shows what's going on:

$ ( echo; echo ls; ) | strace ./foo
[...]
read(0, "\nls\n", 4096)                 = 4
[...]
clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffdefc88b9c) = 9680

In other words, your program reads a whole 4096 byte buffer that includes both lines before it runs your shell. It's fine interactively, because by the time the read happens, there's only one line in the pipe buffer, so over-reading is not possible.

You instead need to stop reading after the first \n, and the only way to do that is to read byte by byte until you hit it. I don't know libc well enough to know if this kind of functionality is supported, but here it is with read directly:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    char buf[1];
    while((read(0, buf, 1)) == 1 && buf[0] != '\n');
    system("/bin/sh");
}