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);
Related
For my piping function, I am creating two child processes with fork() and attempting to pipe the contents of the first over to the second child process before calling execvp(). I am doing something wrong, as if I do ls -la | more, the output is a vertical list of the files in the directory. I am very new to piping in c (and c) so chances are that it is an overlooked mistake. Any help is appreciated!
void handle_pipe_cmds(char **args1, char **args2){
char ** word1 = parse_space(*args1);
char ** word2 = parse_space(*args2);
int p[2];
pipe(p);
pid_t pid = fork();
if (pid == -1) {
printf("CANNOT FORK!");
} else if (pid == 0) {
dup2(p[1], STDOUT_FILENO);
close(p[1]);
int status = execvp(word1[0], word1);
if (status != 0) {
printf("%s failed\n", word1[0]);
exit(1);
}
exit(0);
} else {
close(p[1]);
wait(NULL);
}
pid = fork();
if (pid == -1) {
printf("CANNOT FORK!");
} else if (pid == 0) {
dup2(p[0], STDIN_FILENO);
close(p[1]);
close(p[0]);
int status = execvp(word2[0], word2);
if (status != 0) {
printf("%s failed\n", word2[0]);
exit(1);
}
exit(0);
} else {
close(p[1]);
close(p[0]);
wait(NULL);
}
}
If I do ls -la | more, the output is a vertical list of the files in the directory.
That is because the ls program is aware of whether its output stream is a terminal or not, and acts different in both cases. On a terminal, it (may) print colorized entries, and multiple items per line; to a pipe, or a file, it will print just one entry per line with no color. See man ls for details.
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.
I am trying to read specific lines from a file using a parent process to read one line and a child process to read some other line. The simple text file has as contents: "This is line one\n", This is line two\n", etc.
However, I find that when I execute my program multiple times, my child does not always print the line from my file. The printf("Entering child\n") does always get executed, indicating that I am able to enter the child process fine.
Why does this work only sometimes?
int main() {
FILE* fptr = fopen("my_text.txt","r");
int stat;
pid_t pid;
int count = 0;
if(fptr != NULL) {
char line[256];
fflush(stdout);
if((pid = fork()) < 0) {
fprintf(stderr, "fork error: %s\n",strerror(errno));
}
else if(pid == 0) {
printf("entering child\n");
int lineNumber = 2; // read the second line
while (fgets(line, sizeof(line), fptr) != NULL) { // loop until we get to desired line
if (count == lineNumber) {
printf("CHILD read: %s", line);
fflush(stdout);
rewind(fptr);
break;
} else {
count++;
}
}
exit(0); // exit child process
} else { // parent process
int lineNumber = 1;
while (fgets(line, sizeof(line), fptr) != NULL) { // loop until desired line
if (count == lineNumber) {
printf("PARENT read: %s", line);
fflush(stdout);
rewind(fptr);
break;
} else {
count++;
}
}
wait(&stat); // reap the child process
}
fclose(fptr);
} else {
printf("File Does not exist\n");
exit(0);
}
return 0; }
From the above code, I sometimes print "This is line two" (from parent) and "This is line three" (from child), or sometimes only "This is line two". The goal is to get both to print.
The two processes share the open file description, which means that they both share the file offset
and therefore you have a race condition because they read the file concurrently. There are two ways obvious to fix:
use some IPC mechanism for synchronization, or file description locks - see for example flock.
open the file in each process after the fork.
Other advanced methods would e.g. be to read the file with pread, or mmap it before or after the fork...
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.
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.