什么会导致exec失败?接下来发生什么?

时间:2010-09-13 17:51:18

标签: c linux unix exec

exec(execl,execlp等)失败的原因是什么?如果您调用exec并返回,除了恐慌和调用exit之外,还有其他最佳实践吗?

5 个答案:

答案 0 :(得分:34)

处理exec失败的问题在于,通常在子进程中执行exec,并且您希望在父进程中执行错误处理。但是你不能只exit(errno),因为(1)你不知道错误代码是否适合退出代码,(2),你无法区分失败到exec和失败退出来自新计划的代码exec

我所知道的最佳解决方案是使用管道来传达exec

的成功或失败
  1. 在分叉之前,在父进程中打开一个管道。
  2. 分叉后,父级关闭管道的写入端并从读取端读取。
  3. 孩子关闭阅读结束并为写作结束设置close-on-exec标志。
  4. 孩子打电话给exec。
  5. 如果exec失败,孩子会使用管道将错误代码写回父级,然后退出。
  6. 如果子项成功执行exec,则父级读取eof(零长度读取),因为close-on-exec使成功exec关闭管道的写入端。或者,如果exec失败,则父级会读取错误代码并可以相应地继续。无论哪种方式,父母都会阻止,直到孩子打电话给exec
  7. 父级关闭管道的读取端。

答案 1 :(得分:19)

来自exec(3) man page

  

execl()execle()execlp()execvp()execvP()函数可能会失败,并为为库函数指定的任何错误设置errno execve(2)malloc(3)

     

execv()函数可能会失败,并为库函数execve(2)指定的任何错误设置errno。

然后从execve(2) man page

  

<强>错误

     如果出现以下情况,

Execve()将失败并返回调用进程:

     
      
  • [E2BIG] - 新进程参数列表中的字节数大于系统强加的限制。此限制由sysctl(3) MIB变量KERN_ARGMAX指定。
  •   
  • [EACCES] - 对路径前缀的组件拒绝搜索权限。
  •   
  • [EACCES] - 新的处理文件不是普通文件。
  •   
  • [EACCES] - 新的进程文件模式拒绝执行权限。
  •   
  • [EACCES] - 新的流程文件位于已禁用执行的文件系统上(MNT_NOEXEC中的<sys/mount.h>)。
  •   
  • [EFAULT] - 新处理文件的长度不会超过其标题中的尺寸值。
  •   
  • [EFAULT] - Path,argv或envp指向非法地址。
  •   
  • [EIO] - 从文件系统读取时发生I / O错误。
  •   
  • [ELOOP] - 在翻译路径名时遇到了太多符号链接。这被视为表示循环符号链接。
  •   
  • [ENAMETOOLONG] - 路径名的组件超出{NAME_MAX}个字符,或整个路径名超过{PATH_MAX}个字符。
  •   
  • [ENOENT] - 新的流程文件不存在。
  •   
  • [ENOEXEC] - 新的处理文件具有相应的访问权限,但格式无法识别(例如,标题中包含无效的幻数)。
  •   
  • [ENOMEM] - 新进程需要的虚拟内存超过强加的最大值(getrlimit(2))所允许的数量。
  •   
  • [ENOTDIR] - 路径前缀的组件不是目录。
  •   
  • [ETXTBSY] - 新的流程文件是一个纯过程(共享文本)文件,目前可供某些流程写入或读取。
  •   

malloc()不那么复杂,仅使用ENOMEM。来自malloc(3) man page

  

如果成功,calloc()malloc()realloc()reallocf()valloc()函数会返回指向已分配内存的指针。如果出现错误,则会返回NULL指针并将errno设置为ENOMEM

答案 2 :(得分:8)

exec()调用返回后执行的操作取决于上下文 - 程序应该执行的操作,错误是什么以及您可以采取哪些措施来解决此问题。

一个麻烦来源可能是您指定了一个简单的程序名而不是路径名;也许您可以使用execvp()重试,或将命令转换为sh -c 'what you originally specified'的调用。这些是否合理取决于应用。如果涉及重大安全问题,您可能不会再试一次。

如果您指定了路径名并且存在问题(ENOTDIR,ENOENT,EPERM),那么您可能没有任何明智的回退,但您可以有意义地报告错误。

在过去(10多年前),有些系统不支持'#!' shebang表示法,如果您不确定是否正在执行可执行文件或shell脚本,则将其作为可执行文件进行尝试,然后将其作为shell脚本重试。如果您运行Perl脚本,那可能会或可能不会起作用,但在那些日子里,您编写了Perl脚本以检测它们是由shell运行并使用Perl重新执行。幸运的是,那些日子已经结束了。

在可能的范围内,确保进程报告问题以便跟踪问题非常重要 - 将其消息写入日志文件或仅写入stderr(甚至可能syslog()),以便那些必须解决出错的人有更多的信息来帮助他们,除了倒霉的最终用户的报告“我试过X但它没有用”。至关重要的是,如果没有任何作用,则退出状态不为0,因为这表示成功。即使这可能会被忽略 - 但你做了你能做的事。

答案 3 :(得分:3)

不仅仅是恐慌,你可以根据errno的价值做出决定。

答案 4 :(得分:0)

Exec应该永远成功。 (shell除外,即如果用户输入了伪造的命令)

如果exec失败,则表示:

  • 程序的“错误”(组件丢失或错误,路径名错误,内存不良......)或
  • 严重的系统错误(内存不足,进程太多,磁盘故障......)

对于任何严重错误,通常的方法是在stderr上写入错误消息,然后使用失败代码退出。几乎所有的标准工具都可以做到这一点。对于exec:

execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);

shell也会这样做(或多或少)。

通常,如果子进程失败,父进程也会失败并应该退出。无论孩子是在执行失败还是在运行程序时都没有关系。如果exec失败,exec失败的原因并不重要。如果子进程因任何原因失败,则调用进程出现问题并需要停止。

不要浪费大量时间来预测所有可能的错误情况。不要编写试图以最佳方式处理每个错误代码的代码。你只会膨胀代码,并引入许多新的bug。如果您的程序被破坏,或者它被滥用,它应该会失败。如果你强迫它继续下去,那就会出现更糟的麻烦。

例如,如果系统内存不足并且交换掉,我们不想一遍又一遍地试图运行一个进程;这只会使情况变得更糟。如果我们收到文件系统错误,我们不希望继续在该文件系统上运行;它可能会使腐败变得更糟。如果程序安装错误,或有错误或内存损坏,我们希望尽快停止,然后破坏的程序会造成一些真正的损害(例如向客户端发送损坏的报告,破坏数据库,。 ..)。

一种可能的替代方法:失败的进程可能会寻求帮助,暂停自己(SIGSTOP),然后在被告知继续时重试该操作。当系统内存不足或磁盘已满时,或者即使程序出现故障,这也可能有所帮助。很少有操作是如此昂贵和重要,以至于这是值得的。

如果您正在制作交互式GUI程序,请尝试将其作为可重复使用的命令行工具(如果出现问题则退出)的瘦包装器。程序中的每个函数都应该可以通过GUI,命令行和函数调用访问。写你的功能。编写一些工具来为任何函数创建commmand-line和GUI包装器。也使用子流程。

如果您正在制造一个真正关键的系统,例如核电站的控制器或预测海啸的程序,那么您在阅读我的愚蠢建议是什么?关键系统不应完全依赖于计算机或软件。需要有一个“手动覆盖”,有人来驱动它。特别是,不要试图在MS Windows上建立一个关键系统,就像在水下建造沙堡一样。