Forking multiple child processes and open()/dup2() - c

So I'm trying to read a line with multiple commands and creating a child process for each command to handle it. I'm having some trouble once I try to uncomment the if(fd2) statement for stdout redirection. Instead of the desired output appearing once, it appears multiple times. I'm not exactly sure what is causing this, I'm closing all of the file descriptors and having the child exit.
The commands I'm dealing with may have both stdout and stdin redirection, and I'm successful in setting up the redirection for stdin, but I cant seem to find my bug. I don't get a perror() warning on display. It's not so much as a problem with dup2(), but rather with open(), if I just uncomment that part I get the error.
int concCmd(Cmd cmdM[], int iCmdCnt, Token tokenM[], int iTokenCnt){
long pid, wpid;
int i, status=0, fd = 0, fd2=0;
fflush(0);
for(i=0; i<iCmdCnt; i++){ //create a process for every command
pid=fork();
switch(pid){
case -1:
perror("Fork\n");
return 1;
case 0:
if(cmdM[i].iStdinRedirectIdx != 0){ //does command need stdin redirection
if((fd = open(tokenM[cmdM[i].iStdinRedirectIdx], O_RDONLY)) <0){
perror("open stdin");
return 1;
}
if((dup2(fd,STDIN_FILENO))<0){
perror("dup2");
return 1;
}
close(fd); //close in parent process
}
if(cmdM[i].iStdoutRedirectIdx != 0){ //command needs stdout redirection
// if((fd2 = open(tokenM[cmdM[i].iStdoutRedirectIdx], O_WRONLY|O_CREAT|O_EXCL)) < 0){
// perror("open stdout");
// return 1;
// }
// if((dup2(fd, STDOUT_FILENO))<0){
// perror("dup2");
// return 1;
// }
close(fd2);
}
//EXECVP HERE!!!
//execvp (cmdM[i].szCmdNm, tokenM[
exit(0);
default:
wpid = wait(&status);
close(fd);
close(fd2);
fprintf(stderr, "%ld %ld\n", (long)getpid(), wpid);
}
}
return 0;
}
I went ahead an added a fflush(0) before I started forking, to clear out the buffer, as suggested in another question, which should reasonably clear the buffer for when I call open for stdout, but I still get the same errors.

I was going to make a comment, because what I want to say is very simple, but I need to reference some code, so maybe posting an answer is easier.
// if((fd2 = open(tokenM[cmdM[i].iStdoutRedirectIdx], O_WRONLY|O_CREAT|O_EXCL)) < 0){
// perror("open stdout");
// return 1;
// }
// if((dup2(fd, STDOUT_FILENO))<0){
// perror("dup2");
// return 1;
// }
In your code, the part about fd2 which you comment in your question has a typo.
dup2(fd, STDOUT_FILENO) should be dup2(fd2, STDOUT_FILENO)
I don't know whether it is really a typo in your source code, or you just made a mistake when you posting your question.

Related

piping for a simple shell in c

I'm trying to implement piping in a simple shell program that I'm writing in C.
But for some reason, I'm not getting output when I try to run ls | wc -l.
I'm really not sure why this is happening since I'm basically putting the child process's output to pipe[1] which does the command before pipe indicator and I'm putting parent's input to pipe[0] which does the command after pipe indicator and it should be printing to the terminal since the output of parent's never been changed, my approach right now is if piping is flagged the call fork in child and do the piping.
code below
int pipe1[2];
int pipepid;
int piping; /*flag for piping*/
int pipeposition;/*index of pipe indicator*/
//* code... */
if(pipe(pipe1)!= 0){
perror("pipe");
exit(1);
};
/* split commands to before pipe indicator and after */
for(int p = 0;p<pipeposition;p++){
argsbefore[p]=args[p];
}
/* after */
int e=0;
for(int h = pipeposition+1; h<cnt;h++){
argsafter[e]=args[h];
e++;
}
/* code ... */
if(piping){
pipepid = fork();
if(pid == 0){
/* do child */
if(dup2(pipe1[1],1)==-1){
perror("dup2 child");
exit(1);
}
close(pipe1[1]);
if (execvp(argsbefore[0], argsbefore) < 0) {
printf("exec failed\n");
exit(1);
}
exit(0);
}/* else if error */
else if(pid == -1){
printf("ERROR: fork failed\n");
exit(1);
}/* parent */
else{
if(dup2(pipe1[0],0)==-1){
perror("dup2 parent");
exit(1);
}
close(pipe1[0]);
if (execvp(argsafter[0], argsafter) < 0) {
printf("exec failed\n");
exit(1);
}
}
}
you seem to be doing that on a unix-like system. If you're lucky, your system might have a tool that reports every system call your program perform (strace -f my_program my_ar gu_ments would do that on Linux).
That would give you a list of what process did what and when, and whether there have been error code for some operations. That usually helps a lot with these multi-process setups.
It turns out I didn't close all the pipes so the second command wasn't able to finish, after putting close for both ends in the main parent process it was fixed

Shell Redirection causes infinite printing to terminal

So I am writing a simple shell in C that can do STDOUT redirects. I am reading a string from the user, split it in arguments and then feeding it to exec pretty much. But when I am redirecting the output it 1st: Creates the file I asked for and populates it with the correct data, and then prints the command results in the terminal infinitely. Am I doing something wrong with dup2?
Here is my redirecting code. All the variables used are global, except for the file descriptor.
int execRedirectCommand(){
int fd;
pid_t pid = fork();
// fork failed
if(pid == -1){
char* error = strerror(errno);
printf("fork: %s\n", error);
return -1;
}
//Child process
else if(pid == 0){
fd = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, 0755);
if(fd == -1){
char* error = strerror(errno);
printf("open:%s\n", error);
return -1;
}
dup2(fd, STDOUT_FILENO);
close(fd);
execvp(payloadArgv[0], payloadArgv);
// execvp failed
char* error = strerror(errno);
printf("pdsh:%s:%s\n", payloadArgv[0], error);
return -1;
}
// Parent process
else{
close(fd);
fileName = NULL;
// Wait for child process to finish
int childStatus;
waitpid(pid, &childStatus, 0);
return 0;
}
}
EDIT Fixed a typo in the code nothing major.
EDIT 2 Including my main:
int main(){
setSigHandler();
char* user = getlogin();
while(1){
printf("[%s]-->$", user);
getNextCommand(payload);
if(!strcmp(payload, "\n")) continue;
if(!strcmp(payload, "close")) break;
parseCommandString();
if(fileName != NULL){
execRedirectCommand();
}else{
execSimpleCommand();
}
}
return 0; }
Note: commands without STDOUT redirection work just fine.
Thanks to mata and nsilent22 for pointing that out. The close(fd) in the parent process is not needed.

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.

Pipes and Forks

The goal of this project is to use pipes and forks to execute a line-count utility already written in a multi-process manner (one process per argument). I'm currently working on getting a single process working before expanding to handle multiple args.
Given two executables, lc1 and lc2, I want lc2 to establish a pipe to the stdout file descriptor of lc1, so that when execlp("lc1", argv[1], NULL) is called, the output will be read in by
while ((c= read(pipefd[0], readin, SIZE)) > 0)
According to my Unix book, I should use the open, dup2, close method for redirecting stdout to stdin, and here's my code:
int pid, c, i;
char *readin= (char *)malloc(sizeof(SIZE));
if (pipe(pipefd)== -1)
perror("Can't open a pipe\n");
for (i=1; i< argc; i++){
if ((pid= fork())==-1)
perror("Can't fork\n");
run(argv[i]);
}
//close pipe
close(1);
if (dup2(pipefd[0], 0)==-1)
perror("Can't redirect stdin");
close(pipefd[1]);
for (i=1; i< argc; i++){
if ((wait(NULL))== -1)
perror("Wait error");
while ((c= read(pipefd[0], readin, SIZE)) > 0){;
//print buf count
total += atoi(readin);
}
}
The run function is
void run(char *f){
int fp;
if ((fp= open(f, O_RDONLY)) == -1)
perror("Can't open the file");
close(pipefd[0]);
dup2(pipefd[1], 1);
close(pipefd[1]);
execlp("ls1", f, NULL);
}
When I try to execute this code, I get a stdin redirect error saying bad file descriptor. Why is this happening, and would appreciate any hints to for fixing.
run(argv[i]) is executed by both parent and child because are not assigning the functionality based on the returned PID, so one close after the other may have closed.
See below code, can he handy, I will use the code sample for situations like this. :
int main()
{
int pipe_fd[2] = {0};
int pid = -1;
int status = -1;
int ret_value = INVALID_CMD;
int cmd_output_len = -1;
status = pipe(pipe_fd);
if(status<0)
{
perror("pipe create err");
}
else
{
pid = fork();
if(pid<0)
{
}
else if (pid == 0)
{
/*Child functionality*/
child_func(pipe_fd, cmd);
}
else
{
/*Parent functionality*/
cmd_output_len = parent_fun(pid, pipe_fd);
}
}
return ret_value;
}
int child_func(int pipe_fd[], const char * cmd)
{
int status = 5;
int read_fd = pipe_fd[0]; /*read file descriptor*/
int write_fd = pipe_fd[1]; /*write file descriptor*/
int exit_status = 0;
/*close read fd*/
close(read_fd);
/*dup2 stdout to write fd*/
//status = dup2(1, write_fd);
status = dup2(write_fd, 1);
if(status<0)
{
exit(-1);
}
else
{
system(cmd);
exit(0);
}
}
int parent_fun(int child_id, int pipe_fd[])
{
int status = -1;
int len = 0;
bool_e break_loop = FALSE;
int read_fd = pipe_fd[0]; /*read file descriptor*/
int write_fd = pipe_fd[1]; /*write file descriptor*/
/*close write fd*/
close(write_fd);
while(1)
{
sleep(1);
status = waitpid(child_id, &status, WNOHANG);
switch(status)
{
case 0:
/*Child is still active*/
printf("No process waiting to exit..\n");
len = do_ur_fun(read_fd);
write(1, output, len);
break;
/*case EINTR:
case ECHILD:
case EINVAL:
perror("waitpid error");
break_loop = TRUE;
break;*/
default:
if(status<0)
{
perror("waitpid error");
break_loop = TRUE;
len = -1;
}
else if(child_id == status)
{
/*Valid staus from child*/
len = read_output(read_fd, output);
//write(1, output, len);
break_loop = TRUE;
}
else
{
}
break;
}
if(TRUE == break_loop)
{
break;
}
}
return len;
}
int do_ur_fun (int read_fd)
{
/*Do your exec*/
}
MaheshGupta024 identified a very important problem in your code; I'm assuming you will fix that.
One of the other problem areas is:
close(1);
if (dup2(pipefd[0], 0)==-1)
perror("Can't redirect stdin");
close(pipefd[1]);
for (i=1; i< argc; i++){
if ((wait(NULL))== -1)
perror("Wait error");
while ((c= read(pipefd[0], readin, SIZE)) > 0){;
//print buf count
total += atoi(readin);
}
}
The first close closes the process's standard output; this is seldom a good idea. The next line duplicates the read end of the pipe to standard input - which is fine. As noted in a comment above, perror() does not exit. You then close the write end of the pipe - that's correct; but you should presumably close the read end of the pipe too since you have set it to come from the pipe.
Your loop starts OK; you have redundant parentheses in the wait() line. You read from pipefd[0] instead of standard input - so maybe you didn't want to close pipefd[0] but neither did you need to duplicate it to standard input. You then have a nested loop that reads on the pipe while there's more data to be read from a child - you don't absolutely need the wait() code with its loop since the inner while won't terminate until all the children are dead. On the other hand, there's no great harm in it - after the first child dies, you'll read the data from all the other children, then go into the outer loop and wait for each other child, with the inner loop terminating immediately since there is no data left to read.
So:
Don't close stdout.
Don't dup the pipe read to stdin.
Decide whether you want to clean up the loop - it will work, but could be cleaner.
The run() function is:
void run(char *f){
int fp;
if ((fp= open(f, O_RDONLY)) == -1)
perror("Can't open the file");
close(pipefd[0]);
dup2(pipefd[1], 1);
close(pipefd[1]);
execlp("ls1", f, NULL);
}
The argument should be const char *f (or use name or file instead of f). I would also pass the pipefd array to the function rather than use a global variable
.
Do not call a file descriptor fp; that name conventionally indicates a variable of type FILE *, not int.
However, you don't need to open the file in the first place - unless you want the calling program to do the error reporting instead of the invoked program. However, if you do want the calling program to do the error reporting, you should close the file descriptor before proceeding. (I've already commented on perror() returning).
It would be a good idea to print an error message after execlp(); the only time the function returns is when it fails, so there is no need to test its return value. You might want to exit too - rather than have the failed function go through the rest of the main program after the call to run().
Good points: you did close both the pipe file descriptors.
Hence:
void run(const char *file, int *pipefd)
{
close(pipefd[0]);
dup2(pipefd[1], 1);
close(pipefd[1]);
execlp("ls1", f, NULL);
perror("Failed to exec ls1");
exit(EXIT_FAILURE);
}

How to catch the ouput from a execl command

I'm using the execl function to run a Linux process from C. When I do, for example:
int cmd_quem() {
int result;
result = fork();
if(result < 0) {
exit(-1);
}
if (result == 0) {
execl("/usr/bin/who", "who", NULL);
sleep(4); //checking if father is being polite
exit(1);
}
else {
// father's time
wait();
}
return 0;
}
I get on the console the result of doing "who" on the terminal. What I'd like to know is if there is any function to "catch" the output result from a command. What I mean is, if there is anyway to catch this:
feuplive tty5 2009-11-21 18:20
Which is one of the lines resulting from the who command.
To do this, you need to open a pipe. You then replace the child's stdout with the writing end of the pipe, and read from the reading end of the pipe in the parent. Like this modified version of your code:
int cmd_quem(void) {
int result;
int pipefd[2];
FILE *cmd_output;
char buf[1024];
int status;
result = pipe(pipefd);
if (result < 0) {
perror("pipe");
exit(-1);
}
result = fork();
if(result < 0) {
exit(-1);
}
if (result == 0) {
dup2(pipefd[1], STDOUT_FILENO); /* Duplicate writing end to stdout */
close(pipefd[0]);
close(pipefd[1]);
execl("/usr/bin/who", "who", NULL);
_exit(1);
}
/* Parent process */
close(pipefd[1]); /* Close writing end of pipe */
cmd_output = fdopen(pipefd[0], "r");
if (fgets(buf, sizeof buf, cmd_output)) {
printf("Data from who command: %s\n", buf);
} else {
printf("No data received.\n");
}
wait(&status);
printf("Child exit status = %d\n", status);
return 0;
}
First, execl does not return unless there's a problem like the executable is not found. That sleep(4) is probably never executed.
As for redirecting and getting the output, check out the Unix Programming FAQ. Look for spawn_background_command.
The exec() family of functions creates a new process image from a regular, executable file. This file is either an executable object file, or an interpreter script. There is no return from a successful call to an exec() function, because the calling process is functionally replaced by the new process.
So any code after exec() is never executed unless it is failed.
If you want to capture output of a shell command you need popen.

Resources