【发布时间】:2018-10-22 19:25:09
【问题描述】:
将fgets 和execve 混合在一起时遇到一个奇怪的问题。
我有一个从文件中读取行并执行它们的函数,或者通过它自己的函数解析它们,或者使用execve。为了简单起见,我的测试文件只有外部函数:
echo whoowhee
lx
ls
dir
第二个命令lx 不存在,所以此时execve 应该会失败。
这是我从文件中读取的代码:
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++;
}
这是我的 fork-exec 块:
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()");
}
由于execve 需要程序的绝对路径,paths 是程序应该存在的可能路径数组。BG 表示进程是否应该在后台执行,value("EXITCODE") 是程序的退出代码进程,execute 是执行该行的函数。
现在,这是我运行测试文件时的输出,代码原样:
<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
如您所见,dir 运行了两次。
问题不止于此,如果我在文本文件中添加另一个无意义的命令:
echo whoowhee
lx
onetimesoneistwo
ls
dir
如果我尝试运行该函数,它最终会陷入无限循环。不仅如此,这一次它还会一遍又一遍地运行整个文件!
但是,如果我从fork-exec 位中删除exit,然后重新运行该函数,就会出现这样的结果:
<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
令人惊讶的是,它不是无限循环!当然onetimesoneistwo 之后的每个命令都运行了 4 次,onetimesoneistwo 本身也运行了两次,但至少它是一些东西。
我猜execve 失败会导致分叉子进程永远不会终止,但是为什么exit 会导致无限循环,而之后没有exit 重复所有指令呢?
有趣的故事是,如果我正常运行程序,自己提供输入而不是从文件中获取输入,则不会发生同样的事情,所以我的猜测是我的源函数或我的fork-exec 代码。
【问题讨论】:
-
用
kill(SIGINT, getpid())替换exit似乎停止了无限循环,但下一行仍然运行两次。然而,之后的每一行只运行一次。似乎是一种情况的破解,同时也没有解决整个问题...... -
paths数组究竟包含什么?它是当前PATH环境变量上的目录名称列表吗?如果是这样,您在尝试execve()之前是否需要将paths[i]和命令名称格式化为args[0]? -
execve适用于实际命令。它包含来自PATH的所有目录名称的列表,并附有命令名称。因此,如果命令是ls,则来自PATH变量的所有路径都添加了/ls。 -
这是一种奇怪的方法——创建大量格式化的字符串,即使其中只有一个有用。大多数人仅在必要时才安排进行连接,而不仅仅是以防万一。有用;这不是显而易见的方法。您应该确保
args[1]也是一个空指针(假设命令没有参数;命令的最后一个参数之后必须有一个空指针)。 -
老实说,我事先一次性完成了所有操作,以便可以将它们全部组合到一个函数中。因此,当我进入实际的执行块时,我会减少不相关的代码使其复杂化。至于空指针,这是有道理的。我不确定它是否相同,但如果没有参数,
args[1]甚至根本没有初始化。调试器说是0x0,所以我猜是空指针?