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 originalfd
into the new fd number. The only one that really should have that problem is the first one becausefd
==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.