Bash reopen tty on simple program - c

#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?

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");
}

Related

C program is not reading redirected shell command standard input

I have compiled the below c code which should send any input it receives in standard input to standard output. It works as expected in the following scenarios:
[user#host ~]$ ./my_program
test...
test...
^C
[user#host ~]$ echo "Hello" | ./my_program
Hello
[user#host ~]$ ./my_program < test.txt
Contents of test.txt ...
However, if I redirect the output of a shell command into my program like so:
[user#host ~]$ ./my_program <(echo "Hello")
It does not output anything and waits for input as if I started the program with just ./my_program
I expected an output of Hello and then the program to end. When I run the command cat <(echo "Hello") I get this expected result. What is causing the difference in behaviour between cat and my_program?
/* my_program.c */
#include <stdio.h>
int main()
{
int c = getchar();
while (c != EOF) {
putchar(c);
c = getchar();
}
return 0;
}
Posting Community Wiki because the question is caused by a typo and thus off-topic.
You're passing a filename associated with a pipeline with the output of echo "Hello" on your program's command line, not attaching it to stdin.
To attach it to stdin you need an extra < on the command line:
./my_program < <(echo "Hello")
It works with cat the other way because when cat is passed command-line arguments, it treats them as files to read from.

Program linux command with pipe in C

I´m trying to execute the next linux command
cat file_a file_b file_c | wc –l > result.txt
in a C program, but I´m not able to do it properly. I have very low level of C programming, and I would like to see how to make that command works in a C program.
This is the code I developed without success:
void main() {
execlp("/bin/sh", "/bin/sh", "-c", "cat file1 file2 fileN | wc –l > lines.txt", 0);
}
I follow your example.
Its results:
implicit declaration of function 'execlp' is invalid in C99
And some other warning errors.
But I think it would be better if you use the system() C-function, here is what I've done:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
system("cat filea fileb filec | wc -l > result.txt");
return EXIT_SUCCESS;
}
After compilation that works!
If you want to run just a command without needing to read the resulting output, you could use the C-function system(), but if you want to run it getting its results, you should use popen().
system()
popen()

Why does using pipes with `who` cause mom not to like me?

In a program I'm writing, I fork() and execl() do determine who mom likes. I noticed that if I set up pipes to write to who's stdin, it produces no output. If I don't set up pipes to write to stdin, then who produces output as normal. (yes, I know, writing to who's stdin is pointless; it was residual code from executing other processes that made me discover this).
Investigating this, I wrote this simple program (edit: for a simpler example, just run: true | who mom likes):
$ cat t.c:
#include <unistd.h>
#include <assert.h>
int main()
{
int stdin_pipe[2];
assert( pipe(stdin_pipe) == 0);
assert( dup2(stdin_pipe[0], STDIN_FILENO) != -1);
assert( close(stdin_pipe[0]) == 0);
assert( close(stdin_pipe[1]) == 0);
execl("/usr/bin/who", "/usr/bin/who", "mom", "likes", (char*)NULL);
return 0;
}
Compiling and running results in no output, which is what surprised me initially:
$ cc t.c
$ ./a.out
$
However, if I compile with -DNDEBUG (to remove the piping work in the assert()s) and run, it works:
$ cc -DNDEBUG t.c
$ ./a.out
batman pts/0 2014-08-15 12:57 (:0)
$
As soon as I call dup2(stdin_pipe[0], STDIN_FILENO), who stops producing output. The only explanation I could come up with is that dup2 affects the tty, and who uses the tty do determine who I am (given the -m flag prints "only hostname and user associated with stdin"). My main question is:
Why can't who mom likes/who am i/who -m determine who I am when I give it a pipe for stdin? What mechanism is it using to determine its information, and why does using a pipe ruin this mechanism? I know it's using stdin somehow, but I don't understand exactly how or exactly why stdin being a pipe matters.
Let's look at the source code for GNU coreutils who:
if (my_line_only)
{
ttyname_b = ttyname (STDIN_FILENO);
if (!ttyname_b)
return;
if (STRNCMP_LIT (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH) == 0)
ttyname_b += DEV_DIR_LEN; /* Discard /dev/ prefix. */
}
When -m (my_line_only) is used, who finds the tty device connected to stdin, and then proceeds to finds the entry for that tty in utmp.
When stdin is not a terminal, there is no name to look up in utmp, so it exits without printing anything.

C: stdout hangs linux pipes

I've written a simple I/O echoing program in C to test a problem with a bigger real program. Here, linux FD redirection doesn't work.
The echoing program (aka a.out) is:
#include <stdio.h>
int main(int argc, char **argv) {
char buff[10];
while (1) {
if (fgets(buff, 10, stdin) == NULL) break;
printf("PRINT: %s \n", buff);
}
}
From Bash, I run it as:
$ mkfifo IN OUT
$ # this is a method to keep the pipes IN and OUT opened over time
$ while :; do read; echo Read: $REPLY >&2; sleep 1; done <OUT >IN &
$ a.out >OUT <IN &
$ echo xyz >IN
and there is no output produced: the Bash while loop isn't able to read from OUT.
Let's compare this a.out with cat, which instead works as expected:
$ mkfifo IN OUT
$ while :; do read; echo Read: $REPLY >&2; sleep 1; done <OUT >IN &
$ cat >OUT <IN &
$ echo xyz >IN
Read: xyz
This last line is printed on console to stderr.
cat's output, differently from a.out's, is able to travel across OUT and reach the Bash while loop, which then prints it on console.
What's wrong with a.out?
try to add fflush(stdout) after printf(...).

execve("/bin/sh", 0, 0); in a pipe

I have the following example program:
#include <stdio.h>
int
main(int argc, char ** argv){
char buf[100];
printf("Please enter your name: ");
fflush(stdout);
gets(buf);
printf("Hello \"%s\"\n", buf);
execve("/bin/sh", 0, 0);
}
I and when I run without any pipe it works as it should and returns a sh promt:
bash$ ./a.out
Please enter your name: warning: this program uses gets() which is unsafe.
testName
Hello "testName"
$ exit
bash$
But this does not work in a pipe, i think I know why that is, but I cannot figure out a solution. Example run bellow.
bash$ echo -e "testName\npwd" | ./a.out
Please enter your name: warning: this program uses gets() which is unsafe.
Hello "testName"
bash$
I figure this has something to do with the fact that gets empties stdin in such a way that /bin/sh receives a EOF and promtly quits without an error message.
But how do I get around this (without modifying the program, if possible, and not removing gets, if not) so that I get a promt even though I supply input through a pipe?
P.S. I am running this on a FreeBSD (4.8) machine D.S.
You can run your program without any modifications like this:
(echo -e 'testName\n'; cat ) | ./a.out
This way you ensure that your program's standard input doesn't end after what echo outputs. Instead, cat continues to supply input to your program. The source of that subsequent input is your terminal since this is where cat reads from.
Here's an example session:
bash-3.2$ cc stdin_shell.c
bash-3.2$ (echo -e 'testName\n'; cat ) | ./a.out
Please enter your name: warning: this program uses gets(), which is unsafe.
Hello "testName"
pwd
/home/user/stackoverflow/stdin_shell_question
ls -l
total 32
-rwxr-xr-x 1 user group 9024 Dec 14 18:53 a.out
-rw-r--r-- 1 user group 216 Dec 14 18:52 stdin_shell.c
ps -p $$
PID TTY TIME CMD
93759 ttys000 0:00.01 (sh)
exit
bash-3.2$
Note that because shell's standard input is not connected to a terminal, sh thinks it is not executed interactively and hence does not display the prompt. You can type your commands normally, though.
Using execve("/bin/sh", 0, 0); is cruel and unusual punishment for the shell. It gives it no arguments or environment at all - not even its own program name, nor even such mandatory environment variables as PATH or HOME.
Not 100% sure of this (the precise shell being used and the OS might throw these answers a bit; I believe that FreeBSD uses GNU bash by default as /bin/sh?), but
sh may be detecting that its input is not a tty.
or
Your version of sh might go into non-interactive mode like that also if called as sh, expecting login will prepend a - onto argv[0] for it. Setting up execve ("/bin/sh", { "-sh", NULL}, NULL) might convince it that it's being run as a login shell.

Resources