I am learning about how fork, pipe and execve works. I have a main from where i fork into a child and parent process. The parent waits until the child has finished writing the output of ls -la into fd[1] (which is redirected to stdout). The problem is it only works if i have a printf() inside of my main right before the parent is called. Otherwise I cannot get an output. You can test it for yourself by uncommenting the line in main. Any idea what is causing this?
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void child_call(int ac, char **ag, char **ep, int fd[2])
{
char *buf;
char *argv[3] = {"ls", "-la", NULL};
dup2(fd[1], 1);
close(fd[1]);
close(fd[0]);
execve("/usr/bin/ls", argv, ep);
}
void parent_call(int ac, char **ag, char **ep, int fd[2])
{
char *buf;
close(fd[1]);
while (read(fd[0], buf, 1) > 0)
write(1, buf, 1);
close(fd[0]);
}
int main(int ac, char **ag, char **ep)
{
pid_t pid;
int fd[2];
int *status;
pipe(fd);
pid = fork();
if (pid == 0)
child_call(ac, ag, ep, fd);
else
{
// printf("uncomment me and it will work\n");
waitpid(-1, NULL, WNOHANG);
parent_call(ac, ag ,ep, fd);
}
close(fd[0]);
close(fd[1]);
}
Related
I am currently working on a university project to basically built my own simple shell. Everything is working great so far. The only thing giving me trouble is pipes. To make it easier for myself to figure out why they are not working as intended I wrote this little testing program where I try to replicate the bash behaviour of cat | ls. But i now sadly get this error cat: stdin: Input/output error and i really can't figure it out.
Here is my program:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
extern char **environ;
char *argv1[] = {"cat",NULL};
char *argv2[] = {"ls",NULL};
int fd[2];
pid_t pid;
int ret;
pipe(fd);
pid = fork();
if (pid == 0)
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execve("/bin/cat", argv1, environ);
exit (0);
}
else if (pid > 0)
{
close(fd[1]);
dup2(fd[1], STDIN_FILENO);
execve("/bin/ls", argv2, environ);
waitpid(pid, &ret, 0);
}
return (0);
}
You want a pipe like:
ls | cat
But, you're setting this up like:
cat | ls
And, in your current code, for the ls side, you're doing:
close(fd[1]);
dup2(fd[1], STDIN_FILENO);
This is wrong for two reasons:
You're closing the wrong side of the pipe, so the dup2 gets a closed fd as its first argument
You're attaching the output side of the pipe to the command's input side
So, we need to reverse the pipe order and fix the closing.
Also, after doing dup2(X,...) we want to do close(X).
Also, note that doing waitpid _after execve will have no effect unless the execve fails.
Here is the refactored and working code:
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int
main(void)
{
extern char **environ;
char *argv_cat[] = { "cat", NULL };
char *argv_ls[] = { "ls", NULL };
int fd[2];
pid_t pid;
int ret;
pipe(fd);
pid = fork();
// we want:
// ls | cat
if (pid == 0) {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execve("/bin/cat", argv_cat, environ);
exit(0);
}
else if (pid > 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execve("/bin/ls", argv_ls, environ);
waitpid(pid, &ret, 0);
}
return (0);
}
UPDATE:
waitpid after execve is pointless. –
William Pursell
Not quite. It reaps the [stuck] child process so that it doesn't become a child of the init/systemd process.
In the above example, I had forgotten to add a close(STDOUT_FILENO) before the waitpid to "release" the cat process.
Here is the adjusted code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int opt_f;
int
main(int argc,char **argv)
{
extern char **environ;
char *argv_cat[] = { "cat", NULL };
char *argv_ls[] = { "ls", NULL };
int fd[2];
pid_t pid;
int status;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'f':
opt_f = ! opt_f;
break;
}
}
pipe(fd);
pid = fork();
// we want:
// ls | cat
if (pid == 0) {
if (opt_f)
fprintf(stderr,"cld: getpid=%d\n",getpid());
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execve("/bin/cat", argv_cat, environ);
exit(0);
}
else if (pid > 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
const char *ls = opt_f ? "/bin/gooch" : "/bin/ls";
execve(ls, argv_ls, environ);
fprintf(stderr,"execve failure of '%s' -- %s\n",ls,strerror(errno));
// release other process (cat)
close(STDOUT_FILENO);
// reap the child
pid_t ret = waitpid(pid, &status, 0);
fprintf(stderr,"ret=%d pid=%d status=%8.8X\n",ret,pid,status);
}
return (0);
}
I want to reproduce this shell behaviour with a small c program:
< file cmd1 | cmd2
So it takes three arguments and can be executed this way for exemple:
./a.out foo /bin/cat /usr/bin/wc
Here's my code, without error checking:
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
static void exe_cmd(char *cmd, char **env)
{
int pid;
char *av[] = {cmd, NULL};
pid = fork();
if (pid == 0)
{
execve(av[0], av, env);
exit(0);
}
waitpid(pid, NULL, 0);
}
static void left_pipe_side(int fd[2], char *cmd, char *path, char **env)
{
int file;
file = open(path, O_RDONLY);
dup2(fd[1], STDOUT_FILENO); // Can be commented out to see the first command output
dup2(file, STDIN_FILENO);
close(fd[0]);
close(fd[1]);
close(file);
exe_cmd(cmd, env);
}
static void right_pipe_side(int fd[2], char *cmd, char **env)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
exe_cmd(cmd, env);
}
int main(int ac, char **av, char **env)
{
int fd[2];
pid_t pid;
pid = fork();
pipe(fd);
if (pid == 0)
{
left_pipe_side(fd, av[2], av[1], env);
exit(0);
}
right_pipe_side(fd, av[3], env);
waitpid(pid, NULL, 0);
return (0);
}
My problem is that the output of wc as the second command will always be 0 0 0, as if the input were empty (but the given file contains text).
If I comment out the line where I dup fd[1] to stdout in the left pipe side, the first command will output correctly, so I don't understand why the redirection makes this data "disappear"
You open pipe after fork, so each process has it's own very unrelated pipe. Open it before, so that it's shared between processes.
pipe(fd);
pid = fork();
in your code, you open the pipe after the fork so that's mean every process has its own pipe and that's not how pipes work
you have to open the pipes before the fork that's make the process linked with your pipe.
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
static void exe_cmd(char *cmd, char **env)
{
int pid;
char *av[] = {cmd, NULL};
pid = fork();
if (pid == 0)
{
execve(av[0], av, env);
exit(0);
}
waitpid(pid, NULL, 0);
}
static void left_pipe_side(int fd[2], char *cmd, char *path, char **env)
{
int file;
file = open(path, O_RDONLY);
dup2(fd[1], STDOUT_FILENO); // Can be commented out to see the first command output
dup2(file, STDIN_FILENO);
close(fd[0]);
close(fd[1]);
close(file);
exe_cmd(cmd, env);
}
static void right_pipe_side(int fd[2], char *cmd, char **env)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
exe_cmd(cmd, env);
}
int main(int ac, char **av, char **env)
{
int fd[2];
pid_t pid;
/************************ the error part **********************/
// pid = fork();
// pipe(fd);
pipe(fd);
pid = fork();
/**************************************************************/
if (pid == 0)
{
left_pipe_side(fd, av[2], av[1], env);
exit(0);
}
right_pipe_side(fd, av[3], env);
waitpid(pid, NULL, 0);
return (0);
}
I'm writing my minishell and I can't understand why execve doesn't work when calling pid_2 ?
My main task is to implement env | grep LANG
int main(void)
{
pid_t pid_1, pid_2;
int fd[2];
int status;
char *mass_1[] = {"env", NULL};
char *mass_2[] = {"grep", "LANG", NULL};
pid_1 = fork();
pipe(fd);
if (pid_1 == 0)
{
dup2(fd[1], 0);
close(fd[0]);
execve(mass_1[0], mass_1, NULL);
exit(1);
}
pid_2 = fork();
if (pid_2 == 0)
{
dup2(fd[0], 0);
close(fd[1]);
execve(mass_2[0], mass_2, NULL);
exit(1);
}
close(fd[0]);
close(fd[1]);
waitpid(pid_1, &status, WUNTRACED);
waitpid(pid_2, &status, WUNTRACED);
return (0);
}
There are three issues in your code:
execve() doesn't search for the command in $PATH so generally it won't find neither env nor grep. Use execvp() instead.
You should call pipe() before the first fork() to prevent another pipe to be created and used by the child process
as #KamilCuk has pointed out: in the first child process, you should dup2() stdout instead of stdin
Fixing these bugs lead to this working code
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
pid_t pid_1, pid_2;
int fd[2];
int status;
char *mass_1[] = {"env", NULL};
char *mass_2[] = {"grep", "LANG", NULL};
pipe(fd);
pid_1 = fork();
if (pid_1 == 0)
{
dup2(fd[1], 1);
close(fd[0]);
execvp(mass_1[0], mass_1);
exit(1);
}
pid_2 = fork();
if (pid_2 == 0)
{
dup2(fd[0], 0);
close(fd[1]);
execvp(mass_2[0], mass_2);
exit(1);
}
close(fd[0]);
close(fd[1]);
waitpid(pid_1, &status, WUNTRACED);
waitpid(pid_2, &status, WUNTRACED);
return (0);
}
Btw. you should add some error handling e.g. pipe() or fork() could fail
If you must not call execvp() you have to provide pathnames to execve() like "/usr/bin/env" or "/bin/grep". In this case you also have to provide the parent's environment at least to env otherwise it won't print anything.
The easiest way to do so is:
change the signature of main() to int main(int argc, char *argv[], char *envp[])
call execve(mass_1[0], mass_1, envp);
I need to execute a file from the bash using and store its output to a variable, there's also the needs to pass to its stdin a string s. Something like this in bash:
usr:~$ s | program args
I know how to call the program and give him args:
execvp(program,args);
So my problem is giving to that his stdin and store output to a variable(string)!
P.S.:can't use system and popen.
Some example code for you to experiement. This excute ls | cat.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv) {
int fd[2];
int pid;
char* cmd1[2] = {"ls", NULL};
char* cmd2[2] = {"cat", NULL};
int status;
pid = fork();
if (pid == 0) {
pipe(fd);
pid = fork();
if (pid == 0) {
printf("cmd1\n");
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
execvp(cmd1[0], cmd1);
printf("Error in execvp\n");
}
else {
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
printf("cmd2\n");
execvp(cmd2[0], cmd2);
printf("Error in execvp\n");
}
}
else {
close(fd[0]);
close(fd[1]);
wait(&status);
printf("%d\n", status);
wait(&status);
printf("%d\n", status);
}
}
I wrote the following code in order to pipe two commands:
#include <stdlib.h>
#include <unistd.h>
char *program_1[3] = {"/bin/cat", "/dev/random", NULL};
char *program_2[2] = {"/bin/ls", NULL};
char *program_3[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
int pid;
pipe(fd);
if ((pid = fork()) == 0) //Child process
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(program_3[0], program_3, NULL);
}
else if (pid > 0) //Parent process
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(program_2[0], program_2, NULL);
}
return (EXIT_SUCCESS);
}
Each pair of program_x / program_y where x != y works fine, except this one.
When i pipe sort into ls, ls well prints its output on stdout, but then, sort throw this error: sort: Input/output error.
When I type sort | ls into bash, it prints ls result as my program, but then waits for input.
Am I doing someting wrong ?
edit: I'm trying to reimplement the shell's behaviour
The problem is that when ls finishes, the parent process will exit which will close the read-end of the pipe, which will lead to an error being propagated to the write-end of the pipe which is detected by sort and it write the error message.
That it doesn't happen in the shell is because shells handle pipes differently than your simple example program, and it keeps the right-hand side of the pipe open and running (possibly in the background) until you pass EOF (Ctrl-D) to the sort program.
Your program isn't quite equivalent to what a shell typically does.
You're replacing the parent with ls; whereas shell would create who child processes and connect them and wait for them to finish.
It's more like:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
char *program_2[2] = {"/bin/ls", NULL};
char *program_3[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
pid_t pid;
pid_t pid2;
pipe(fd);
if ((pid = fork()) == 0) //Child process
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(program_3[0], program_3, NULL);
}
else if (pid > 0) //Parent process
{
if ( (pid2 = fork()) == 0) {
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(program_2[0], program_2, NULL);
}
}
waitpid(pid, 0, 0);
waitpid(pid2, 0, 0);
return (EXIT_SUCCESS);
}
I finally found the solution, we were close to:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
char *cat[3] = {"/bin/cat", "/dev/random", NULL};
char *ls[2] = {"/bin/ls", NULL};
char *sort[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
pid_t pid;
pid_t pid2;
pipe(fd);
if ((pid = fork()) == 0)
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(cat[0], cat, NULL);
}
else if (pid > 0)
{
if ( (pid2 = fork()) == 0)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(ls[0], ls, NULL);
}
waitpid(pid2, 0, 0);
close(fd[0]);
}
waitpid(pid, 0, 0);
return (EXIT_SUCCESS);
}
We need to close the read end of the pipe once the last process ends, this way, if the first process tries to write on the pipe, an error will be throwed and the process will exit, else if it only reads from stdin as sort, it will keep reading as stdin is still open