运行 bash 使用 posix 而不是 fork/execv

Running bash using posix instead of fork/execv

我有一个 CLI,其中一个命令是进入 Linux bash shell。 这是使用 fork & execv 执行此操作的代码:

if ((pid = fork()) < 0) {
    syslog_debug(LOG_ERR, "Could not fork");
}
if (pid == 0) {
    /* In child, open the child's side of the tty.  */
    int i;
    for(i = 0; i <= maxfd; i++)
    {   
        close(i);
    }       
    /* make new process group */
    setsid();
    if ((fd[0] = open(tty_name, O_RDWR /*| O_NOCTTY*/)) < 0) {
        syslog_debug(LOG_ERR, "Could not open tty");
        exit(1);
    }
    fd[1] = dup(0);
    fd[2] = dup(0);
    /* exec shell, with correct argv and env */
    execv("/bin/sh", (char *const *)argv_init);
    exit(1);
}

我想替换 fork/execv 并改用 posix_spawn:

ret = posix_spawn_file_actions_init(&action);
pipe(fd);
for(i = 0; i <= maxfd; i++)
{   
   ret = posix_spawn_file_actions_addclose (&action, i);
}    
ret = posix_spawn_file_actions_adddup2 (&action, fd[1], 0);
ret = posix_spawn_file_actions_adddup2 (&action, fd[2], 0); 
ret = posix_spawn_file_actions_addopen (&action, STDOUT_FILENO, tty_name, O_RDWR, 0);
char* argv[] = { "/bin/sh", "-c", "bash", NULL };
int status;
extern char **environ;
posix_spawnattr_t attr = { 0 };

snprintf( cmd, sizeof(cmd), "bash");
status = posix_spawn( &pid,
                    argv[0],
                    action /*__file_actions*/,
                    &attr,
                    argv,
                    environ );
posix_spawn_file_actions_destroy(&action);

但是没用。 有帮助吗?

让我们看看原始fork/exec中的代码是做什么的:

  • 关闭所有文件描述符
  • 打开tty,会巧合得到0的fd,即stdin
  • 复制此 fd 两次 (dup(0)),将它们放入 stdout 和 stderr
  • 执行shell命令

您的新代码根本不遵循相同的模式。你想要做的是重复这个过程,但更明确:

关闭所有 FD:

for(i = 0; i <= maxfd; i++)
{
   ret = posix_spawn_file_actions_addclose (&action, i);
}

打开 tty 进入 STDIN_FILENO:

ret = posix_spawn_file_actions_addopen (&action, STDIN_FILENO, tty_name, O_RDWR, 0);

STDIN_FILENO 复制到 STDOUT_FILENOSTDERR_FILENO:

ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDOUT_FILENO);
ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDERR_FILENO);

那么 posix_spawn 应该发生在正确的上下文中。

对于旧的 fork/exec 流程,您应该执行如下操作:

int fd = open(tty_name, O_RDWR /*| O_NOCTTY*/);
if (fd != STDIN_FILENO) dup2(fd, STDIN_FILENO);
if (fd != STDOUT_FILENO) dup2(fd, STDOUT_FILENO);
if (fd != STDERR_FILENO) dup2(fd, STDERR_FILENO);

它的意图更明确。

The reason for the ifs is to prevent the accidental dup2 of the original fd into the new fd number. The only one that really should have that problem is the first one because fd == STDIN_FILENO because you don't have any other file descriptors open at that point.

为了将其组合成一小段代码,使用 echo something 而不是调用 bash 我们有:

#include <stdio.h>
#include <spawn.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

void
do_spawn()
{
  int ret;
  posix_spawn_file_actions_t action;
  int i;
  pid_t pid;
  int maxfd = 1024;
  char *tty_name = ttyname (0);

  ret = posix_spawn_file_actions_init (&action);
  for (i = 0; i <= maxfd; i++) {
      ret = posix_spawn_file_actions_addclose (&action, i);
  }
  ret = posix_spawn_file_actions_addopen (&action, STDIN_FILENO, tty_name, O_RDWR, 0);
  ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDOUT_FILENO);
  ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDERR_FILENO);
  char *argv[] = { "/bin/sh", "-c", "echo something", NULL };
  int status;
  extern char **environ;
  posix_spawnattr_t attr = { 0 };

  posix_spawnattr_init(&attr);
  posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
  status = posix_spawn(&pid, argv[0], &action /*__file_actions*/ , &attr, argv, environ);
  printf ("%d %ld\n", status, pid);

  wait (0);

  posix_spawn_file_actions_destroy (&action);

}

int
main(int argc, char **argv)
{
  do_spawn();
}

这需要用 -D_GNU_SOURCE 编译,否则 POSIX_SPAWN_USEVFORK 未定义。