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.
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.