Can't read and print pipe content (from child executing through execv) - c

I wrote a program that reads a bash file and execute the commands inside that file. I got the execution part working, but I can't seem to be able to redirect the command output to a pipe, then read from the pipe and print its content. I've read some topics on the subject but the solution do not seem to work. Here is the code,
int main(int argc, char * argv[]) {
int pipefd[2];
char buf[1024];
int bytes = 0;
for (int i = 1; i < argc; i++) { // reading file as argument one at a time
FILE * fp;
fp = fopen(argv[i], "r");
char * buffer = NULL;
size_t buf_size = 0;
char bufread[1024];
int bytes_read = 0;
while (getline( & buffer, &buf_size, fp) != EOF) {
pid_t pid;
pid = fork();
pipe(pipefd);
if (pid == 0) {
close(pipefd[0]);
dup2(pipefd[1], 1);
dup2(pipefd[1], 2);
close(pfd[1]);
int length = countWords(buffer);
char * init_argv[length + 2];
init_argv[0] = "sh";
init_argv[1] = "-c";
init_argv[2] = buffer;
init_argv[3] = NULL;
execv("/bin/bash", init_argv);
} else {
int status;
close(pfd[1]);
waitpid(pid, & status, 0);
while (read(pfd[0], bufread, sizeof(bufread)) != 0) {
fprintf(stdout, "%s\n", bufread);
}
}
}
}
}
return 0;
}
Why there isn't any output when this program is run with a valid file containing commands as argument? I know without the dup2 and close instructions, the program execute the commands so that is not the problem. As soon as I add the dup2 and close instruction to the program, I can't debug it anymore (it crash at execv).

I'm not sure if this is the problem, but shouldn't the call pipe(pipefd); be executed before the call to fork?
As it is now, I think the parent and the child each create a different pipe, and they cannot use those pipes to communicate with one another, if that is what you want to do.

Related

Minibash in C, problem making pipes between execvp and parent proccess

I have to do this as a university project so I cant share the whole code, im sorry for that.
I have to create a function called "read" that enables the user to create new env variables, thats the easy part. The problem comes when I call that function as the last one of the commands array e.g "ls | grep aux.txt | read a" this should give the env var A the value aux.txt, the problem is that it get stuck in the
fgets(value, sizeof(value),stdin);
and I cant even recover the terminal.
Thanks in advance for the help if you need more info about the problem I will happily give it.
I can't reproduce exactly the main function as there are parts that are not mine but I hope this helps:
char **argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv) {
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++) {
pipe(fd[i]);
}
for(int i = 0; i< 3; i++){
pid = fork();
if(pid == 0){
if(i ==0){
dup2(fd[0][1], 1);
fun_close(fd);
execvp(argvv[0][0], argvv[0]);
}
if(i == 1){
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
fun_close(fd);
execvp(argvv[1][0], argvv[0]);
}
}else{
if(i == 2){
close(fd[0][1]);
close(fd[0][0]);
fun_read("read a", 3, fd[1]);
}
}
}
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
void fun_close(int **fd){
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
}
And here is the fun_read:
int fun_read(char **command, int argc, int fd[]){
char **env_varv;
char value[1024];
char last_var[1024];
long size = 0;
char *token;
int status;
char *delim = " \t\n";
env_varv = malloc((argc-1) * sizeof(char *));
for(int i = 1; i < argc; i++){
env_varv[i-1] = strdup(command[i]);
wait(status);
}
if (fd[0] !=0){
printf("%d\n", fd[0]);
dup2(fd[0],0);
close(fd[0]);
close(fd[1]);
}
fgets(value, sizeof(value),stdin);
int i = 0;
token = strtok(value, delim);
last_var[0] = '\0';
while(token != NULL){
if(i == argc-2){
while (token != NULL){
strcat(last_var,token);
setenv(env_varv[i],last_var,1);
token = strtok(NULL,delim);
strcat(last_var," ");
}
}
else if (env_varv[i] != NULL){
setenv(env_varv[i],token,1);
token = strtok(NULL,delim);
i++;
}
else{
break;
}
}
return 0;
The program should put an envariomental variable called a with the value of example.
postscript: it seems like there is no problem if the previous command is a builtin "echo hi | echo hi2 | read a" $a=hi2
Sincerely I have tried all, changing the pipes doesnt work, changing fgets for read doesn't help either. Is the only part of the code I haven't been able to fix
This fragment of code shows some problems:
char ***argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv) {
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++) {
pipe(fd[i]);
}
for(int i = 0; i< 3; i++){
pid = fork();
if(pid == 0){
if(i ==0){
close(fd[0][0]);
close(fd[1][1]);
close(fd[1][0]);
dup2(fd[0][1], 1);
execvp(argvv[0][0], argvv[0]);
}
if(i = 1){
close(fd[0][1]);
close(fd[1][0]);
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
execvp(argvv[1][0], argvv[0]);
}
if(i = 2){
close(fd[0][1]);
close(fd[0][0]);
close(fd[1][1]);
dup2(fd[1][0], 0);
fun_read("read a", 3, fd[1]);
}
}
}
Rule of Thumb
You aren't closing enough pipe file descriptors in any of the processes.
If you dup2()
one end of a pipe to standard input or standard output, close both of the
original file descriptors returned by
pipe()
as soon as possible.
In particular, you should close them before using any of the
exec*()
family of functions.
The rule also applies if you duplicate the descriptors with either
dup()
or
fcntl()
with F_DUPFD or F_DUPFD_CLOEXEC.
Other comments on the use of pipes
If the parent process will not communicate with any of its children via
the pipe, it must ensure that it closes both ends of the pipe early
enough (before waiting, for example) so that its children can receive
EOF indications on read (or get SIGPIPE signals or write errors on
write), rather than blocking indefinitely.
Even if the parent uses the pipe without using dup2(), it should
normally close at least one end of the pipe — it is extremely rare for
a program to read and write on both ends of a single pipe.
Note that the O_CLOEXEC option to
open(),
and the FD_CLOEXEC and F_DUPFD_CLOEXEC options to fcntl() can also factor
into this discussion.
If you use
posix_spawn()
and its extensive family of support functions (21 functions in total),
you will need to review how to close file descriptors in the spawned process
(posix_spawn_file_actions_addclose(),
etc.).
Note that using dup2(a, b) is safer than using close(b); dup(a);
for a variety of reasons.
One is that if you want to force the file descriptor to a larger than
usual number, dup2() is the only sensible way to do that.
Another is that if a is the same as b (e.g. both 0), then dup2()
handles it correctly (it doesn't close b before duplicating a)
whereas the separate close() and dup() fails horribly.
This is an unlikely, but not impossible, circumstance.
Analyzing your code
The parent process has the pipes open; if the commands are reading from the pipes, they won't get EOF until the parent process closes them. Although you close most of the pipes in the child processes, you don't close those that you duplicate to the standard I/O channels — and yet that is required too.
Note that if (i = 1) should be if (i == 1), and if (i = 2) should be if (i == 2). The first of those bugs prevents your fun_read() from being invoked — which is why it isn't responding. Using diagnostic printing to standard error would confirm that fun_read() is never called.
So, at bare minimum, you need to have code like this:
char ***argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv)
{
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++)
{
pipe(fd[i]);
}
for (int i = 0; i < 3; i++)
{
pid = fork();
if (pid == 0)
{
if (i == 0)
{
dup2(fd[0][1], 1);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
execvp(argvv[0][0], argvv[0]);
fprintf(stderr, "failed to execute %s\n", argvv[0][0]);
exit(EXIT_FAILURE);
}
if (i == 1)
{
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
execvp(argvv[1][0], argvv[0]);
fprintf(stderr, "failed to execute %s\n", argvv[1][0]);
exit(EXIT_FAILURE);
}
if (i == 2)
{
dup2(fd[1][0], 0);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
fun_read("read a", 3, fd[1]);
exit(EXIT_SUCCESS);
}
}
}
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
/* wait loop here - and not before */
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
}
Note that it is important to handle failure to execute. And error messages should be reported to standard error, not to standard output.
Given that the same sequence of 4 calls to close() is made 4 times, a function to do the job seems appropriate. You could make it:
static inline void close_pipes(int fd[2][2])
{
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
}
There is a decent chance the compiler will inline the function, but it is easier to see that the same 4 descriptors are closed if one function always does the closing. For bigger arrays of pipes (more processes), you'd have a loop inside the close_pipes() function with a counter as well as the array.
There are still some issues to be resolved, notably with the fun_read() function. The fd[1] file descriptors were both closed, so passing those to fun_read() doesn't seem likely to be useful. Since fun_read() is executed in a separate process, any changes made by fun_read() won't be reflected in the parent process. There are probably other problems too.
AFAICT, on looking at fun_read() more closely, the fd argument should not be needed at all. The paragraph of code:
if (fd[0] != 0) {
printf("%d\n", fd[0]);
dup2(fd[0], 0);
}
is not useful. You've already redirected standard input so it comes from the pipe and then closed the pipe file descriptor. This paragraph then changes standard input to come from the closed descriptor, which isn't going to help anything. But none of this helps you with the fact that anything done by fun_read() is done in a child process of your shell, so the environment in the main shell is not going to be affected.

Why does program hang on child to parent communication?

I am trying to understand why my program hangs. The Parent sends input froma
file it reads to the child program, and the child program will send the result of its computation back to it's parent. However, I have trouble sending the message back through a second pipe. The parent seems to hang when reading from the pipe.
From the other posts, I have read it seems to indicate that the parent should wait for the child to finish by using wait or waitpid (which in my case both of them does not resolve my issue).
I have notice by adding print statement that neither the PARENT or the CHILD finishes.. Could someone please explain to me why this is happening?
Why does this not work?
int main(int argc,char** argv) {
char buffer[1];
int i;
int fd1[2]; int fd2[2];
pipe(fd1); pipe(fd2);
pid_t pid;
// FIRST PROCESS.
// -------------------
pid = fork();
if(pid == 0) {
int cnt;
dup2(fd1[0], STDIN_FILENO);
dup2(fd2[1], STDOUT_FILENO);
for (i = 0; i < 2; i++) {
close(fd1[i]);
close(fd2[i]);
}
while(read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {
fprintf(stderr, "( %s )", buffer);
cnt = cnt + *buffer - 48;
}
write(STDOUT_FILENO, &cnt, sizeof(cnt));
exit(0);
}
// PARENT.
// ------------------------
int file = open(argv[1], O_RDONLY);
// READ THE FILE.
while(read(file, buffer, 1) > 0) {
if (48 <= *buffer && *buffer <= 57) {
// PIPE TO CHILD.
write(fd1[1], buffer, 1);
}
}
// WAIT FOR CHILD TO FINISH SENDING BACK.
// int status = 0;
// waitpid(pid, &status, 0);
// THIS BLOCK DOESN'T RESOLVE ANYTHING. IT HANGS AT WAIT OR WAITPID.
// **** THIS IS THE PART WHERE IT DOESN'T WORK.
while(read(fd2[0], buffer, 1) > 0) {
fprintf(stderr, "RESULT : %s", buffer);
}
// CLOSING PIPES
for (i = 0; i < 2; i++) {
close(fd1[i]);
close(fd2[i]);
}
close(file);
exit(0);
}
You aren't closing enough file descriptors in the parent soon enough.
Rule of thumb: If you
dup2()
one end of a pipe to standard input or standard output, close both of the
original file descriptors returned by
pipe()
as soon as possible.
In particular, you should close them before using any of the
exec*()
family of functions.
The rule also applies if you duplicate the descriptors with either
dup()
or
fcntl()
with F_DUPFD
Now, your child process is following the RoT perfectly. But the corollary for parent processes is that they need to close the unused ends of the pipe, and they must close the write end of a pipe that they use to signal EOF to the reading end of that pipe. This is where your code fails.
Arguably, before reading the file, the parent process should close the read end of the pipe it uses to write to the child, and it should close the write end of the pipe it uses to read from the child.
Then, after reading the whole of the file, it should close the write end of the pipe to the child, before going into the 'read from child' loop. That loop never terminates because the parent still has the write end of the pipe open, so there's a process that could (but won't) write to the pipe.
Also, since the child writes the bytes of an integer onto a pipe, the parent should read the bytes of an integer. Using char buffer[1]; with a %s format is pointless; you need a null terminator for the string, and a single char buffer can't hold both a null byte and any data.
Along with various other improvements ('0' instead of 48, for example), you might end up with:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(EXIT_FAILURE);
}
int fd1[2];
int fd2[2];
char buffer[1];
pipe(fd1);
pipe(fd2);
pid_t pid = fork();
if (pid == 0) {
int cnt = 0;
dup2(fd1[0], STDIN_FILENO);
dup2(fd2[1], STDOUT_FILENO);
for (int i = 0; i < 2; i++) {
close(fd1[i]);
close(fd2[i]);
}
while (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {
fprintf(stderr, "(%c)", buffer[0]); // Changed
cnt = cnt + buffer[0] - '0';
}
putc('\n', stderr); // Aesthetics
write(STDOUT_FILENO, &cnt, sizeof(cnt));
exit(0);
}
int file = open(argv[1], O_RDONLY);
if (file < 0) {
fprintf(stderr, "failed to open file '%s' for reading\n", argv[1]);
exit(EXIT_FAILURE);
}
close(fd1[0]); // Added
close(fd2[1]); // Added
while (read(file, buffer, sizeof(buffer)) > 0) {
if ('0' <= buffer[0] && buffer[0] <= '9') {
write(fd1[1], buffer, sizeof(buffer));
}
}
close(file); // Moved
close(fd1[1]); // Added
// Rewritten
int result;
while (read(fd2[0], &result, sizeof(result)) == sizeof(result)) {
fprintf(stderr, "RESULT : %d\n", result);
}
close(fd2[0]); // Added
// Close loop removed
return 0;
}
If that is stored in file pipe71.c and compiled, I get the following outputs when it is run:
$ ./pipe71 pipe71.c
(2)(0)(1)(2)(2)(2)(1)(1)(2)(0)(0)(2)(1)(0)(2)(2)(1)(0)(2)(1)(2)(0)(0)(0)(0)(0)(1)(0)(1)(1)(0)(2)(1)(0)(0)(0)(0)(9)(1)(1)(1)(1)(2)(0)(2)(0)(0)
RESULT : 49
$ ./pipe71 pipe71
(0)(0)(8)(0)(0)(2)(2)(0)(8)(1)(1)(5)(1)(1)(1)(1)(5)(1)(1)(1)(8)(5)(1)(9)(8)(5)(1)(1)(0)(4)(4)(4)(6)(0)(2)(8)(0)(0)(0)(2)(7)(1)(3)(8)(3)(0)(4)(3)(0)(4)(9)(0)(0)(0)(0)(7)(1)(9)(8)(1)(3)(0)
RESULT : 178
$

C: redirecting stdin, stdout to pipes

I'm in the process of properly understanding pipes and FDs and I'm trying to program the following thing:
The program basically compresses stuff like gzip does with the options -cf.
The basic idea is:
I create two pipes in the parent process, then I fork it twice so that I'll have two children. In the first child, I redirect the first pipe's read end to stdin, and the second pipe's write end to stdout. Then I exec gzip with the -cf options so that it'll write to stdout (now the writing end of pipe2)
In the second child, I read from pipe2 and either output it directly or save it to a file.
The problem is, however, that no data arrives at the second child and I'm not really sure why. Here's the code:
int main(int argc, char **argv) {
char *file;
int out = 0;
if(argc == 2) {
file = argv[1];
out = 1;
} else if (argc > 2) {
exit(EXIT_FAILURE);
}
int c1pipe[2];
int c2pipe[2];
pipe(c1pipe);
pipe(c2pipe);
int f;
for(int i = 0; i < 2; i++) {
switch(f = fork()) {
case 0: //child
if(i == 0) { //first loop iteration, child 1
close(c1pipe[1]);
dup2(c1pipe[0], fileno(stdin));
close(c1pipe[0]);
close(c2pipe[0]);
dup2(c2pipe[1], fileno(stdout));
close(c2pipe[1]);
execlp("gzip", "gzip", "-cf", (char *) NULL);
} else if (i == 1) { //second loop iteration, child2
close(c1pipe[0]);
close(c1pipe[1]);
close(c2pipe[1]);
FILE *read = fdopen(c2pipe[0], "r");
char buffer[1024];
if(out == 0) { //output to stdout
while(fgets(buffer, 1024, read) != NULL) {
fprintf(stdout, "%s", buffer);
fflush(stdout);
}
} else { //write to specified file
FILE *writeto = fopen(file, "w");
while(fread(buffer, sizeof(char), strlen(buffer)+1, read) > 0) {
fwrite(buffer, sizeof(char), strlen(buffer)+1, writeto);
fflush(writeto);
}
fclose(writeto);
}
close(c2pipe[0]);
fclose(read);
}
break;
case -1: //err
//not implemented
break;
default: //parent
if(i == 0) {
close(c2pipe[0]);
close(c2pipe[1]);
close(c1pipe[0]);
FILE *writer;
writer = fdopen(c1pipe[1], "w");
char buffer[1024];
while(fgets(buffer, sizeof buffer, stdin) != NULL) {
fwrite(buffer, sizeof (char), strlen(buffer)+1, writer);
}
close(c1pipe[1]);
fclose(writer);
}
break;
}
}
return 0;
}
Please excuse the missing error handling as I wanted to create a quick-and-dirty version.
Any help is appreciated.
In the parent process, you are closing both ends of c2pipe before you have forked the second child.
You'd probably have figured this out already if you had put any error handling in on any of the read/write calls. In fact, if you checked for an error on the dup2 calls and then looked at errno, you probably would have found that it was EBADF (bad file descriptor).
Another issue is that your parent process exits before it knows that both child processes have finished. This means that the child processes will receive a signal and will themselves be terminated. The parent needs to call one of the variants of wait() to make sure both children have gone.

Writing data to fork pipe does not work

I have the following code in which I use fork to launch my script. The script is listening on the stdin. I try to send data via pipe to myscript but the scipt did not get the data from C. Am I missing something in my code?
static int pfds_in[2], pfds_out[2];
void external_init()
{
int pid;
if (pipe(pfds_in) < 0)
return;
if (pipe(pfds_out) < 0)
return;
if ((pid = fork()) == -1)
goto error;
if (pid == 0) {
/* child */
close(pfds_in[0]);
dup2(pfds_in[1], 1);
close(pfds_in[1]);
close(pfds_out[0]);
dup2(pfds_out[1], 0);
close(pfds_out[1]);
const char *argv[5];
int i = 0;
argv[i++] = "/bin/sh";
argv[i++] = fc_script;
argv[i++] = "--json";
argv[i++] = "json_continuous_input";
argv[i++] = NULL;
execvp(argv[0], (char **) argv);
exit(ESRCH);
}
close(pfds_in[1]);
close(pfds_out[1]);
return;
error:
exit(EXIT_FAILURE);
}
static void external_write_pipe_output(const char *msg)
{
char *value = NULL;
int i=0, len;
asprintf(&value, "%s\n", msg);
if (write(pfds_out[0], value, strlen(value)) == -1) {
perror("Error occured when trying to write to the pipe");
}
free(value);
}
int main()
{
external_init();
external_write_pipe_output("any");
}
You mismatched the both file descriptors you get from pipe(). pfds_in[0] is for reading, so you have to use dup2( pfds_in[0], 0 ) in your child and in the parent you write into the pipe using pfds_in[1].
Btw: What did you want to achieve by dup( ..., 1 ) in your child?. If you want to redirect child's stdout into a pipe to your parent you have to create another pipe
You've got your pipe ends in a muddle.
In the child you should have:
close(pfds_in[0]);
dup2(pfds_in[1], 1);
close(pfds_in[1]);
close(pfds_out[1]);
dup2(pfds_out[0], 0);
close(pfds_out[0]);
And in the parent:
close(pfds_in[1]);
close(pfds_out[0]);

C Programming pipe only half working

I'm working on a mini shell for a college assignment. We have to read in the command, find the binary to execute from the path var, and execute command, both with and without pipes. I have everything working (I think) except for the pipe.
Through web searches I've been able to build a test program that use two hard coded commands and pipes one to the other, with the expected results. Now when I copy and paste that code into my actual program, the first command outputs fine (actually outputs the command as if there were no pipe), while the second I don't think actually does anything (the output from the first is not piped through to the second).
Here is the entire code:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFSIZE 1024
#define MAXWORDS 17
#define MAXCHAR 64
static char *path;
extern char **environ;
//split cmd "string" on pipe (|) symbol
void split(char **pipe, char **left, char **right, int n)
{
int i, x;
for(i = 0; i < n; i++)
{
if (strchr(&pipe[i][0], '|') != 0)
{
for(x = 0; x < i; x++)
strcpy(left[x], pipe[x]);
left[x++] = 0;
break;
}
}
i++;
for(x = 0; i < n; x++)
strcpy(right[x], pipe[i++]);
right[x++] = 0;
}
//Find directory where cmd can be executed from (PATH or direct access)
char *finddir(char *s)
{
char *pp;
char *pf;
int ok;
strcpy(path, getenv("PATH"));
pp = strtok(path, ":");
while (pp != NULL)
{
pf = (char *)malloc(strlen(pp) + strlen(s) + 2);
if (pf == NULL)
{
fprintf(stderr, "Out of memory in finddir\n");
return NULL;
}
strcpy(pf,pp);
strcat(pf,"/");
strcat(pf,s);
ok = !access(pf, X_OK);
free(pf);
if (ok)
return pp;
pp = strtok(NULL, ":");
}
return NULL;
}
int cmdcheck(char *cmd, char *p)
{
char *dir;
if (strchr(p, '/') != NULL)
sprintf(cmd, "%s\0", p);
else
{
dir = finddir(p);
if (dir == NULL)
return 1;
else
sprintf(cmd, "%s/%s\0", dir, p);
}
return 0;
}
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
dup(pfd[0]);
close(pfd[1]); //the child does not need this end of the pipe
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
dup(pfd[1]);
close(pfd[0]); //the parent does not need this end of the pipe
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
int main(void)
{
int status; //read status when reading cmd in
char ch; //character currently reading
int n, i, x; //(n) count of chars read; (i) cmd args iter; (x) cmd arg iter in cmd array
char buffer[BUFFSIZE]; //read buffer
char *token; //token var when splitting buffer
int pid0, pid1, pid2; //return ID from fork call
int which; //return value from wait (child pID that just ended)
char msg[100]; //messages to print out
char *cmd1, *cmd2; //cmds when piping
char *params[MAXWORDS]; //cmd parameters to send to execve
int fd[2]; //pipe file descriptors
char *pparam1[MAXWORDS]; //cmd "string" on left side of pipe
char *pparam2[MAXWORDS]; //cmd on right side of pipe
for(;;)
{
for (i = 0; i < MAXWORDS; i++)
params[i] = malloc(MAXCHAR);
n = 0;
write(1, "# ", 2);
for(;;)
{
status = read(0, &ch, 1);
if (status == 0)
return 0; //End of file
if (status == -1)
return 1; //Error
if(n == BUFFSIZE)
{
write(1, "Line too long\n", 14);
return 1;
}
buffer[n++] = ch;
if(ch == '\n')
break;
}
buffer[n] = '\0';
x = 0;
token = strtok(buffer, " \t\n\0");
while(token != NULL)
{
strcpy(params[x++], token);
token = strtok(NULL, " \t\n\0");
}
params[x] = 0;
path = getenv("PATH");
if (path == NULL)
{
fprintf(stderr, "PATH environment variable not found.\n");
return 1;
}
n = strlen(path);
path = (char *)malloc(n+1);
if (path == NULL)
{
fprintf(stderr, "Unable to allocate space for copy of PATH.\n");
return 1;
}
cmd1 = malloc(MAXCHAR);
cmd2 = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam1[i] = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam2[i] = malloc(MAXCHAR);
split(params, pparam1, pparam2, x);
//Check first cmd
if(cmdcheck(cmd1, pparam1[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam1[0]);
write(1, msg, strlen(msg));
break;
}
//Check second cmd
if(cmdcheck(cmd2, pparam2[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam2[0]);
write(1, msg, strlen(msg));
break;
}
pipe(fd);
switch (pid0 = fork())
{
case 0: //Child
switch (pid1 = fork())
{
case 0: //Child
runpipe(fd, cmd1, pparam1, cmd2, pparam2);
exit(0);
default:
exit(0);
//break;
case -1: //ERROR
perror("fork-2");
exit(1);
}
default: //Parent
which = wait(&status);
if (which == -1)
{
write(1, "wait failed\n", 12);
exit(1);
}
if (status & 0xff)
sprintf(msg, "process %d terminated abnormally for reason %d\n", which, status & 0xff);
else
sprintf(msg, "process %d terminated normally with status %d\n", which, (status >> 8) & 0xff);
write(1, msg, strlen(msg));
break;
case -1: //ERROR
perror("fork-1");
exit(1);
}
free(cmd1);
free(cmd2);
for (i = 0; i < MAXWORDS; i++)
free(pparam1[i]);
for (i = 0; i < MAXWORDS; i++)
free(pparam2[i]);
free(path);
for (i = 0; i < MAXWORDS; i++)
free(params[i]);
}
return 0;
}
Typing echo one | wc -l at the prompt will only output one with the respective wait print statement following. It has been a few years since I've used C, so am I on the right track?
Thanks.
EDIT:
Here is the runpipe function as it stands now. But the only thing that is printed is the wait statement.
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
const int READ = 0;
const int WRITE = 1;
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
There are a couple of things going on there that are contributing to the unexpected behavior.
The first is that you're forking too much. If you unroll your runpipe() function call into the switch statement in main(), you'll see that you reach the great-grandchild level:
switch (pid0 = fork())
{
case 0: // Child
switch (pid1 = fork())
{
case 0: // GRAND-Child
// function call to runpipe()
switch (pid = fork())
{
case 0: // GREAT-GRAND-Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: // GRAND-Child
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
Which is not necessary. Fork once in main() and then call your runpipe() function.
Related to this issue is where you're creating your pipe. When you fork, the newly created child process inherits all of the parent process's open files (among many other things). This includes the default descriptors 0, 1, and 2 (stdin, stdout, and stderr), as well as any other open files, including the pipe you created called fd. This means that the parent, child, grandchild, and great-grandchild are all inheriting a copy of both ends of the pipe. You correctly close the unused ends inside the runpipe() function (the grandchild's and great-grandchild's copies), but the parent and child in your main() function also have copies!
Since the only pair of processes using the pipe are those created in runpipe(), you can move the declaration of fd and the call to pipe(2) into that function.
These two modifications will resolve your issues.
A completely unrelated issue that just relates to the flow of your shell is that your main() ends up doing its wait(2) on the "parent" process of the runpipe() function. Since that parent is the one running cmd1, your shell is going to return its prompt as soon as cmd1 finishes, instead of when the last command (cmd2 in this case) in the pipeline finishes. You can see the behavioral difference by running something like echo | sleep 10 into your shell and a real shell.
The dup function duplicates a file descriptor, and returns the new duplicate. However, this will not work, as stdin in the child still exists, and the new file descriptor will not be put in place of the standard input.
You must close the standard input file descriptor first, before doing dup. Or use dup2 which will close the destination file descriptor automatically first before doing the duplication:
dup2(pfd[0], STDIN_FILENO);

Resources