c - Make multiple pipe with waiting for return code of each process -
i want reproduce pipe system of unix shell in c using execve, dup2, fork, waitpid & pipe functions.
right, example command : /bin/ls -l | /usr/bin/head -2 | /usr/bin/wc reproduced :
#include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> int add_child_process(char **argv, int in, int out, char **envp) { int pid; pid = fork(); if (pid == 0) { if (in != 0) { dup2(in, 0); } if (out != 1) { dup2(out, 1); } if (execve(argv[0], argv, envp) == -1) perror("execve failed"); } else return (pid); } int main(int av, char **ag, char **envp) { int in; int pipes[2]; char *argv[3]; int pids[3]; /** launch first process read on original stdin (0) , write on out of pipe **/ pipe(pipes); argv[0] = "/bin/ls"; argv[1] = "-l"; argv[2] = null; pids[0] = add_child_process(argv, 0, pipes[1], envp); close(pipes[1]); in = pipes[0]; /** launch second process read on in of old pipe , write on out of new pipe **/ pipe(pipes); argv[0] = "/usr/bin/head"; argv[1] = "-2"; argv[2] = null; pids[1] = add_child_process(argv, in, pipes[1], envp); close(in); close(pipes[1]); in = pipes[0]; /** launch last process read on in of old pipe , write on original stdout (1) **/ argv[0] = "/usr/bin/wc"; argv[1] = null; pids[2] = add_child_process(argv, in, 1, envp); close(in); /** wait process end catch return codes **/ int return_code; waitpid(pids[0], &return_code, 0); printf("process 0 return : %d\n", return_code); waitpid(pids[1], &return_code, 0); printf("process 1 return : %d\n", return_code); waitpid(pids[2], &return_code, 0); printf("process 2 return : %d\n", return_code); } work, output :
2 11 60 process 0 return : 0 process 1 return : 0 process 2 return : 0 like echo "/bin/ls -l | /usr/bin/head -2 | /usr/bin/wc" | bash
but blocking command ping google.com | head -2 | wc it's show :
2 16 145 and remains blocked.
here updated main command :
int main(int av, char **ag, char **envp) { int in; int pipes[2]; char *argv[3]; int pids[3]; /** launch first process read on original in (0) , write on out of pipe **/ pipe(pipes); argv[0] = "/bin/ping"; argv[1] = "google.com"; argv[2] = null; pids[0] = add_child_process(argv, 0, pipes[1], envp); close(pipes[1]); in = pipes[0]; /** launch second process read on in of old pipe , write on out of new pipe **/ pipe(pipes); argv[0] = "/usr/bin/head"; argv[1] = "-2"; argv[2] = null; pids[1] = add_child_process(argv, in, pipes[1], envp); close(in); close(pipes[1]); in = pipes[0]; /** launch last process read on in of old pipe , write on original stdout (1) **/ argv[0] = "/usr/bin/wc"; argv[1] = null; pids[2] = add_child_process(argv, in, 1, envp); close(in); /** wait process end catch return codes **/ int return_code; waitpid(pids[0], &return_code, 0); printf("process 0 return : %d\n", return_code); waitpid(pids[1], &return_code, 0); printf("process 1 return : %d\n", return_code); waitpid(pids[2], &return_code, 0); printf("process 2 return : %d\n", return_code); } i don't understand why behaviour occur example in bash : echo "ping google.com | head -2 | wc" | bash show 2 16 145 , don't stuck.
how can deal blocking command ? need return codes of process make return according last error code.
your add_child_process() function supposed return pid; doesn't. should have error handling after execve() in case program fails execute.
first solution — not enough
with fixed (and sundry other changes past minimum level of compilation warnings — such #include <stdio.h> there's declaration printf() before used), expected output.
i used code (source file pc67.c) test:
#include <assert.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> static int add_child_process(char **argv, int in, int out, char **envp) { int pid; pid = fork(); if (pid == 0) { if (in != 0) { dup2(in, 0); } if (out != 1) { dup2(out, 1); } execve(argv[0], argv, envp); abort(); } return pid; } int main(int av, char **ag, char **envp) { int in; int pipes[2]; char *argv[3]; int pids[3]; assert(ag[av] == 0); /** launch first process read on original stdin (0) , write on out of pipe **/ pipe(pipes); argv[0] = "/bin/ls"; argv[1] = "-l"; argv[2] = null; pids[0] = add_child_process(argv, 0, pipes[1], envp); close(pipes[1]); in = pipes[0]; /** launch second process read on in of old pipe , write on out of new pipe **/ pipe(pipes); argv[0] = "/usr/bin/head"; argv[1] = "-2"; argv[2] = null; pids[1] = add_child_process(argv, in, pipes[1], envp); close(in); close(pipes[1]); in = pipes[0]; /** launch last process read on in of old pipe , write on original stdout (1) **/ argv[0] = "/usr/bin/wc"; argv[1] = null; pids[2] = add_child_process(argv, in, 1, envp); close(in); /** wait process end catch return codes **/ int return_code; waitpid(pids[0], &return_code, 0); printf("process 0 return : %d\n", return_code); waitpid(pids[1], &return_code, 0); printf("process 1 return : %d\n", return_code); waitpid(pids[2], &return_code, 0); printf("process 2 return : %d\n", return_code); } the abort() didn't fire; commands executed (i did check in directories listed before trying run code).
i compile on mac running macos sierra 10.12.4 using gcc 6.3.0 command line:
$ gcc -o3 -g -std=c11 -wall -wextra -werror -wmissing-prototypes \ > -wstrict-prototypes -wold-style-definition pc67.c -o pc67 $ with that, output:
$ ./pc67 2 11 70 process 0 return : 0 process 1 return : 0 process 2 return : 0 $ the 11 words 'total' , number of blocks, , 9 words 1 line of ls -l output. i've rerun checking output ps before , after command; there no stray processes running.
second solution — circumvention , not cure
thanks, unlike bash
echo "ping google.com | head -2 | wc" | bash, still blocked in wait , doesn't terminate program.
i'm not clear alternative command line has question. however, there number of things going on when introduce ping google.com instead of ls -l command. can't legitimately change parameters of question that. it's new question altogether.
in shell surrogate, you've not specified paths programs; code won't handle that. (if use execvp() instead of execve() — using execve() when you're relaying inherited environment pointless; environment inherited anyway — paths commands irrelevant).
however, using "/sbin/ping" , "google.com" (in copy of program pc23.c) seem hang. looking @ setup terminal, see:
$ ps -ftttys000 uid pid ppid c stime tty time cmd 0 51551 51550 0 10:13am ttys000 0:00.50 login -pf jleffler 502 51553 51551 0 10:13am ttys000 0:00.14 -bash 502 54866 51553 0 11:10am ttys000 0:00.01 pc23 502 54867 54866 0 11:10am ttys000 0:00.00 /sbin/ping google.com 502 54868 54866 0 11:10am ttys000 0:00.00 (head) 502 54869 54866 0 11:10am ttys000 0:00.00 (wc) $ both head , wc processes have terminated (those entries zombies), ping process isn't dying. doesn't seem doing much, isn't dying either.
some time later, got:
$ ps -ftttys000 uid pid ppid c stime tty time cmd 0 51551 51550 0 10:13am ttys000 0:00.50 login -pf jleffler 502 51553 51551 0 10:13am ttys000 0:00.14 -bash 502 54866 51553 0 11:10am ttys000 0:00.01 pc23 502 54867 54866 0 11:10am ttys000 0:00.09 /sbin/ping google.com 502 54868 54866 0 11:10am ttys000 0:00.00 (head) 502 54869 54866 0 11:10am ttys000 0:00.00 (wc) $ it's managed use 9 cpu seconds. so, in context, reason, ping doesn't pay attention sigpipe signal.
how fix? requires experimentation , more intricate. easiest fix add options such -c , 3 — worked me. don't have incentive go looking alternative fixes.
the macos man page ping says, in part:
-c countstop after sending (and receiving) count echo_response packets. if option not specified,
pingoperate until interrupted. if option specified in conjunctionpingsweeps, each sweep consist of count packets.
it intriguing speculate how precise term 'interrupted' is. program terminates if sent actual (control-c) interrupt, or sigterm signal (kill 54867). curious stops on sigpipe , not.
third solution — close file descriptors
on further thought, problem code not closing enough file descriptors. that's problem pipelines not terminating. ls first process, problem hidden because ls terminates automatically, closing stray file descriptors. changing ping exposes problems. here's revision of code. closes dup2() descriptors (oops; slap self on wrist). ensures other pipe descriptor closed via new tbc (to closed) file descriptor argument add_child_process(). i've opted use execv() instead of execve(), removing 1 argument add_child_process() function, , allowing int main(void) since program pays no attention arguments (which allows me lose assert(), there 'ensure' argument count , vector arguments 'used', long didn't compile -dndebug or equivalent).
i tweaked command-building code easy add/remove -c , 3 arguments ping, or add/remove arguments other commands.
#include <unistd.h> #include <stdlib.h> #include <stdio.h> static int add_child_process(char **argv, int in, int out, int tbc) { int pid; pid = fork(); if (pid == 0) { if (tbc >= 0) close(tbc); if (in != 0) { dup2(in, 0); close(in); } if (out != 1) { dup2(out, 1); close(out); } execv(argv[0], argv); abort(); } return pid; } int main(void) { int in; int pipes[2]; char *argv[10]; int pids[3]; /** launch first process read on original stdin (0) , write on out of pipe **/ pipe(pipes); int argn = 0; argv[argn++] = "/sbin/ping"; //argv[argn++] = "-c"; //argv[argn++] = "3"; argv[argn++] = "google.com"; argv[argn++] = null; pids[0] = add_child_process(argv, 0, pipes[1], pipes[0]); close(pipes[1]); in = pipes[0]; /** launch second process read on in of old pipe , write on out of new pipe **/ pipe(pipes); argn = 0; argv[argn++] = "/usr/bin/head"; argv[argn++] = "-2"; argv[argn++] = null; pids[1] = add_child_process(argv, in, pipes[1], pipes[0]); close(in); close(pipes[1]); in = pipes[0]; /** launch last process read on in of old pipe , write on original stdout (1) **/ argn = 0; argv[argn++] = "/usr/bin/wc"; argv[argn++] = null; pids[2] = add_child_process(argv, in, 1, -1); close(in); /** wait process end catch return codes **/ int return_code; waitpid(pids[0], &return_code, 0); printf("process 0 return : %d\n", return_code); waitpid(pids[1], &return_code, 0); printf("process 1 return : %d\n", return_code); waitpid(pids[2], &return_code, 0); printf("process 2 return : %d\n", return_code); } with code (executable pc73 built pc73.c), output like:
$ ./pc73 2 14 109 process 0 return : 13 process 1 return : 0 process 2 return : 0 $ there's short, sub-second pause between output wc appearing , other output; ping command waits second before trying write again. exit status of 13 indicates ping did die sigpipe signal. previous problem ping still had read end of pipe open, didn't sigpipe signal.
lesson learned — close lots of file descriptors
when working pipes, make sure you're closing file descriptors need close.
Comments
Post a Comment