Difficulty with switching between stdout and file using Dup2 - c

I have a problem with dup2 syscall.
I added a while loop(a part of my program) that running over a students directory,
each student has a "c" file that I compile to an "exe" file. the students' programs scan from
testInput file (using dup2 to take the keyboard) 2 numbers, add them and then the answers is written down to programOutPut file.
afterward, I compare the 2 programs with a comparison program I wrote and it helps me with WEXITSTATUS to know whether a student succeeded in the test.
The problem is that I try to write the grade sheet into the file and then print it also to the screen.
somehow it only appears on the file or the screen but not both.
while (myDirent = readdir(dir)) {
if (strcmp(myDirent->d_name, ".") == 0 || strcmp(myDirent->d_name, "..") == 0)
continue;
if ((pid = fork()) == 0) {
status=execlp("gcc", "gcc", "-o", mainPath, cPath, NULL); //compiling students code to main.out path
if (status == -1) {
fprintf(stderr, "gcc Exec failed\n");
exit(-1);
}
}
wait(&status);
fdin = open(testInputPath, O_RDONLY); //test input file I wrote to compare with student's output
if(fdin==-1){
fprintf(stderr, "Error opening test input file\n");
exit(-1);
}
fdout = open(programOutputPath, O_WRONLY | O_CREAT | O_TRUNC,0777); //opening file for each student
if(fdout==-1){
fprintf(stderr, "Error opening Student's program output file\n");
exit(-1);
}
if ((pid = fork()) == 0) {
dup2(fdin, 0);
dup2(fdout, 1);
status= execlp(mainPath,mainPath, NULL);
if (status == -1) {
fprintf(stderr, "Student's main Exec failed\n");
exit(-1);
}
}
wait(&status);
fdresults = open("results.txt", O_WRONLY | O_CREAT | O_APPEND, 0777); //grades sheet
if (fdresults == -1) {
fprintf(stderr, "Error opening results.csv file\n");
exit(-1);
}
if ((pid = fork()) == 0) {
status= execlp("./comp.out", "./comp.out", programOutputPath, expectedOutputPath, NULL); //compare program I wrote that simply compare 2 files and return value to status
if (status == -1) {
fprintf(stderr, "Compare Exec failed\n");
exit(-1);
}
}
wait(&status);
**dup2(fdresults, 1); //trying to write to the file the grades
printf("%s,%d\n", myDirent->d_name,WEXITSTATUS(status));
dup2(fdscreen, 1); // trying to return to stdout unsuccessfuly**
}//end of while loop

First, do not open a file that is already open (source). Right now, you are opening testInputPath and programOutputPath during each iteration of the the loop without closing them. This can lead to undefined behavior.
It's unclear where fdscreen came from. If you do something similar to this answer, then you can achieve what you're looking for using dup(2). You should flush the corresponding userspace file buffer using fflush(3) before calling dup2.
A Better Solution
A much better solution is to open results.txt outside the loop, and then use dprintf(3) to write directly to the file. This is more elegant, and avoids having to change stdout. So, you can easily replace the last three lines of the loop with
dprintf(fdresults, "%s,%d\n", myDirent->d_name, WEXITSTATUS(status));
Don't forget to close fdresults after the loop is complete. In general, each open(2) should have a corresponding close(2). Using this approach is strongly encouraged over changing stdout every iteration of the loop.

Related

C program iterates too much when using fork inside while

I'm reading lines of text from file, and for each line I'm processing it using several { fork() --> child process invokes execvp(), and parent invokes wait() } .
at the end of process I'm writing the results to a file.
Problem is: the while loop seems to iterate too much and also the writing to the file.
The results.csv file contains 6 lines instead of just 2 (the while iteration
iterates a text file with 2 lines, but also when I use printf it seems like the last line is read twice).
What am I missing here?
The code example is:
FILE* results = fopen("results.csv", "w");
if (results == NULL){
fclose(fp);
perror("Failed opening results file");
exit(-1);
}
fdIn = open(inputPath, O_RDONLY);
if (fdIn < 0){
perror("Failed opening input file");
exit(-1);
}
while (fgets(student, sizeof(student), fp) != NULL) {
// override end line char of unix ('\n') with '\0'
student[strlen(student)-1] ='\0';
pid = fork();
if (pid < 0){
close(fdIn);
perror("Failed creating process for executing student's program");
exit(-1);
}
if (pid == 0) {// son process code
fdOut = open("tempOutput.txt", (O_WRONLY | O_CREAT | O_TRUNC), 0666);
if (fdOut < 0){
perror("Failed opening temporary output file");
exit(-1);
}
close(1);
dup(fdOut);
close(fdOut);
close(0);
dup(fdIn);
close(fdIn);
char studProgPath[bufSize];
strcpy(studProgPath,studentsFolderPath);
strcat(studProgPath,"/");
strcat(studProgPath,student);
strcat(studProgPath,"/");
strcat(studProgPath,"a.out");
char * args[] = {"a.out", NULL};
ret_code = execvp(studProgPath,args);
if (ret_code == -1){
perror("Failed executing student program");
exit(-1);
}
}
waited = wait(&stat);
if (stat == -1){ // need to grade 0
printf("%s,0\n",student);
}else{ // open process to compare the output with the expected
pid = fork();
if (pid < 0){
perror("Failed opening process for comparing outputs");
exit(-1);
}
if(pid == 0) { // son process
char * args[] = {"comp.exe",outputPath,"tempOutput.txt",NULL};
ret_code = execvp("comp.exe",args);
exit(ret_code);
}
waited = wait(&stat);
if (stat == -1) {
perror("Failed executing comparing program");
exit(-1);
} else if (stat == 0 || stat == 1) { // if outputs are not the same
fprintf(results,"%s,0\n",student);
} else { // matching outputs grade 100
fprintf(results,"%s,100, pid: %d\n",student,getpid());
}
}
}
The file which gets triple entries gets opened here:
FILE* results = fopen("results.csv", "w");
The following lines write to this results file, slightly before the function calls fork():
} else if (stat == 0 || stat == 1) { // if outputs are not the same
fprintf(results,"%s,0\n",student);
} else { // matching outputs grade 100
fprintf(results,"%s,100, pid: %d\n",student,getpid());
}
This file should be flushed with fflush(results) before the fork, otherwise the buffer of results might be flushed three times: in the parent, and in the two copies in the children.
Also, results and student should be closed with fclose(results) and student, before calling execvp. If the files are not closed, then the a.out might manipulate the results file. I assume that a.out is an external code which you don't control.
while (fgets(student, sizeof(student), fp) != NULL) {
// override end line char of unix ('\n') with '\0'
student[strlen(student)-1] ='\0';
fflush(results); // otherwise each child may flush the same chars
pid = fork();
if (pid < 0){
fclose(results); // otherwise ./a.out might write to this file
fclose(fp); // better also close it.
close(fdIn);

Using execl to call file from folder

I'm writing a program in C in Linux which creates main and assistant process and communication should be like: Main process (parent) receives keyboard input (char array) then passes it through pipe to assistant (child) process, then child should call some other .c file with execl (my .c file to call is named simple.c) and then pass the result on the second pipe... and at that point with execl I have some issue, here is my child code:
if (childpid == 0) {
printf("I am child!\n");
if (dup2(fd[0], STDIN_FILENO) == -1
|| dup2(fd2[1], STDOUT_FILENO) == -1)
fprintf(stderr,
"Child error: Redirection of std input/output failed!");
else if (close(fd[0]) == -1 || close(fd[1]) == -1
|| close(fd2[0]) == -1 || close(fd2[1]) == -1)
fprintf(stderr, "Child error: Pipes closing failed!");
else {
execl("/home/myproj/simple.c", " ", NULL);
fprintf(stderr, " Execl failed ! ");
}
return 0;
}
simple.c should be something like this:
int main()
{
// ...
read(0, string, sizeof(string));
string[0] = string[1];
write(1, string, sizeof(string)); // sending new string on second pipe
return 0;
}
Can anybody help me whats the problem with execl line? Thanks a lot.
Can anybody help me whats the problem with execl line?
You could have helped yourself by not using the poor error message
fprintf(stderr, " Execl failed ! ");
but something more sensible, like
perror("execl /home/myproj/simple.c");
this would have told you Permission denied and you could have looked up the possible reasons in the manual, finding the most probable:
The file described by path or file is not
executable.

Calling "fork() execvp() _exit()" , and the process is not exiting

So the brief summary is- I have a program which recursively searches for a file with a certain extension. Each time it finds one, it makes a copy, makes some changes to the copy, creates a patch (by using diff and execvp()) and delete the original file.
The problem I'm having is, after several hundred files, fork() returns with "Resource temporarily unavailable". I added a counter to see how many processes are still running when this failure happens, and it looks like none have been closed- the number of processes open is always the same as the number of files processed.
Now, I was under the impression that the flow should go something like this-
fork();//creates a child process
dostuff();//in the child process
_exit(1);//return control to the parent
but things appear not to be so simple. Perhaps somebody here may spot something obvious I'm missing in the code.
I have posted the 'cleanup' function which is responsible for the forking & patching- the rest is split over several files so hopefully this is enough.
(argument "name" is the original filename, and "newname" is the modified copy.)
void cleanup (char * name, char * newname)
{
if (pf)
{
pid_t patch_pid;
char * const diffargs[5] = {thisdiff, "-u", newname, name, NULL};
char * patchname = malloc(strlen(name) + 6);
strcpy(patchname, name);
strcat(patchname, ".patch");
if((patch_pid = fork()) < 0 )
{
printf("fork failed.\n%s\nfilecount: %ld\nopen forks: %d\n", strerror(errno), filecount, pcount);
exit(-1);
}
pcount++;
if (patch_pid == 0)
{
FILE *pfp;
if ((pfp = fopen(patchname, "w")) == NULL)
{
printf("Error opening file \"%s\" for writing.\n%s\n", patchname, strerror(errno));
exit(-1);
}
dup2(fileno(pfp), STDOUT_FILENO);
fclose(pfp);
execvp(diffargs[0], diffargs);
free(patchname);
if (remove(name) != 0)
{
printf("Error removing file %s\n%s\n", name, strerror(errno));
exit(-1);
}
if (rename(newname, name) != 0)
{
printf("Error renaming file %s\n%s\n", newname, strerror(errno));
exit(-1);
}
pcount--;
_exit(1);
}
}
else if (!df && !xf)
{
if (remove(name) != 0)
{
printf("Error removing file %s\n%s\n", name, strerror(errno));
exit(-1);
}
if (rename(newname, name) != 0)
{
printf("Error renaming file %s\n%s\n", newname, strerror(errno));
exit(-1);
}
}
}
Two suggestions:
Understand that exec*() replaces your process (if successful). Any code following it is unreachable.
Reap the exit status of exited processes with one of the wait*() functions; possibly in a signal handler for SIGCHLD.
Bonus suggestion: Read W. Richard Stevens' Advanced Programming in the Unix Environment; it's the Bible for tasks like these.
You probably want an else clause after the fork to handle the parent process as well. Something like the following might work;
void cleanup (char * name, char * newname)
{
if (pf)
{
/* SNIP - unchanged */
if (patch_pid == 0) /* child */
{
FILE *pfp;
if ((pfp = fopen(patchname, "w")) == NULL)
{
printf("Error opening file \"%s\" for writing.\n%s\n",
patchname, strerror(errno));
exit(EXIT_FAILURE);
}
dup2(fileno(pfp), STDOUT_FILENO);
fclose(pfp);
execvp(diffargs[0], diffargs);
perror("execvp");
exit(EXIT_FAILURE);
}
else /* parent */
{
pid_t rc;
int stat;
free(patchname);
rc = waitpid(patch_pid, &stat, 0);
if (rc < 0)
{
perror("waitpid");
/* do something appropriate here */
}
else
{
/* if you care about your children */
if (WIFEXITED(stat))
{
printf("%d exited with status %d\n",
(int)rc, WEXITSTATUS(stat));
}
else if (WIFSIGNALED(stat))
{
printf("%d terminated because of signal %d\n",
(int)rc, WTERMSIG(stat));
}
else if (WIFSTOPPED(stat))
{
printf("%d was STOPPED with signal %d\n",
(int)rc, WSTOPSIG(stat));
}
}
pcount--;
if (remove(name) != 0)
{
printf("Error removing file %s\n%s\n", name, strerror(errno));
exit(EXIT_FAILURE);
}
if (rename(newname, name) != 0)
{
printf("Error renaming file %s\n%s\n", newname, strerror(errno));
exit(EXIT_FAILURE);
}
}
}
}
I also high recommend reading Advanced Programming in the UNIX Environment by the late W. Richard Stevens. Actually, you should add UNIX Network Programming volume 1 & 2 to your list as well ;)
The other thing that I noticed is that you have a memory management error related to patchname. The malloc call isn't accounting for the extra NUL character at the end of the string.

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.

shell file redirection

I'm writing a shell script for class in C and I got my program to execute all commands except for file redirection. Can someone help figure out how I need to go about file redirection, I know I need to do it when fork returns 0, but I'm not sure how to do it.
Any help would be appreciated.
CODE:
Here is an updated version of my code. Creates the file fine but then i'm not sure what to do after. I close the correct file descriptors I think then duplicate my file descriptor to the lowest open descriptor. after dup besides closing the correct file descriptor can you help me what to do?
args[] is the array that i store the input (i.e. ls -l, ls -l > out)
if ((cpid = fork()) == 0)
{
for ( i = 0; i < size; i++)
{
if ( *args[i] == '<' )
{
if ((fd = open(args[i+1], O_RDONLY)) == -1)
{
perror("open");
exit(2);
}
else
{
close(0);
dup(fd);
close(fd);
}
}
if ( *args[i] == '>' )
{
if ((fd = open(args[i+1], O_WRONLY | O_CREAT, 0644)) == -1)
{
perror("open");
exit(2);
}
else
{
close(1);
dup(fd);
close(fd);
}
}
}
execve(path,args,environ);
perror("execve");
exit(2);
}
Check out the dup function here

Resources