I'm having trouble understanding how to do basic piping in C. I looked at a couple other questions on this topic, and either they were for subtly different issues, or I'm so far off the mark on this subject I couldn't understand why the answers are good for my problem.
This program below is just a simple test I made, where I'm trying to get behaviour equivalent to typing "ls | grep a" into my shell. (I have a homework assignment where I have to build a shell that can handle piping, but this is my first step towards understanding pipes to even attempt the homework). I get the correct output, but the terminal prompt ends up appearing before the output, making it look like it did not properly terminate. Since this is connected to a shell homework, I'm worried that will impact the grade (and it just feels wrong to let it look like that anyway). Any advice?
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main()
{
int fdpipe[2];
pipe(fdpipe);
int f1 = fork();
if(f1 == 0)
{
close(fdpipe[1]);
dup2(fdpipe[0],0);
close(fdpipe[0]);
execlp("/bin/grep","grep","a",NULL);
}
else
{
close(fdpipe[0]);
dup2(fdpipe[1],1);
close(fdpipe[1]);
execlp("/bin/ls","ls",NULL);
wait(NULL);
}
return 0;
}
Here's an example of my terminal output.
1067: ls
a.out test.c test.cc
NathanE: ~/Desktop/playground
1068: ./a.out
NathanE: ~/Desktop/playground
1069: a.out
(The beginning of this line is where my cursor is)
What I'm expecting would be:
1067: ls
a.out test.c test.cc
NathanE: ~/Desktop/playground
1068: ./a.out
a.out
NathanE: ~/Desktop/playground
1069: (my cursor would go here)
The child process runs grep, while the parent replaces itself with ls. The wait(NULL) does nothing, because successful exec*() never return.
Because the control returns to the shell immediately after ls completes, the shell can display the next prompt before grep completes.
There are two approaches you can use to avoid this:
fork() both child processes, and wait() for them
Replace the process itself with the last process in the pipe chain
Either will ensure that control is returned to the shell only after the last process in the pipe chain completes.
Related
This question already has answers here:
Why is main called twice?
(3 answers)
Closed 1 year ago.
I encountered this in my school work and it didn't produce what I thought it should:
int main() {
printf("c");
fork();
printf("d");
}
I know there are several things that aren't good about this code (i.e. no parameters in main, no variable for the return value from fork, no return statement at the end, etc.), but this is how it was presented and it's not relevant to my question anyway.
This code produces the output:
cdcd
It was my understanding that when fork is called, both parent and child would resume/begin on the line after the fork call. Based on that, I would have expected the output to be:
cdd
Assuming, of course, that the fork call is successful. Can anyone explain to me why that "c" is printed a second time even though it's on the line before the fork call?
Thanks!
M_MN
You forked your program before flushing stdout (i.e.: data was still in the output buffer). Just call fflush(stdout) to fix it:
❯ cat test.c
#include <stdio.h>
#include <unistd.h>
int main() {
printf("c");
fflush(stdout);
fork();
printf("d");
}
[22:14:01]~/devel
❯ clang test.c -o test
[22:14:07]~/devel
❯ ./test
cdd[22:14:09]~/devel
The reason you're seeing c twice is that the fork() duplicates the unprinted buffered output. You could flush the output stream before the fork():
fflush(stdout);
Or you could set stdout to be unbuffered, but you should do this first, before calling printf() the first time:
setvbuf(stdout, NULL, _IONBF, 0);
Here's what I'm guessing is happening. printf writes to the stream stdout. Since you didn't flush stdout after printing "c" nor did that string end in a new line, the character sat there in a user-space buffer. When you called fork, the child process got a copy of the parent's virtual address space including the buffered text. When both programs exited, their buffers were flushed and so "c" showed up twice.
Try adding fflush(stdout); just prior to the call to fork.
I am currently writing my own shell implementation in C. I understood the principle behind piping and redirecting the fds. However, some specific behavior with pipes has attracted my attention:
cat | ls (or any command that does not read from stdin as final element of the pipe).
In that case, what happens in the shell is that ls executes and cat asks for a single line before exiting (resulting from a SIGPIPE I guess). I have tried to follow this tutorial to better understand the principle behind multiple pipes: http://web.cse.ohio-state.edu/~mamrak.1/CIS762/pipes_lab_notes.html
Below is some code I have written to try to replicate the behavior I am looking for:
char *cmd1[] = {"/bin/cat", NULL};
char *cmd2[] = {"/bin/ls", NULL};
int pdes[2];
pid_t child;
if (!(child = fork()))
{
pipe(pdes);
if (!fork())
{
close(pdes[0]);
dup2(pdes[1], STDOUT_FILENO);
/* cat command gets executed here */
execvp(cmd1[0], cmd1);
}
else
{
close(pdes[1]);
dup2(pdes[0], STDIN_FILENO);
/* ls command gets executed here */
execvp(cmd2[0], cmd2);
}
}
wait(NULL);
I am aware of the security flaws of that implementation but this is just for testing. The problem with that code as I understand it is that whenever ls gets executed, it just exits and then cat runs in the background somehow (and in my case fail because it tries to read during the prompt of zsh as my program exits). I cannot find a solution to make it work like it should be. Because if I wait for the commands one by one, such commands as cat /dev/random | head -c 10 would run forever...
If anyone has a solution for this issue or at least some guidance it would be greatly appreciated.
After consideration of comments from #thatotherguy here is the solution I found as implemented in my code. Please bear in mind that pipe and fork calls should be checked for errors but this version is meant to be as simple as possible. Extra exit calls are also necessary for some of my built-in commands.
void exec_pipe(t_ast *tree, t_sh *sh)
{
int pdes[2];
int status;
pid_t child_right;
pid_t child_left;
pipe(pdes);
if (!(child_left = fork()))
{
close(pdes[READ_END]);
dup2(pdes[WRITE_END], STDOUT_FILENO);
/* Execute command to the left of the tree */
exit(execute_cmd(tree->left, sh));
}
if (!(child_right = fork()))
{
close(pdes[WRITE_END]);
dup2(pdes[READ_END], STDIN_FILENO);
/* Recursive call or execution of last command */
if (tree->right->type == PIPE_NODE)
exec_pipe(tree->right, sh);
else
exit(execute_cmd(tree->right, sh));
}
/* Should not forget to close both ends of the pipe */
close(pdes[WRITE_END]);
close(pdes[READ_END]);
wait(NULL);
waitpid(child_right, &status, 0);
exit(get_status(status));
}
I was confused with the original link I posted and the different ways to handle chained pipes. From the link to the POSIX documented posted below my original question (http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_02) it appears that:
If the pipeline is not in the background (see Asynchronous Lists), the shell shall wait for the last command specified in the pipeline to complete, and may also wait for all commands to complete.
Both behavior are therefore accepted: waiting for last command, or waiting for all of them. I chose to implement the second behavior to stick to what bash/zsh would do.
In bash, when I type ls and hit enter, the binary ls will run and I will return to shell prompt again without doing anything from my side.
However this program, written in C will block:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t other = fork();
// other will be 0 for the child process
// other will be the childs process' value in the parent process.
switch(other) {
case 0:
printf("%s %i\n", "I am the child process!", other);
execl("/bin/ls","ls",NULL);
return 0;
default:
printf("%s %i\n", "I am the parent process!", other);
return 1;
}
}
Why?
The output is as follows:
Korays-MacBook-Pro:~ koraytugay$ ./a.out
I am the parent process! 40309
I am the child process! 0
Korays-MacBook-Pro:~ koraytugay$ AndroidStudioProjects Movies happyko koray.i
Applications Music hello.c koray.o
ClionProjects Pictures hello.sh koray.s
Code Public innbound mssql
Desktop TheElementsFiles innbound-pf nono.txt
Documents VirtualBox VMs innbound_usage.log svn-key
Downloads a.out k.txt tugay.c
IdeaProjects asm.asm klinnck webtoolkit
Koray.class asm.hack klinnck-pf
Koray.java cexamples koray.a
Library fifa.sql koray.c
At this point I will need to hit Enter so that I return to bash prompt. Why?
At this point I will need to hit ENTER so that I return to bash prompt.
Actually, you're already back to the prompt, you just did not realize it.
To elaborate, the problem you facing here is, the parent does not wait for the child to exit and returns beforehand the child finishes execution. So, the shell prompt comes back, and then the output from the chlid process (output of ls) gets printed on the output.
If you notice properly, You've already got the prompt back, and your output appears later.
Korays-MacBook-Pro:~ koraytugay$ ./a.out
I am the parent process! 40309
I am the child process! 0
****Korays-MacBook-Pro:~ koraytugay$***** AndroidStudioProjects Movies happyko koray.i
Applications Music hello.c koray.o
ClionProjects Pictures hello.sh koray.s
Code Public innbound mssql
Desktop TheElementsFiles innbound-p
Above, please note the **** marked line. There, you got your shell prompt back.
At this point I will need to hit Enter so that I return to bash.
Except no, you're already in bash. But all that ls output after the prompt makes it seem that you are not. Go ahead, try another command.
I am writing a program in C in one of my systems classes. We write c code and run it in a unix environment. I have looked all over the internet, but can't seem to find any way to make the kill(int PID) command work. The code will compile and run fine, but if I use the
ps -u username
command in a unix command prompt (after execution has completed of course,) it says that the all of the processes I tried to kill in my c code are still running. I can kill them from the unix command prompt by manually entering their PIDs, but for the life of me, I cannot figure out how to do it inside of my program.
In this particular program, I am trying to kill process CC, which is a process that just infinitely calls usleep(100); until terminated.
I tried using kill(C3, -9); and variations of execlp("kill", "kill", C3, (char *)0); but still no luck. Does anyone have any idea what I am doing wrong here? My only guess is that the kill command is being passed the wrong PID parameter, but if that's the case, I have no idea how I would get the correct one.
EDIT: Also, the kill command returns a value of zero, which I believe means that it "succeeded" in executing the command.
EDIT: Just noticed that the solution to my problem was in the instructions for the assignment all along. Yup. I'm stupid.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int args, char* argv[])
{
//
//Step 7
//
//Create process C3
int C3=fork();
if (C3==0)
{
execlp("CC", "CC", (char *)0);
}
else
{
usleep(500000);
//
//Step 8
//
int ps=fork();
if (ps==0)
{
execlp("ps", "ps", "-u", "cooley", (char *)0);
}
else
{
wait(NULL);
kill(C3);
}
}
exit(0);
}
You're calling the kill system call with only one argument, when it takes two. This leads to undefined behavior since then the second argument can by anything. You should get a warning about this when compiling.
The second argument should be a value from <signal.h> (see the signal(7) manual page).
How can I execute shell from a C code?
My shell is placed in /bin/sh
Following didn't seem to work for me
system("/bin/sh");
exec("/bin/sh");
Maybe you need to tell the shell it should be interactive:
system("/bin/sh -i");
However, I believe that your original system() call should have produced a shell prompt too.
Both notations (with and without the '-i') in this program give me a shell prompt (return to the previous shell by typing 'exit' and RETURN or Control-D):
#include <stdlib.h>
int main(void)
{
system("/bin/sh -i");
return 0;
}
This program works as expected for me:
int main()
{
int ret = system("/bin/sh");
printf ("Shell returned %d\n", ret);
return 0;
}
using -i causes some sort of redirection issue and everything hangs as soon as I type a command that produces output.
There are important differences between system() and exec(). system() is effectively the same as /bin/sh -c yourCommand on the command line, so the system("/bin/sh") is the same as
/bin/sh -c /bin/sh
This is why it is rarely used, because the command you want is executed by first starting an unnecessary shell process.
exec() causes the entire process image to be replaced by the command specified so if I had written:
int main()
{
int ret = exec("/bin/sh");
printf ("Shell returned %d\n", ret);
return 0;
}
The printf() and everything after it would never have been executed because the whole process transforms into an instance of /bin/sh. The proper way to run a child command is to fork and then exec in the child and wait in the parent.