2

I'm writing a small program with system calls which parses an input file that contains commands and its parameters in each line, "benchmarks" them by outputting their elapsed time and waits for them to run one by one or executes them in parallel (basically, whether to wait() the spawned children or not in a loop)

The problem is that I'm required to use gets(), which I understand is deprecated and extremely dangerous, but still is going to be a requirement for my CS exams (sigh).

The way I'm doing this is by parsing each line with gets() and strtok() with the file redirected to stdin.

This seems to work fine if the last parameter is p, that is, if the children are to be run in parallel by not doing a wait() on each loop of parsing a new line and spawning the children.

BUT, if the last parameter is s (or anything else), gets() seems to strangely read duplicate lines from the file, and it will spawn twice the children. I have absolutely no clue on why and how is this happening.

As an example, if the input file contains the stuff below and the last parameter is p:

Input file:

ls -l
ps
ps -lu user

Output file:
Comm 1: ls
Elapsed time: 0000.001824
Comm 2: ps
Elapsed time: 0000.014160
Comm 3: ps
Elapsed time: 0000.015795

But, if the input file contains the same thing and the last parameter is s:

Input file:

ls -l
ps
ps -lu user

Output file:
Comm 1: ls
Elapsed time: 0000.001612
Comm 2: ps
Elapsed time: 0000.008564
Comm 3: ps
Elapsed time: 0000.009006
Comm 4: ps
Elapsed time: 0000.007379
Comm 5: ps
Elapsed time: 0000.008407
Comm 6: ps
Elapsed time: 0000.008975
// ./prog inputFile logFile p
// inputFile: file with a series of lines of max. 200 chars which consist of:
//  command p1 p2 ... pn (between 0 and 30 parameters)

// logFile: where the times are going to be logged
//  For each command parsed and executed:
//      command <i>: <name of the program executed>
//      Elapsed time: xxxx.yyyyyy

// p is either "s" or "p"
//  If it's "s", the commands must be executed in series
//  If it's "p", the commands must be executed in parallel

#include <unistd.h> // exec, read
#include <fcntl.h>  // open
#include <stdio.h>  // printf
#include <stdlib.h> // exit
#include <string.h> // strtok
#include <time.h>   // timeval
#include <sys/wait.h> 

int main(int argc, char *argv[]) {
    if (argc < 4) {
        printf("Usage: ./prog inputFile logFile p\n"); exit(0);
    }
    
    int fdCom = open(argv[1], O_RDONLY); // opens the input file with the instructions in it
    
    int fdLog = creat(argv[2], 0600);   // creates the logFile
    
    int fd[2]; pipe(fd); // pipe son->father
    
    close(0);dup(fdCom); // place the inputFile into stdin in order to use gets() on it
    close(fdCom);
    int lineNumber = 0;
    char buf[200];      // max length of a line
    char *com[21];      // command + max number of parameter
    
    while (gets(buf) != NULL) { // While there's still lines left in the input file...
        lineNumber++;
        int i = 0;
        com[i++] = strtok(buf, " "); // Parse the command
        while ((com[i] = strtok(NULL, " ")) != NULL) { // And its parameters
            i++;
        }
        
        printf("Leftover buf is: %s\n", buf);                          
        printf("The first three parsed parts are:%s %s %s\n", com[0], com[1], com[2]);
        
        if (fork() == 0) {  // child
            if (fork() == 0) { // create another child in charge of running the current command
                close(fd[0]);close(fd[1]);
                execvp(com[0], com);
            } else {    // wait for it to finish
                close(1); dup(fd[1]); // stdout to pipe output
                close(fd[0]);close(fd[1]);
                
                struct timeval initialTime, elapsedTime;
                gettimeofday(&initialTime, NULL);
                wait(NULL);
                gettimeofday(&elapsedTime, NULL);
                elapsedTime.tv_sec = elapsedTime.tv_sec - initialTime.tv_sec;
                elapsedTime.tv_usec = elapsedTime.tv_usec - initialTime.tv_usec ;
                
                if(elapsedTime.tv_usec < 0){
                    elapsedTime.tv_usec = elapsedTime.tv_usec + 100000;
                    elapsedTime.tv_sec--;
                }
                
                printf("Comm %d: %s\nElapsed time: %04d.%06d\n",lineNumber, com[0], elapsedTime.tv_sec, elapsedTime.tv_usec); // Va por pipe a padre
                fprintf(stderr, "Comm %d: %s\nElapsed time: %04d.%06d\n",lineNumber, com[0], elapsedTime.tv_sec, elapsedTime.tv_usec); // Va por pipe a padre
                exit(0);
            }
        } else {    // Father
            if (argv[3][0] == 's') { // If they indicated "s" as the last parameter, we have to wait for the current program until we execute another command
                printf("Waiting...\n");
                wait(NULL);
            } // Else, continue launching new ones
        }  
    }
    
    close(fd[1]); // Close the pipe's output 
    
    int readBytes;
    
    while((readBytes = read(fd[0], buf, sizeof(buf))) > 0) { // Writes everything that comes from the pipe into the logFile
        write(fdLog, buf, readBytes);
    }
    
    close(fd[0]); // Close the pipe's output
    
    // once everything is fixed, handle waiting for children here in case it was a parallel run

    exit(0);
}
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Lightsong
  • 312
  • 2
  • 8
  • Wow that's obscure and _undefined_ behavior! Does this answer your question? [C file pointer changing after fork and (failed) exec](https://stackoverflow.com/questions/50914178/c-file-pointer-changing-after-fork-and-failed-exec) Basically, you need to `fflush(stdin)` before calling `fork()` (for me, calling `ftell(stdin)` after the `gets()` also avoids this problem). – pilcrow Jan 26 '21 at 13:08
  • May I also recommend a [code review](https://codereview.stackexchange.com/)? You have a few dubious practices above — missing `sys/time.h`, playing with file handles as both "raw" descriptors and as `stdio` constructs, e.g., — that could be improved. – pilcrow Jan 26 '21 at 13:12
  • @pilcrow Wow, that was weird...Thank you very much! Indeed the code is rather ugly - this is for a course on "beginner" C and UNIX system calls where they only allow us to use a very specific set of library functions to mix with other syscalls (as in the case of `gets()`), and the odd library function here and there such as `gettimeofday` is provided alongside the needed header as part of the problem. So, this is basically done in what you could call blind mode and as such I didn't add the missing headers on purpose. – Lightsong Jan 26 '21 at 13:35

0 Answers0