1

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());
        }
    }
}
16
  • Note that you don't need to test the return value from execvp() (or any other exec*() function). If the function is successful, it does not return; if it returns, it failed. Commented Apr 18, 2019 at 5:59
  • What does the a.out program do with its standard input? It's a bit surprising that you redirect the file input to the standard input of what becomes the a.out process. You don't print an error if the comp.exe process fails. Until you know what's wrong, that should be printed — and probably even after you know what's up. Commented Apr 18, 2019 at 6:06
  • You should also fclose(fp) and fclose(results) before the first execvp. Also, should flush results before each fork, or after the fprintf and before the next fork Commented Apr 18, 2019 at 6:11
  • @JonathanLeffler the a.out is reading from the stdin, so I redirected it to my input file. Commented Apr 18, 2019 at 8:19
  • 1
    @ovedmani what I mean is that you should close it in the child only, after flushing it before the fork. Closing in the child doesn't close it in the parent Commented Apr 18, 2019 at 8:23

1 Answer 1

2

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.