Skip to content
Advertisement

Running bash using posix instead of fork/execv

I have a CLI, one of the commands is entering into Linux bash shell. This is the code which does it using 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);
}

I want to replace the fork/execv and to use posix_spawn instead:

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);

But it doesn’t work. Any help?

Advertisement

Answer

Let’s look at what the code in the original fork/exec does:

  • close all file descriptors
  • open the tty, which will coincidentally get an fd of 0, i.e. it’s stdin
  • duplicate this fd twice (dup(0)), which puts them into stdout and stderr
  • exec the shell command

Your new code doesn’t follow the same pattern at all. What you want to do is repeat the process, but be more explicit:

close all the FDs:

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

open the tty into STDIN_FILENO:

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

duplicate the STDIN_FILENO into STDOUT_FILENO and STDERR_FILENO:

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

Then the posix_spawn should take place in the correct context.

For the old fork/exec process, you should have done something like:

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);

It’s more explicit in intent.

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.

To combine this into a small piece of code, with echo something rather than an invocation of bash we have:

#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 %ldn", status, pid);

  wait (0);

  posix_spawn_file_actions_destroy (&action);

}

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

This needs to be compiled with -D_GNU_SOURCE, as otherwise POSIX_SPAWN_USEVFORK is not defined.

Advertisement