I'm having the following code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
exit(-1);
}
int pfd[2]; // pipe
int pfd2[2]; // pipe2
pid_t pid; // child
if (pipe(pfd) < 0) {
fprintf(stderr, "error.\n");
exit(-1);
}
if (pipe(pfd2) < 0) {
fprintf(stderr, "error.\n");
exit(-1);
}
if ((pid = fork()) < 0) {
fprintf(stderr, "error.\n");
exit(-1);
}
// child process
if (pid == 0) {
close(pfd[1]);
int v[100] = {0};
int k = 0;
int r;
char buf[128];
while ((r = read(pfd[0], buf, sizeof(buf)))) {
buf[r] = '\0';
// some processing here vor the 'v' variable
}
close(pfd[0]); // close the reading end 1st pipe
dup2(pfd2[1], 1); // redirect
close(pfd2[1]);
execlp("bash", "bash", "script.sh", v, NULL);
exit(-1);
} else {
// parent code
close(pfd2[1]);
dup2(pfd2[0], 0);
char buf[16];
int r = 0;
while ((r = read(pfd2[0], buf, sizeof(buf)))) {
buf[r] = '\0';
// read here from second pipe
}
close(pfd2[0]); // close reading end from 2pnd pipe
close(pfd[0]); // close reading end from 1st pipe
dup2(pfd[1], 1); // redirect
execlp("cat", "cat", argv[1], NULL); // exec 'cat' on the given arg
exit(-1);
}
return 0;
}
That respect the following flow: the parent process executes the 'cat' command on the given arg file, then pass it to the child -> the child does some processing and then stores values in the 'v' variable -> then executes the 'script.sh' script with the values taken from the 'v' variable and then passes the output to the parent which will print to stdout the output based on the result from the child.
I'm unsure where should the code for the 2nd pipe reading should go, I'm pretty sure that's the problem right now.
Can anyone take a look at it and point where the problem is? Thanks!
Related
Problem - when calling ls -l | grep etc, stuck on grep (grep child process does not exit)
trying to run "ls | grep r" with "execvp()" suggests that
need to close file descriptors
wait outside of the forking loop
IMO I have performed both of above but the problem still exists.
Any opinion is welcome, thanks!
Note that below is a hard-coded version for 2 pipes only
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
int i = 0;
int pfd[2];
if (pipe(pfd) != 0)
{
printf("Error creating pipe\n");
exit(errno);
}
char **ptr = get_pipes(); // pipes as array of strings
char *command = *ptr;
while (command != NULL)
{
if (i == 2)
break; // hard code to ignore all commands after 2nd pipe
char **args = parse_cmd(command); // this parses a space-separated command as arguments
pid_t pid = fork();
if (pid == 0 && i == 0) // 1st pipe, 1st child
{
close(pfd[0]); // close pipe read end
dup2(pfd[1], 1); // set pipe write end to stdout
if (execvp(args[0], args) == -1)
{
fprintf(stderr, "%s: %s\n", args[0], strerror(errno));
exit(EXIT_FAILURE);
}
}
else if (pid == 0 && i == 1) // 2nd pipe, 2nd child
{
close(pfd[1]); // close pipe write end
dup2(pfd[0], 0); // set pipe read end to stdin
if (execvp(args[0], args) == -1)
{
fprintf(stderr, "'%s': %s\n", args[0], strerror(errno));
exit(EXIT_FAILURE);
}
// exit(EXIT_SUCCESS);
}
else if (pid > 0) // parent
{
printf("Parent pid: %d and child's pid is %d\n", (int)getpid(), (int)pid);
}
command = *++ptr;
i++;
}
pid_t zombie_pid;
int status;
do
{
zombie_pid = waitpid(-1, &status, 0);
printf("Child PID %d died with status %d\n", (int)zombie_pid, WEXITSTATUS(status));
} while (zombie_pid > 0);
}
I have user read/write permissions on a pipe. Group has read. Other has read. But program gets "stuck" when I run it. Program 1 is the "parent". Program 2 is the "child".
Program 1:
int main(int argc, char * argv[])
{
FILE *fptr; //for opening and closing input file
int fdw;// write to pipe;
int fdr; //read to pipe;
pid_t pid;
int inputarray[500];
int arraylength = 0; int j =0;
char *mypipe = "mypipe";
if (argc < 2)
{
printf("Need to provide the file's name. \n");
return EXIT_FAILURE;
}
//open input file
fptr = fopen(argv[1], "r");
if (fptr==NULL)
{
printf("fopen fail.\n");
return EXIT_FAILURE;
}
//read input file and fill array with integers
while (!feof(fptr))
{
fscanf(fptr,"%d",&inputarray[arraylength]);
arraylength = arraylength + 1;
}
fclose(fptr); //close input file
pid = fork();
mkfifo(mypipe, 0666);
fdw = open("mypipe",O_WRONLY);
if (fdw < 0)
{
perror("File can't open to write.");
return;
}
int b;
b=3;
write(fdw,&b,sizeof(b));
close(fdw);
if ( pid ==-1)
{
perror("fork");
exit(1);
}
int status; //exit status of child
if(pid==0)//if child process
{
execl("program2", (char*) NULL);
}
else //if parent process
{
wait(&status);}
if((WIFEXITED(status)))
{
printf("Child's exit code %d", WEXITSTATUS(status));
}
else{
printf("Child did not terminate with exit");}
}
Program 2:
int fdl;
int data;
fdl = open("mypipe",O_RDONLY);
if ( fdl < 0)
{
perror("File can't open to read.");
return;
}
read(fdl,&data,sizeof(data));
close(fdl);
The program will block on writing to the fifo until what it's writing is being read. The reading in the child process won't happen since the execl() doesn't happen until after the writing.
Also, it looks like both processes will actually attempt to write to the fifo since you fork() and then immediately start writing.
You should fork(), then test on the returned PID. The parent should then write to the fifo while the child should call execl(). The fifo should be created by the parent before the fork() call.
You should also consider using indent or clang-format to properly format your code, which eases reading it and may expose bugs (forgotten curly braces etc.).
A simple complete example program. The parent writes a string to the child and the child reads it character by character and outputs it to standard output:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
void parent(void);
void child(void);
int main(void) {
pid_t pid;
mkfifo("myfifo", 0666); /* fails if exists, but we don't care here */
if ((pid = fork()) < 0)
abort();
if (pid == 0)
child(); /* will not return */
else
parent();
return EXIT_SUCCESS;
}
void parent(void) {
int fd;
int len;
int ret;
int stat;
char *ptr;
char *msg = "Hello World!";
if ((fd = open("myfifo", O_WRONLY)) < 0)
abort();
len = strlen(msg) + 1;
ptr = msg;
puts("Parent: About to write to child");
while ((ret = write(fd, ptr, len)) != 0) {
if (ret > 0) {
len -= ret;
ptr += ret;
} else
abort();
}
close(fd);
puts("Parent: Waiting for child to exit");
wait(&stat);
printf("Parent: Child exited with status %d\n", stat);
}
void child(void) {
int fd;
int ret;
char ch;
if ((fd = open("myfifo", O_RDONLY)) < 0)
abort();
puts("Child: About to read from parent");
while ((ret = read(fd, &ch, 1)) != 0) {
if (ret > 0)
putchar(ch);
else
abort();
}
putchar('\n');
close(fd);
puts("Child: I'm done here");
exit(EXIT_SUCCESS);
}
In this case, since both child and parent processes are in the same context, I could have used an anonymous pipe pair created with pipe(), but this illustrates the flow, including the creation of the named pipe.
Hi i'm trying to build a shell on linux and i'm stuck with the pipelining part.First i take the inputs from the user like "ls | sort" then when i try to run the program it lookls like the commands ls and sort doesnt work
It looks like i've done everything right but it still cant seem to work. can you help please. thanks in advance
include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int setup();
int main(int argc, const char * argv[])
{
while(1)
{
printf("333sh: ");
if(setup())
break;
}
return 0;
}
int setup(){
char input [128];
char *arg[32];
int i = 1;
while(fgets(input,128,stdin)!=NULL)
{
arg[0] = strtok(input," \n");
while((arg[i]=strtok(NULL," \n")) != NULL){
i++;
}
if (arg[1]!=NULL && strcmp(arg[1],"|")==0 && arg[2]!=NULL ){
pid_t pid;
int fd[3];
pipe(fd);
pid=fork();
if(pid<0){
printf("fork");
}
else if(pid==0){
pid_t cpid;
cpid=fork();
if(cpid==0){
dup2(fd[2], 1); // Replace stdin with the read end of the pipe
close(fd[0]); // Don't need another copy of the pipe read end hanging about
close(fd[2]);
execvp(arg[0],arg);
}
else if(pid>0){
dup2(fd[0], 0); // Replace stdout with the write end of the pipe
close(fd[0]); //close read from pipe, in parent
close(fd[2]); // Don't need another copy of the pipe write end hanging about
execvp(arg[2], arg);
}
}
else if(pid>0){
waitpid(pid, NULL,0);
}
}
}
}
Your biggest problem is that your argument lists for your commands are malformed (after you've resolved the index 2 vs index 1 issue with the pipe file descriptors diagnosed by Ben Jackson in his answer).
I added a function:
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
and called it just before the calls to execvp(), and the output I got was:
$ ./ns
333sh: ls | sort
args for 29780:
0: [ls]
1: [|]
2: [sort]
ls: sort: No such file or directory
ls: |: No such file or directory
^C
$
The control-C was me interrupting the program. The arguments for each command must be 'the command name' (conventionally, the name of the executable), followed by the remaining arguments and a null pointer.
Your tokenization code is not providing two correct commands.
You also have a problem with which PID you're looking at:
cpid = fork();
if (cpid == 0)
{
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else if (pid > 0) // should be cpid!
{
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(pid, arg);
execvp(arg[1], arg);
fprintf(stderr, "Failed to exec %s\n", arg[1]);
exit(1);
}
You also need to close the pipe file descriptors in the parent process before waiting.
This code compiles and 'works' for simple x | y command sequences such as ls | sort or ls | sort -r. However, it is far from being a general solution; you'll need to fix your argument parsing code quite a lot before you reach a general solution.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int setup(void);
int main(void)
{
while (1)
{
printf("333sh: ");
if (setup())
break;
}
return 0;
}
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
int setup(void)
{
char input[128];
char *arg[32];
int i = 1;
while (fgets(input, sizeof(input), stdin) != NULL)
{
arg[0] = strtok(input, " \n");
while ((arg[i] = strtok(NULL, " \n")) != NULL)
{
i++;
}
if (arg[1] != NULL && strcmp(arg[1], "|") == 0 && arg[2] != NULL)
{
pid_t pid;
int fd[2];
arg[1] = NULL;
pipe(fd);
pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (pid == 0)
{
pid_t cpid = fork();
if (cpid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (cpid == 0)
{
printf("Writer: [%s]\n", arg[0]);
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else
{
printf("Reader: [%s]\n", arg[2]);
assert(cpid > 0);
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), &arg[2]);
execvp(arg[2], &arg[2]);
fprintf(stderr, "Failed to exec %s\n", arg[2]);
exit(1);
}
}
else
{
close(fd[0]);
close(fd[1]);
assert(pid > 0);
while (waitpid(pid, NULL, 0) != -1)
;
}
}
}
return 1;
}
You're using fd[0] and fd[2] but pipe(fd) only sets fd[0] and fd[1].
Couple of immediate problems:
setup() has no return value, but you expect an int
The definition of fgets is:
char * fgets ( char * str, int num, FILE * stream );
Get string from stream
Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
fgets() returns NULL on an error; otherwise it returns a pointer to str. So this seems like a very unsound test condition in your while loop.
I'm trying to make a pipe system for my shell, but it's not working as intended.
void pipes (char *listaCommand[], int end, char **argv)
{
int cont = end;
for (cont;listaCommand[cont]; cont++)
{
if (listaCommand[cont] != NULL)
{
if (!strcmp(listaCommand[cont],"|")){
int pid2, status;
int pipefd[2], ret;
listaCommand[cont] = NULL;
ret = pipe (pipefd);
if (ret < 0) fatal();
/* Now fork. */
pid2 = fork ();
if (pid2 <0) fatal ();
if (pid2 > 0)
{
printf ("P: waiting for child\n");
wait (&status);
close(STDIN_FILENO);
dup(pipefd[0]);
close(pipefd[0]);
close(pipefd[1]);
/*execvp (auxCommand[0], auxCommand);*/
pipes(listaCommand, cont+1, argv);
/*break;*/
}
else
{
close (STDOUT_FILENO);
dup (pipefd[1]);
close (pipefd[1]);
close (pipefd[0]);
}
}
}
}
if (end >= 3)
{
printf("%s \n", listaCommand[end-1]);
}
execvp (listaCommand[end], listaCommand);
printf ("%s: command not found.\n", listaCommand[end]); /* Exec failed. */
exit(EXIT_FAILURE);
}
If I use commands like ls | sort, it works, but if ls has any argument, it doesnt work, because for some reason, listaCommand[cont] where its == "|" is not NULL, so I just get
ls: option -- 'a' invalid.
listaCommand have
[0] = "ls"
[1] = "-al"
[2] = "|"
[3] = "sort"
You don't need to pass the end argument, instead increment the pointer to your command array. You are passing the initial array to the execvp call so it tries to execute ls multiple times. Further, you need a break statement after setting the listaCommand[cont] to NULL because after the iteration cont is incremented. Also I think you need to protect the execvp call so that the parent does not call it after the processing is done.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define fatal() exit(1)
void pipes (char *listaCommand[], char **argv)
{
printf("pipes %s\n", listaCommand[0]);
int cont = 0;
for (;listaCommand[cont]; cont++) {
if (listaCommand[cont][0] == '|'){
int pid2, status;
int pipefd[2], ret;
listaCommand[cont] = NULL;
ret = pipe (pipefd);
if (ret < 0) fatal();
/* Now fork. */
pid2 = fork ();
if (pid2 <0) fatal ();
if (pid2 > 0)
{
printf ("P: waiting for child\n");
wait (&status);
close(STDIN_FILENO);
dup(pipefd[0]);
close(pipefd[0]);
close(pipefd[1]);
/*execvp (auxCommand[0], auxCommand);*/
pipes(listaCommand + cont + 1, argv);
/*break;*/
}
else
{
close (STDOUT_FILENO);
dup (pipefd[1]);
close (pipefd[1]);
close (pipefd[0]);
break;
}
}
}
if (listaCommand[0]) {
execvp (listaCommand[0], listaCommand);
printf ("%s: command not found.\n", listaCommand[0]); /* Exec failed. */
exit(EXIT_FAILURE);
}
}
int main() {
char *args[] = { "ls", "-al", "|", "sort", "|" , "tr", "[a-z]", "[A-Z]", 0 };
pipes(args, 0);
return 0;
}
I'm trying to implement a very small shell of my own. I have to be able to handle pipes, like
ls -l | wc -l
but only for two programs at a time. Right now, I have this:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFFER_SIZE 256
#define NO_PARAMS 32
void split_string(char **params, char *string){
char *arg;
int i;
arg = strtok(string, " ");
params[0] = arg;
i = 1;
while(arg != NULL){
arg = strtok(NULL, " ");
params[i] = arg;
i++;
}
}
int main(int argc, char **argv){
char string[BUFFER_SIZE];
char *prog1, *prog2;
int i, err;
int fd[2];
pid_t pid1, pid2;
size_t buffer = BUFFER_SIZE;
char *params1[NO_PARAMS], *params2[NO_PARAMS];
int pipe_exists = 0;
memset(string,0,buffer);
while(1){
/*Read command*/
fgets(string, BUFFER_SIZE-1, stdin);
if(string == NULL){
perror("Error reading input:\n");
exit(1);
}
/*replace linefeed character with end of line character*/
for(i=0;i<BUFFER_SIZE;i++){
if(string[i] == 10){
string[i] = 0;
}
}
/*check if command is "exit"*/
if(strcmp(string,"exit") == 0){
return 0;
}
/*split command into different program calls*/
prog1 = strtok(string, "|");
prog2 = strtok(NULL,"\0");
if(prog2 != NULL){
pipe_exists = 1;
printf("PIPE!\n");
err = pipe(fd);
if(err<0){
perror("Error creating pipe:\n");
exit(1);
}
}
/*split string into arguments*/
split_string(params1, prog1);
if(pipe_exists){
split_string(params2, prog2);
}
/*fork child process*/
pid1 = fork();
if(pid1==0){ /*child 1*/
if(pipe_exists){
close(fd[0]); /*close read-end*/
err = dup2(fd[1], 1);
if(err<0){
perror("Error with dup in child 1!\n");
exit(1);
}
}
execvp(params1[0],params1);
perror("Error calling exec()!\n");
exit(1);
}else{ /*parent*/
if(pipe_exists){
pid2 = fork();
if(pid2==0){ /*child 2*/
close(fd[1]); /*close pipe write-end*/
err = dup2(fd[0], 0);
if(err<0){
perror("Error with dup in child 2!\n");
exit(1);
}
execvp(params2[0],params2);
perror("Error calling exec()!\n");
exit(1);
}else{ /*parent with 2 children*/
waitpid(pid1,0,0);
waitpid(pid2,0,0);
}
}else{ /*parent with 1 child*/
waitpid(pid1,0,0);
}
}
}
}
Right now, it'll handle single commands fine, but when I input something like the command above, nothing happens!
Thanks!
Oh! I've already figured it out. I had to close the pipe in the parent program as well :)
To start with, you should loop as long as you find the pipe character. Then you need to create a pipe for each "piping".
Real shells usually forks and exec itself for each command in the pipeline. This is so it should be able to handle internal commands.
There are 3 main parts in a command with pipes.
The begining, that takes stdin and pipes its output something |
The middle, optionnal or repeated at will with two pipes | something |
The end, that outputs to stdout | something
Then use three functions, one for each of those:
#define PIPE_INPUT 0
#define PIPE_OUTPUT 1
execute_pipe_start(t_cmdlist *commands)
{
int pid;
int fd[2];
if (!commands)
return;
if (commands->next)
{
if (pipe(fd) < 0)
{
perror("pipe failed");
exit(1);
}
pid = fork();
if (!pid)
{
close(fd[PIPE_INPUT]);
if (dup2(fd[PIPE_OUTPUT, 1) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
{
waitpid(...); //what you put here is a bit tricky because
//some shells like tcsh will execute all
//commands at the same time (try cat | cat | cat | cat)
}
if (commands->next->next != null) //If you have 2 commands in line there is a middle
execute_pipe_middle(commands->next, fd);
else // no middle
execute_pipe_end(commands->next, fd);
}
else
parse_and_exec_cmd(commands->cmd);
}
execute_pipe_middle(t_cmdlist *commands, int fd_before[2])
{
int pid;
int fd_after[2];
if (pipe(fd_after) < 0)
{
perror("pipe failed");
exit(1);
}
pid = fork();
if (!pid)
{
close(fd_before[PIPE_OUTPUT]);
close(fd_after[PIPE_INPUT]);
if (dup2(fd_after[PIPE_OUTPUT, 1) < 0)
{
perror("dup2 failed");
exit(1);
}
if (dup2(fd_before[PIPE_INPUT, 0) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
waitpid(...);
if (commands->next->next != null) //More than two following commands : a middle again
execute_pipe_middle(commands->next, fd_after);
else // No more repetition
execute_pipe_end(commands->next, fd_after);
}
execute_pipe_end(t_cmdlist *commands, int fd_before[2])
{
int pid;
if (!commands)
return;
if (commands->next)
{
pid = fork();
if (!pid)
{
close(fd_before[PIPE_OUTPUT]);
if (dup2(fd_before[PIPE_INPUT, 0) < 0)
{
perror("dup2 failed");
exit(1);
}
parse_and_exec_cmd(commands->cmd);
}
else
waitpid(...);
}
}