How to execute arbitrary pipes in c and continue - c

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.

Related

Can't quit a fork() when using pipe()

I am recreating a complete shell. For that I must simulate "|". To do this, I have to use the dup2(), fork() and pipe() functions.
The code I've had the most success with is this:
int exec_pipe(global *glob, char *commande)
{
int pipefd[2];
char **pipe_commandes = my_split(commande, '|');
char **left = my_str_to_word_array(pipe_commandes[0]);
char **right = my_str_to_word_array(pipe_commandes[1]);
int pid = 0;
int status;
pipe(pipefd);
pid = fork();
if (pid == 0) {
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
glob->commande = right;
distribe_commande(glob);
glob->commande = NULL;
} else {
close(pipefd[0]);
dup2(pipefd[1], 1);
close(pipefd[1]);
glob->commande = left;
distribe_commande(glob);
glob->commande = NULL;
}
}
The function distribe_commande() leads to a formatting of the command so that it is executed with execve() in this function:
void exec_path_commande(char *path, global *glob)
{
int pid;
int status;
pid = fork();
if (pid == 0) {
dup2(glob->fd, glob->origine);
if (execve(path, glob->commande, glob->env) == -1)
exit(0);
} else
while (waitpid(pid, &status, 0) != -1 && !WIFEXITED(status))
error_execve(status);
}
Where char *path is the correct formated command.
My probleme is that when I send the commande ls | cat -e the command work :
$~> ls | cat -e
^[[0$
42sh$
build$
CMakeLists.txt$
hello$
include$
Jenkinsfile$
lib$
main.c$
Makefile$
src$
But if I send another command to the programme the | cat -e effect remain even on the prompt and I don't understand why:
$~> ls | cat -e
^[[0$
42sh$
build$
CMakeLists.txt$
hello$
include$
Jenkinsfile$
lib$
main.c$
Makefile$
src$
^[[0;31m^[[1m$^[[0;36m^[[1m~^[[0;32m^[[1m> ^[[0;37m^[[0mls
^[[0$
42sh$
build$
CMakeLists.txt$
hello$
include$
Jenkinsfile$
lib$
main.c$
Makefile$
src$
^[[0;31m^[[1m$^[[0;36m^[[1m~^[[0;32m^[[1m> ^[[0;37m^[[0m
Thanks in advance for your answers.
You are doing dup2 in a wrong place. You also have one fork too many.
A redirect should look like this (outline/pseudocode, not real C code):
fd = open(...)
pid = fork()
if (pid == 0)
dup2(fd, 1) // redirect the output, just an example
close(fd)
exec(...)
wait(...)
Note, dup2 and close are after the fork and before the exec.
A pipeline is two (or more) redirects coordinated via a pipe, so:
pipe(fds)
pid1 = fork()
if (pid1 == 0)
dup2(fds[0], 0)
close(fds[0])
close(fds[1])
exec(...)
pid2 = fork()
if (pid2 == 0)
dup2(fds[1], 1)
close(fds[0])
close(fds[1])
exec(...)
wait(...)
wait(...)
Note also both waits are after both execs. If you do it the other way (exec-wait-exec-wait), commands like yes | head will not work.
So you need to refactor exec_path_commande quite a bit.

Shell in C to execute pipe

Hi I'm having a bit of trouble with my pipe execute function, where I want a shell in C to be able to execute a pipe. arg1 is the input before the pipe and arg2 is the command after the pipe. I want the program to terminate after ctr -d but it seems to quit without it, the moment the code is executed. An example of my input is ls | wc, where arg1 = ls and arg2 = wc. Any help/ pointers will be greatly appreciated, thank you.
int executepipe (char ** arg1, char ** arg2) {
int fds[2];
int child=-1;
int status = pipe(fds);
if (status < 0)
{
printf("\npipe error");
return -1;
}
int pid =-1;
pid= fork();
while(1){
if (pid < 0) { //error!
perror("fork");
exit(1);
}
//child
if (pid == 0){// child process (command after the pipe)
//signal(SIGINT, SIG_DFL);
//signal(SIGQUIT, SIG_DFL);
close(fds[1]);//nothing more to be written
dup2(fds[0], 0);
execvp(arg2[0], arg2);
//if errors exist execv wouldn't have been invoked
perror("cannot execute command");
exit(1);
}
else { // parent process (command before the pipe)
close(fds[0]);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
dup2(fds[1], 1);
close(fds[1]);
execvp(arg1[0], arg1);
//if errors exist execv wouldn't have been invoked
perror("cannot execute command");
exit(1);
}
if ( wait(&child) == -1 ){
perror("wait");}
}
return 0;
};

Execution of UNIX command is being outputted after I exit the program

For some unknown reason, when I'm executing piped commands in my shell program, they're only outputting once I exit the program, anyone see why?
Code:
int execCmdsPiped(char **cmds, char **pipedCmds){
// 0 is read end, 1 is write end
int pipefd[2];
pid_t pid1, pid2;
if (pipe(pipefd) == -1) {
fprintf(stderr,"Pipe failed");
return 1;
}
pid1 = fork();
if (pid1 < 0) {
fprintf(stderr, "Fork Failure");
}
if (pid1 == 0) {
// Child 1 executing..
// It only needs to write at the write end
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
if (execvp(pipedCmds[0], pipedCmds) < 0) {
printf("\nCouldn't execute command 1: %s\n", *pipedCmds);
exit(0);
}
} else {
// Parent executing
pid2 = fork();
if (pid2 < 0) {
fprintf(stderr, "Fork Failure");
exit(0);
}
// Child 2 executing..
// It only needs to read at the read end
if (pid2 == 0) {
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
if (execvp(cmds[0], cmds) < 0) {
//printf("\nCouldn't execute command 2...");
printf("\nCouldn't execute command 2: %s\n", *cmds);
exit(0);
}
} else {
// parent executing, waiting for two children
wait(NULL);
}
}
}
Output:
In this example of the output, I have used "ls | sort -r" as the example, another important note is that my program is designed to only handle one pipe, I'm not supporting multi-piped commands. But with all that in mind, where am I going wrong, and what should I do to fix it so that it's outputting within the shell, not outside it. Many thanks in advance for any and all advice and help given.
The reason would be your parent process file descriptors are not closed yet. When you wait for the second command to terminate, it hangs because the writing end is not closed so it wait until either the writing end is closed, or new data is available to read.
Try closing both pipefd[0] and pipefd[1] before waiting for process to terminate.
Also note that wait(NULL); will immediately return when one process has terminated, you would need a second one as to not generate zombies if your process still runs after that.

(SIMULATING A UNIX SHELL IN C) How to implement multiple pipes in a for loop?

I'm trying to simulate a unix shell in a C program and it's still in the beginning and working for at most two pipes. I have a vector of commands (char *com[3][3]), which were separated considering the character "|", but my question is how to proceed to more pipes in a for loop? In the follow the current implementation, I'm trying to execute 3 commands separeted by pipes:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char **argv){
//Vector with positions of pipes found, position 0 reserved for the total amount of commands.
char* com[3][3] = { { "/bin/ls", "-la", 0 },
{ "/bin/grep", ".", 0}, { "/usr/bin/wc", "-l", 0 }};
//EXECUTE COMMANDS
pid_t fork1, fork2, fork3;
int fd1[2], fd2[2];
if(pipe(fd1) < 0){
perror("pipe1");
}
if(pipe(fd2) < 0){
perror("pipe2");
}
//COMMAND 1
fork1 = fork();
if(fork1 == 0){
dup2(fd1[1], STDOUT_FILENO);
close(fd1[0]);
close(fd2[0]);
close(fd2[1]);
execvp(com[0][0], com[0]);
perror("execvp 1");
exit(EXIT_FAILURE);
}
//COMMAND 2
fork2 = fork();
if(fork2 == 0){
dup2(fd1[0], STDIN_FILENO);
dup2(fd2[1], STDOUT_FILENO);
close(fd1[1]);
close(fd2[0]);
execvp(com[1][0], com[1]);
perror("execvp 2");
exit(EXIT_FAILURE);
}
//COMMAND 3
fork3 = fork();
if(fork3 == 0){
dup2(fd2[0], STDIN_FILENO);
close(fd2[1]);
close(fd1[0]);
close(fd1[1]);
execvp(com[2][0], com[2]);
perror("execvp 3");
exit(EXIT_FAILURE);
}
close(fd1[0]);
close(fd1[1]);
close(fd2[0]);
close(fd2[1]);
waitpid(-1, NULL, 0);
waitpid(-1, NULL, 0);
waitpid(-1, NULL, 0);
return 0;
}
How do I make to com[n][3], in a for loop?
"To iterate is human, to recurse is divine" -- Anon.
I'd attack this with a recursive approach. This is one of those very rare occasions when being a Three Star programmer is almost justified. ;)
This is completely untested, but should get you pointed in the correct direction.
// You'll need to rearrange your command strings into this three dimensional array
// of pointers, but by doing so you allow an arbitrary number of commands, each with
// an arbitrary number of arguments.
int executePipe(char ***commands, int inputfd)
{
// commands is NULL terminated
if (commands[1] == NULL)
{
// If we get here there's no further commands to execute, so run the
// current one, and send its result back.
pid_t pid;
int status;
if ((pid = fork()) == 0)
{
// Set up stdin for this process. Leave stdout alone so output goes to the
// terminal. If you want '>' / '>>' redirection to work, you'd do that here
if (inputfd != -1)
{
dup2(inputfd, STDIN_FILENO);
close(inputfd);
}
execvp(commands[0][0], commands[0]);
perror("execvp");
exit(EXIT_FAILURE);
}
else if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
waitpid(pid, &status, 0);
return status;
}
else
{
// Somewhat similar to the above, except we also redirect stdout for the
// next process in the chain
int fds[2];
if (pipe(fds) != 0)
{
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid;
int status;
if ((pid = fork()) == 0)
{
// Redirect stdin if needed
if (inputfd != -1)
{
dup2(inputfd, STDIN_FILENO);
close(inputfd);
}
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
execvp(commands[0][0], commands[0]);
perror("execvp");
exit(EXIT_FAILURE);
}
else if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
// This is where we handle piped commands. We've just executed
// commands[0], and we know there's another command in the chain.
// We have everything needed to execute that next command, so call
// ourselves recursively to do the heavy lifting.
status = executePipe(++commands, fds[0]);
// As written, this returns the exit status of the very last command
// in the chain. If you pass &status as the second parameter here
// to waitpid, you'll get the exit status of the first command.
// It is left as an exercise to the reader to figure how to get the
// the complete list of exit statuses
waitpid(pid, NULL, 0);
return status;
}
}
To use this, call it initially with the commands array set up as described, and inputfd initially -1.
If you want to handle < type redirection, you probably want to check for inputfd == -1 at the very top, do redirection if requested and replace inputfd with the appropriate value before entering the remainder of the body.

Can't execute console commands in C with fork and pipe

I'm trying to make program on C, which execute console shell command
cat log.txt| awk '{ print $7 }' | head -10
but the third command won't work with 2 present.
Here's what i done
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int fd[2], status;
pipe(fd);
pid_t pid1 = fork();
if (!pid1)
{
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
char* command[3] = {"/bin/cat", "log.txt", 0};
execvp(command[0], command);
exit(EXIT_FAILURE);
}
else if (pid1 == -1)
{
fprintf(stderr, "Can't fork, exiting...\n");
exit(EXIT_FAILURE);
}
pid_t pid2 = fork();
if (!pid2) {
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
char* command[3] = {"awk", "{ print $7 }", 0};
execvp(command[0], command);
exit(EXIT_FAILURE);
} else if (pid2 == -1) {
fprintf(stderr, "Can't fork, exiting...\n");
exit(EXIT_FAILURE);
}
pid_t pid3 = fork();
if (!pid3) {
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
char* command[3] = {"head", "-10", 0};
execvp(command[0], command);
exit(EXIT_FAILURE);
} else if (pid3 == -1) {
fprintf(stderr, "Can't fork, exiting...\n");
exit(EXIT_FAILURE);
}
close(fd[0]);
close(fd[1]);
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
waitpid(pid3, &status, 0);
exit(status);
return 0;
}
pid3 can't execute. I tried to make dup2(fd[1], 1) in pid3, but thats doesn't work. What should be on pid3 to make it's work and how to make more than 3 commands using dup2?
You have created one pipe. One pipe has two ends. Two ends are enough for two processes. If you have three processes all in a single pipeline, you need two pipes. The process in the middle holds on two pipes and two other processes hold on the remaining ends.
Look at this picture:
cat | awk | head
See two pipe symbols? They are the two pipes you need.
You will have to set up two pipes-- one to connect cat to awk and one to connect awk to head.
Also, don't close file descriptors that you actually need (such as fd[0] in your first fork!)

Resources