2

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?

AZur
  • 39
  • 6

1 Answers1

4

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 %ld\n", 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.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • 'ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDOUT_FILENO); ret = posix_spawn_file_actions_adddup2 (&action, STDIN_FILENO, STDOUT_FILENO);' shouldn't the second be STDERR_FILENO instead of STDOUT_FILENO? – AZur Aug 11 '16 at 13:17
  • oops, typo, the second line should have been `STDERR_FILENO`, not `STDOUT_FILENO` – Anya Shenanigans Aug 11 '16 at 13:19
  • Thanks, It's clarify some issues. but, it's look i still have problem with posix_spawn(). – AZur Aug 11 '16 at 13:44
  • I don't know why, but the above solution is working only incase I delete the 'for' loop of the 'posix_spawn_file_actions_addclose'. Incase the loop isn't deleted I see in the procees list: '[org_proc_name] ' and I don't see the bash prompt or the echo print. – AZur Aug 16 '16 at 10:08
  • My code there is using `ttyname(0)` to get the ttyname. If that doesn't work, then you'll not see the output from the command - that could be the issue (I don't know, I'm just making educated guesses here). the `` may indicate a crash in the runner, triggered by `ttyname(0)` returning `NULL`. Unfortunately I don't know enough about your execution environment to make any other guesses. – Anya Shenanigans Aug 16 '16 at 17:38