Search code examples
cforkfgetsexecve

execve fails - ignores exit statement


I have a weird problem when mixing fgets and execve together.

I have a function that reads lines from a file, and executes them, either by parsing them through it's own functions, or using execve. For the sake of simplicity, my test file only has external functions:

echo whoowhee
lx
ls
dir

The second command, lx does not exist, so at that point execve should fail.

Here's the code where I am reading from the file:

  while(fgets(line, 1024, testfile) != NULL){
    if(strlen(line) < 3){continue;}
    if(line[0] == '#'){continue;}

    printf("%s%s", value("PROMPT"), line); // Emulates the prompt

    //Terminates line at right place to simulate input
    line[strlen(line)-2] = '\0';

    execute(line);

    Var *prompt = retrieveVar("PROMPT");
    sprintf(prompt->value, "< Executing script... - [%s] > $ ", value("EXITCODE"));

    lineNo++; 
  }

And here is my fork-exec block:

  if(pid == 0){ // Child

    // For loop for using all paths
    for(int i = 0; i < pathn; i++){
      args[0] = paths[i];

      // Executes command path[i] with arguments args with environment envp
      execve(paths[i], args, envp);
    }

    perror("execve");
    exit(0);
  }
  else if(pid > 0){ //Parent
    current_pid = pid;

    if(setpgid(pid, pid) != 0) perror("setpid");

    // Waits if background flag not activated.
    if(BG == 0){
      // WUNTRACED used to stop waiting when suspended
      waitpid(current_pid, &status, WUNTRACED);

        if(WIFEXITED(status)){
          setExitcode(WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status)){
          printf("Process received SIGNAL %d\n", WTERMSIG(status));
        }
    }
  }
  else{
    perror("fork()");
  }

Since execve requires absolute paths to the program, paths is an array of possible paths the program should exist in. BG states whether the process should execute in the background, value("EXITCODE") is the exitcode of the process, and execute is the function that, you guessed it, executes the line.

Now, here is the output when I run the testfile, with the code as it is:

<Welcome to Eggshell> $ echo whoowhee
whoowhee
< Executing script... - [0] > $ lx
execve: No such file or directory
< Executing script... - [0] > $ ls
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt

As you can see, dir is run twice.

The problems do not stop here, if I add another meaningless command to the text file:

echo whoowhee
lx
onetimesoneistwo
ls
dir

If I try to run the function, it ends up stuck in an infinite loop. Not only that, but this time it keeps on running the whole file over and over again!

However, if I remove the exit from the fork-exec bit, and rerun the function, this is what comes out:

<Welcome to Eggshell> $ echo whoowhee
whoowhee
< Executing script... - [0] > $ lx
execve: No such file or directory
< Executing script... - [0] > $ onetimesoneistwo
execve: No such file or directory
< Executing script... - [0] > $ ls
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ ls
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ onetimesoneistwo
execve: No such file or directory
< Executing script... - [0] > $ ls
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ ls
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt
< Executing script... - [0] > $ dir
add-on  codecov.yml documentation  eggshell.c  LICENSE  Makefile        README.md  switch.sh      val.log
ci  createfile.txt  eggshell       eggshell.h  main.c   Makefile-Clang  src        testinput.txt

Surprisingly, it isn't an infinite loop! Sure every command after onetimesoneistwo runs 4 times, and onetimesoneistwo itself runs twice, but at least it's something.

I would guess that execve failing causes the forked child to never terminate, but then why would exit cause an infinite loop whereas not having an exit duplicate all instructions afterwards?

The funny story is that the same thing does not happen if I run the program normally, supplying the inputs myself rather than sourcing them from a file, so my guess is that something is severely wrong with either my source function, or my fork-exec code.


Solution

  • The solution, for me at least, seemed to be to use _exit instead of exit. Using the former killed the child immediately if execve failed, rather than having a weird infinite loop effect.

    Though I still have no idea why exit caused an infinite loop, and why _exit works where exit doesn't...

    UPDATE: That didn't work well. What fixed the problem is using fgets in a loop before the one present, and store all the lines in a string array buffer. Then, within the next loop, I simply read the lines from the array one by one. Since exit was resetting the file pointer, the solution was to no longer use the file pointer within the loop.