Recursive piping in Unix again - c

I know this topic came up already several times, but I'm still stuck at one point.
I need to write a program that emulates cmd1 | cmd2 | cmd3 ... piping.
My code is here: http://ideone.com/fedrB8
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
void pipeline( char * ar[], int pos, int in_fd);
void error_exit(const char*);
static int child = 0; /* whether it is a child process relative to main() */
int main(int argc, char * argv[]) {
if(argc < 2){
printf("Usage: %s option (option) ...\n", argv[0]);
exit(1);
}
pipeline(argv, 1, STDIN_FILENO);
return 0;
}
void error_exit(const char *kom){
perror(kom);
(child ? _exit : exit)(EXIT_FAILURE);
}
void pipeline(char *ar[], int pos, int in_fd){
if(ar[pos+1] == NULL){ /*last command */
if(in_fd != STDIN_FILENO){
if(dup2(in_fd, STDIN_FILENO) != -1)
close(in_fd); /*successfully redirected*/
else error_exit("dup2");
}
execlp(ar[pos], ar[pos], NULL);
error_exit("execlp last");
}
else{
int fd[2];
pid_t childpid;
if ((pipe(fd) == -1) || ((childpid = fork()) == -1)) {
error_exit("Failed to setup pipeline");
}
if (childpid == 0){ /* child executes current command */
child = 1;
close(fd[0]);
if (dup2(in_fd, STDIN_FILENO) == -1) /*read from in_fd */
perror("Failed to redirect stdin");
if (dup2(fd[1], STDOUT_FILENO) == -1) /*write to fd[1]*/
perror("Failed to redirect stdout");
else if ((close(fd[1]) == -1) || (close(in_fd) == - 1))
perror("Failed to close extra pipe descriptors");
else {
execlp(ar[pos], ar[pos], NULL);
error_exit("Failed to execlp");
}
}
close(fd[1]); /* parent executes the rest of commands */
close(in_fd);
pipeline(ar, pos+1, fd[0]);
}
}
It works completely fine for up to 3 commands, but when it comes to 4 and more it doesnt any more and after hours of analysing, I still cant get where the problem is.
Example:
./prog ls uniq sort head
gives:
sort: stat failed: -: Bad file descriptor

Not an expert, but it seems the following line is the problem:
((close(fd[1]) == -1) || (close(in_fd) == - 1))
Try not to close in_fd there.
I think, the parent is trying to close the same fd that is closed by child.
When you use dup2() you do not need to close the files as dup2() already closes the file.

Related

pipe in C stuck when calling `wc` or `grep`

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);
}

C - Redirecting stdout after forking of child from parent

I'm writing a program to execute another program as a forked process and redirect it's output to a file or /dev/null on demand.
Currently I have forked and executed the external program using execvp().
Then redirected the stdout from a thread created before forking as the forked process will inherit parents file descriptor table allowing me to redirect after foking.
But, I can initially redirect stdout to a desired file and both parents and child's stdouts are being redirected. However if I try to redirect it again to another file, only parents stdout is redirected, child's stdout stays the same.
Here's the code without all the error checking bits.
struct params {
const char *p;
int fd;
int wait;
};
#define EXIT_NOEXEC 126
#define EXIT_NOTFOUND 127
#define EXIT_MISC 127
static void dofile(struct params* st);
void dupit(const char *p, struct params* st);
void* reload_config(void* para);
int
main(int argc, char *argv[]) {
int exit_status, prog_status;
struct params init;
pid_t prog_pid;
dofile(&init);
prog_pid = fork();
if (prog_pid == 0) {
execvp(*argv, argv);
exit_status = (errno == ENOENT) ? EXIT_NOTFOUND : EXIT_NOEXEC;
err(exit_status, "%s", argv[0]);
exit(EXIT_FAILURE);
} else {
while (wait(&prog_status) != prog_pid);
return prog_status;
}
}
static void dofile(struct params* st) {
const char *p
p = out.txt;
dupit(p, st);
}
void dupit(const char *p, struct params* st) {
pthread_t tid;
st->wait = 0;
int err = pthread_create(&(tid), NULL, &reload_config, st);
if (err != 0) {
printf("\ncan't create thread :[%s]", strerror(err));
exit(1);
} else {
while (st->wait == 0) {
sleep(1)
}
}
}
void* reload_config(void* para) {
struct params *passed = (struct params *) para;
int pre_config = 3;
int cur_config = 1;
int saved_stdout = dup(STDOUT_FILENO);
char infile[5];
int devNull = open("/dev/null", O_WRONLY);
int file = open("out.txt", O_WRONLY);
FILE *config;
config = fopen("config.txt", "r");
if (access("config.txt", F_OK) != -1) {
while (1) {
fgets(infile, 5, config);
fclose(config);
cur_config = infile[0] - '0';
printf("output from thread, current config = %d\n", cur_config);
if (pre_config != cur_config) {
if (cur_config == 1) {
if (dup2(file, STDOUT_FILENO) == -1) {
err(EXIT_MISC, NULL);
}
} else {
dup2(devNull, STDOUT_FILENO);
}
pre_config = cur_config;
}
if (passed->wait==0) {
passed->wait = 1;
}
sleep(1);
}
} else {
if (dup2(passed->fd, STDOUT_FILENO) == -1) {
err(EXIT_MISC, NULL);
}
}
}
Well, I changed the code a bit so you guys will understand, so some parts will make no sense. But you get the basic idea.
How can I redirect child's stdout as I wish after forking.
Since you asked, here is a simple example. Some shortcuts have been taken for brevity but hopefully it gives you some idea. The program opens file1 and redirects stdout to that file. It then does a fork. The child process writes a counter to stdout (via printf) every 1 second. After a few seconds the parent process uses IPC, a pipe in this example, to tell the child to switch redirect file.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char **argv)
{
pid_t pid;
const char *file1 = "file1.txt";
const char *file2 = "file2.txt";
int pipefd[2];
int fd;
int rval;
fd = open(file1, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("file1 open");
exit(-1);
}
/*
* This pipe will be used by parent process to tell child which file
* to redirect to.
*/
rval = pipe2(pipefd, O_NONBLOCK);
if (fd == -1) {
perror("pipe");
exit(-1);
}
/* Redirect stdout to the file opened before the fork. */
dup2(fd, STDOUT_FILENO);
pid = fork();
if (pid == -1) {
perror("fork");
exit(-1);
} else if (pid == 0) {
/* Child process. */
int ix;
char redirect_file[100];
close(pipefd[1]);
for (ix = 0; ix < 10; ix++) {
printf("%d\n", ix);
sleep(1);
rval = read(pipefd[0], redirect_file, sizeof(redirect_file));
if (rval > 0) {
/*
* Parent process has written a filename to the pipe.
*/
fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("file2 open");
exit(-1);
}
/* Ensure previous output has been written to current file. */
fflush(stdout);
/* Change redirect now. */
dup2(fd, STDOUT_FILENO);
}
}
} else {
/* Parent process. */
close(pipefd[0]);
/* Wait a little and then tell child to change redirect file. */
sleep(5);
write(pipefd[1], file2, strlen(file2) + 1);
wait();
}
}
If this program is run you will find that half the child output went to file1 (first redirect) and other half of the output goes to file2 (second redirect).
$ cat file1.txt
0
1
2
3
4
$ cat file2.txt
5
6
7
8
9
One final note. The example program does the first dup before the fork. I did it like that because that's how your code was shown and also to emphasise the before and after fork aspect of the issue. But in real code the conventional way of doing that is to do fork first, then dup and finally exec. The dup is done after the fork so that only the child process gets affected and not the parent (unless that is really what you want).

Almost done linux shell 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.

Can't pipe to less(1)

I'm trying to write a program that should works as " printenv | sort | less ". However the last pipe to less does not work. I can pipe to cat with the expected result (environment variables are displayed) but when i pipe to less nothing is displayed. The program runs less but nothing happens. If I go with more instead the program cats the output until the bottom of the window and after that nothing happens. I'm not in a pager.
Any ideas?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#define WRITE 1
#define READ 0
#define DEBUG 1
int p1[2], p2[2], p3[2];
void err(char* msg){
perror(msg);
exit(EXIT_FAILURE);
}
void closePipe(int pipeEnd[2]){
if (close(pipeEnd[READ]) == -1)
err("error when closing pipe, read");
if (close(pipeEnd[WRITE]) == -1)
err("error when closing pipe, write");
}
int main(int argc, char** argv, char** envp){
int pid1, pid2, pid3;
/*Creating pipes*/
if (pipe(p1) == -1){
err("pipe1");
}
if (pipe(p2) == -1){
err("pipe2");
}
/*
if (pipe(p3) == -1){
err("pipe3");
}
*/
/*First fork*/
pid1 = fork();
if (pid1 == -1){
err("fork1");
}
/**FIRST CHILD*************PRINTENV****************/
else if (pid1 == 0){
if (dup2(p1[WRITE],STDOUT_FILENO)== -1){
err("dup2 miss");
}
closePipe(p2);
closePipe(p1);
if (execlp("printenv", "printenv", NULL) == -1){
err("execlp printenv does not like you!");
}
}
/**PARENT*******************************************/
else {
pid2 = fork();
if (pid2 == -1){
err("fork2");
}
/**SECOND*CHILD***********SORT*******************/
else if (pid2 == 0){
if (dup2(p1[READ],STDIN_FILENO) == -1){
err( "dup2 p1 read" );
}
if (dup2(p2[WRITE], STDOUT_FILENO) == -1){
err("dup2 p2 write");
}
closePipe(p2);
closePipe(p1);
if (execlp("sort", "sort", NULL) == -1){
err("execlp sort does not like you!");
}
}
/**PARENT*****************************************/
else {
pid3=fork();
if (pid3 == -1){
err("fork3");
}
/**THIRD CILD***************LESS****************/
else if (pid3 == 0){
if (dup2(p2[READ], STDIN_FILENO) == -1){
err("err dup2 read");
}
closePipe(p2);
closePipe(p1);
char *args4exec[] = {"less", NULL};
if (execvp(args4exec[0], args4exec) == -1){
err("err in exelp pager");
}
}
}
}
return 0;
}
Two things. First off, in the parent you also need to close the pipes. Second, your parent process should wait for the less process to finish before exiting itself. Try adding this to your code:
/* ... */
closePipe(p1);
closePipe(p2);
waitpid(pid3, NULL, 0);
return 0;
}
The first issue causes the child never to be able to properly receive an EOF, and the 2nd will cause less to exit right away because the parent goes away.

I'm building a small shell. How do I set the standard in- and output of two processes to a pipe, so they can communicate?

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(...);
}
}

Resources