Trying to re-implement "|" pipe operator in C - c

I'm trying to re-implement The pipe "|" operator.
The program will be executed as follows:
$ ./exe infile cmd cmd2 cmd3 cmdn outfile.
where at first i'm gonna read from infile process infile's data through the commands and finally pipe it to outfile.
IMPLEMENTAIONS:
My pseudo code looks like the following:
change infile descriptor to stdin.
loop each command.
pipe();
fork();
if (we're in the child process)
change stdin to read_end of pipe.
execute command.
else if (we're in the parent process)
change stdout to write_end of pipe.
execute command.
wait for child process.
change outfile descriptor to stdout.
CODE:
int infile_fd = open(args->infile, O_RDONLY);
dup2(infile_fd, STDIN_FILENO);
// how many commands
while(i < args->len)
{
// error handling stuff
args->cmds[i].cmd = check_exist(args->cmds[i], args);
if (pipe(args->cmds[i].fd) == -1)
{
perror("piping failed\n");
exit(EXIT_FAILURE);
}
if ((args->cmds[i].pid = fork()) == -1)
{
perror("fork failed\n");
exit(EXIT_FAILURE);
} else {
// child process
if (args->cmds[i].pid == 0)
{
close(args->cmds[i].fd[WRITE_END]);
dup2(args->cmds[i].fd[READ_END], STDIN_FILENO);
if (execve(args->cmds[i].cmd, args->cmds[i].flags, env) == -1)
{
perror("execve failed\n");
exit(EXIT_FAILURE);
}
i++;
}
else
{
// parent process
close(args->cmds[i].fd[READ_END]);
dup2(args->cmds[i].fd[WRITE_END], STDOUT_FILENO);
if (execve(args->cmds[i].cmd, args->cmds[i].flags, env) == -1)
{
perror("execve failed\n");
exit(EXIT_FAILURE);
}
wait(NULL);
i++;
}
}
}
int outfile_fd = open(args->outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
// change stdout to outfile
dup2(outfile_fd, STDOUT_FILENO);
OUTPUT:
output is garbage, commands read data off the stack and start showing env variables.
EXPECTED OUTPUT:
data first being read from "infile" and then passed through commands until the end of the piping channel at "outfile".
It would be silly to ask what am I doing wrong, because I'm probably doing it all wrong, so a better question: How can I do it right?

Related

How can I use few commands in execv() [duplicate]

I want to simulate bash in my Linux C program using pipes and execvp function. e.g
ls -l | wc -l
There is my program:
if(pipe(des_p) == -1) {perror("Failed to create pipe");}
if(fork() == 0) { //first fork
close(1); //closing stdout
dup(des_p[1]); //replacing stdout with pipe write
close(des_p[0]); //closing pipe read
close(des_p[1]); //closing pipe write
if(execvp(bash_args[0], bash_args)) // contains ls -l
/* error checking */
}
else {
if(fork() == 0) { //creating 2nd child
close(0); //closing stdin
dup(des_p[0]); //replacing stdin with pipe read
close(des_p[1]); //closing pipe write
close(des_p[0]); //closing pipe read
if(execvp(bash_args[another_place], bash_args)) //contains wc -l
/* error checking */
}
close(des_p[0]);
close(des_p[1]);
wait(0);
wait(0);
}
This code actually runs, but doesn't do the right thing.
What's wrong with this code? That's not working and I don't have a clue why.
You need to close the pipe fds in the parent, or the child won't receive EOF, because the pipe's still open for writing in the parent. This would cause the second wait() to hang. Works for me:
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int des_p[2];
if(pipe(des_p) == -1) {
perror("Pipe failed");
exit(1);
}
if(fork() == 0) //first fork
{
close(STDOUT_FILENO); //closing stdout
dup(des_p[1]); //replacing stdout with pipe write
close(des_p[0]); //closing pipe read
close(des_p[1]);
const char* prog1[] = { "ls", "-l", 0};
execvp(prog1[0], prog1);
perror("execvp of ls failed");
exit(1);
}
if(fork() == 0) //creating 2nd child
{
close(STDIN_FILENO); //closing stdin
dup(des_p[0]); //replacing stdin with pipe read
close(des_p[1]); //closing pipe write
close(des_p[0]);
const char* prog2[] = { "wc", "-l", 0};
execvp(prog2[0], prog2);
perror("execvp of wc failed");
exit(1);
}
close(des_p[0]);
close(des_p[1]);
wait(0);
wait(0);
return 0;
}
Read up on what the wait function does. It will wait until one child process exists. You're waiting for the first child to exit before you start the second child. The first child probably won't exit until there's some process that reads from the other end of the pipe.

execlp() failing to retrieve correct input

I've been trying to write a really simple program in which the parent process passes 100 lines to a child process through a pipe. The child should then use the generated lines and execute the command line program more over those lines.
However, when I try to run the program, it just freezes. I was careful to close all descriptors not being used by both processes but I don't really understand what may be causing it.
Code:
int main(void){
int fd[2];
if (pipe(fd) == -1){
perror("Error creating pipe");
return 1;
}
dup2(fd[1], STDOUT_FILENO);
int i;
for (i = 1; i <= 100; i++){
printf("Line %d\n", i);
}
close(fd[1]);
pid_t pid = fork();
if(pid == 0) {
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execlp("more", "more",(char*) NULL);
fprintf(stderr, "Failed to execute 'more'\n");
exit(1);
}
wait(NULL);
return 0;
}
I was careful to close all descriptors not being used by both processes
Not really.
dup2(fd[1], STDOUT_FILENO);
Here you make stdout a copy of fd[1].
close(fd[1]);
Here you close fd[1], but stdout is still open.
Then you fork. At this point both processes have access to the write end of the pipe via stdout.
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
In the child process you copy fd[0] to stdin and close fd[0].
Then, when you exec more, it still has access to both ends of the pipe (via stdin / stdout).
At the same time your parent process has access to both ends of the pipe (via fd[0] / stdout).
In effect you've closed nothing.
There's a second issue: Your parent process writes to stdout, which is bound to the write end of the pipe, without anyone reading it. Depending on how much you write, whether stdout is line buffered or block buffered, how big the stdout buffer is, and how much your pipe itself can store, this itself can deadlock. If the pipe runs full and there's no one around to read from it, printf will just block.
To fix this, don't dup2 in the parent process and don't write to the pipe before the child process has started.
int main(void){
int fd[2];
if (pipe(fd) == -1){
perror("Error creating pipe");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("Error spawning process");
return 2;
}
if (pid == 0) {
close(fd[1]); /* close write end of the pipe in the child */
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execlp("more", "more", (char*)NULL);
fprintf(stderr, "Failed to execute 'more'\n");
exit(1);
}
close(fd[0]); /* close read end of the pipe in the parent */
FILE *fp = fdopen(fd[1], "w");
if (!fp) {
perror("Error opening file handle");
return 3;
}
for (int i = 1; i <= 100; i++){
fprintf(fp, "Line %d\n", i);
}
fclose(fp); /* flush and close write end of the pipe in the parent */
wait(NULL);
return 0;
}

How to execute arbitrary pipes in c and continue

I'm trying to fork and then execute two or more piped commands in the child process. My idea is to use a while loop to continuously fork and execute the command in one process while continuing the loop in the other. Here's my code:
void
execute_pipe_command(command_t *c)
{
command_t command = *c;
pid_t pid = fork();
if(pid > 0) {
int status;
while(waitpid(pid, &status, 0) < 0)
continue;
if(!WIFEXITED(status))
error(1, errno, "Child exit error");
command->status = WEXITSTATUS(status);
return;
} else if (pid == 0) {
while(command->type == PIPE_COMMAND)
{
int fd[2]; pipe(fd);
pid = fork();
if(pid > 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
char **args = command->u.command[1]->u.word;
execvp(args[0], args);
} else if (pid == 0) {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
command = command->u.command[0];
continue;
} else {
error(1, errno, "forking error");
}
}
char **args = command->u.word;
execvp(args[0], args);
} else {
error(1, errno, "forking error");
}
}
Command is a struct that hold it's type, and if it's a pipe command it holds left and right children commands. Otherwise if it's a simple command it holds an array of strings that make up the command.
When I call this function with a pipe command like ls | cat it should execute the commands, but instead it behaves weirdly. The first two piped commands will run but won't give control back to the program. Instead it'll hang. The subsequent commands are just ignored. So if I give this ls | cat | wc this function will print ls and won't exit until I give a SIGINT.
I'm pretty much confused as to what's going on. I'd appreciate if someone could point out the problem.
while (command->type == PIPE_COMMAND) is always true! This is way it hangs.

Implementing shell in C and need help handling input/output redirection

Round 2
After reading some of the answers, my revised code is:
int pid = fork();
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
if (in) { //if '<' char was found in string inputted by user
int fd0 = open(input, O_RDONLY, 0);
dup2(fd0, STDIN_FILENO);
close(fd0);
in = 0;
}
if (out) { //if '>' was found in string inputted by user
int fd1 = creat(output, 0644);
dup2(fd1, STDOUT_FILENO);
close(fd1);
out = 0;
}
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
free(res);
}
It works, but seems the standard output isn't being reconnected or something to that effect. Here is execution:
SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output
'<' and '>' both work, but after they are executed there is no output.
Round 1
I have been working on a relatively simple shell in C for a while now, but I am having trouble implementing input (<) and output (>) redirection. Help me find the issues in the following code:
int fd;
int pid = fork();
int current_out;
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_out = dup(0);
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
if (pid == -1) {
perror("fork");
} else if (pid == 0) {
execvp(res[0], res);
perror("execvp");
_exit(1);
} else {
waitpid(pid, 0, 0);
dup2(current_out, 1);
free(res);
}
I may have some unnecessary material in there because I have been trying different things to get it to work. I am not sure what is going wrong.
You have way too many file descriptors open after your redirection. Let's dissect the two paragraphs:
if (in) { //if '<' char was found in string inputted by user
fd = open(input, O_RDONLY, 0);
dup2(fd, STDIN_FILENO);
in = 0;
current_in = dup(0); // Fix for symmetry with second paragraph
}
if (out) { //if '>' was found in string inputted by user
fd = creat(output, 0644);
dup2(fd, STDOUT_FILENO);
out = 0;
current_out = dup(1);
}
I'm going to be charitable and ignore the fact that you are ignoring errors. However, you will need to error check your system calls.
In the first paragraph, you open a file and capture the file descriptor (it might well be 3) in the variable fd. You then duplicate the file descriptor over standard input (STDIN_FILENO). Note, though, that file descriptor 3 is still open. Then you do a dup(0) (which, for consistency, should be STDIN_FILENO), getting another file descriptor, perhaps 4. So you have file descriptors 0, 3 and 4 pointing at the same file (and, indeed, the same open file description — noting that an open file description is different from an open file descriptor). If your intention with current_in was to preserve the (parent) shell's standard input, you have to do that dup() before you do the dup2() that overwrites the output. However, you would be better off not altering the parent shell's file descriptors; it is less overhead than re-duplicating the file descriptors.
Then you more or less repeat the process in the second paragraph, first overwriting the only record of file descriptor 3 being open with the fd = creat(...) call but obtaining a new descriptor, perhaps 5, then duplicating that over standard output. You then do a dup(1), yielding another file descriptor, perhaps 6.
So, you have stdin and stdout of the main shell redirected to the files (and no way of reinstating those to the original values). Your first problem, therefore, is that you are doing the redirection before you fork(); you should be doing it after the fork() — though when you get to piping between processes, you will need to create pipes before forking.
Your second problem is that you need to close a plethora of file descriptors, one of which you no longer have a reference for.
So, you might need:
if ((pid = fork()) < 0)
...error...
else if (pid == 0)
{
/* Be childish */
if (in)
{
int fd0 = open(input, O_RDONLY);
dup2(fd0, STDIN_FILENO);
close(fd0);
}
if (out)
{
int fd1 = creat(output , 0644) ;
dup2(fd1, STDOUT_FILENO);
close(fd1);
}
...now the child has stdin coming from the input file,
...stdout going to the output file, and no extra files open.
...it is safe to execute the command to be executed.
execve(cmd[0], cmd, env); // Or your preferred alternative
fprintf(stderr, "Failed to exec %s\n", cmd[0]);
exit(1);
}
else
{
/* Be parental */
...wait for child to die, etc...
}
Before you do any of this, you should ensure that you've already flushed the shell's standard I/O channels, probably by using fflush(0), so that if the forked child writes to standard error because of a problem, there is no extraneous duplicated output.
Also note that the various open() calls should be error-checked.
You have way too many file descriptors open after your redirection. The code which you need is this.
if (pid == 0)
{ /* for the child process: */
// function for redirection ( '<' , '>' )
int fd0,fd1,i,in=0,out=0;
char input[64],output[64];
// finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that
for(i=0;argv[i]!='\0';i++)
{
if(strcmp(argv[i],"<")==0)
{
argv[i]=NULL;
strcpy(input,argv[i+1]);
in=2;
}
if(strcmp(argv[i],">")==0)
{
argv[i]=NULL;
strcpy(output,argv[i+1]);
out=2;
}
}
//if '<' char was found in string inputted by user
if(in)
{
// fdo is file-descriptor
int fd0;
if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
perror("Couldn't open input file");
exit(0);
}
// dup2() copies content of fdo in input of preceeding file
dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0
close(fd0); // necessary
}
//if '>' char was found in string inputted by user
if (out)
{
int fd1 ;
if ((fd1 = creat(output , 0644)) < 0) {
perror("Couldn't open the output file");
exit(0);
}
dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
close(fd1);
}
execvp(*argv, argv);
perror("execvp");
_exit(1);
// another syntax
/* if (!(execvp(*argv, argv) >= 0)) { // execute the command
printf("*** ERROR: exec failed\n");
exit(1);
*/
}
else if((pid) < 0)
{
printf("fork() failed!\n");
exit(1);
}
else { /* for the parent: */
while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors)
}
}
Here's what's happening. After you call fork() there are two processes executing that are duplicates of the original process. The difference is in the return value of fork() which is stored in pid.
Then both processes (the shell and the child) redirect their stdin and stdout to the same files. I think you were trying to save the previous fd in current_out, but as Seth Robertson points out, this doesn't currently work, since the wrong file descriptor is being saved. The parent also restores its stdout, but not stdin.
You could fix this bug, but you can do better. You don't actually have to redirect parent's output, just the child's. So simply check pid first. Then there is also no need to restore any file descriptors.

Closing pipe, dup2, file descriptors in C?

I'm running a program that does piping.
The command I want to run is ls | cat.
int cmd(char** w, int* pipe, int action){
... some code up here
...
int fd;
if(child_pid == 0) {
if (pipe != 0) {
if (action == 0){
fd = dup2(pipe[0], STDIN_FILENO);
close(pipe[0]);
close(pipe[1]);
//close(fd);
}
else if (action == 1){
fd = dup2(pipe[1], STDOUT_FILENO);
close(pipe[0]);
close(pipe[1]);
//close(fd);
}
}
execvp(w[0], w);
printf("Unknown command\n");
exit(0);
}
... some code down here
When I run the code, the command ls | cat runs fine except that the cat doesn't end(i.e. the pipe doesn't close and just waits there doing nothing). I think it's because I didn't close a stream or something, but I'm not familiar enough with C/IO to know for sure. Am I doing this right?
the code that runs this function is like
int fd[2];
int p = pipe(fd);
cmd(w, fd, 1);
cmd(w, fd, 0);
edit: ur right, fatalerror, i mistyped on the arguement
thxs, looks like i just needed to close pipe[1] in the parent
The parent process also needs to close both ends of the pipe after the two cmd calls.

Resources