feat(process): fork/exec/id/session + system/popen
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "stdio_impl.h"
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
/*
|
||||
* popen/pclose (POSIX base, todo 20): run "sh -c cmd" with a pipe
|
||||
* attached to its stdin ("w") or stdout ("r").
|
||||
*
|
||||
* The stream is a todo-15 FILE built on the FILE core. popen does NOT go
|
||||
* through the public fdopen(): todo 15's fdopen validates the requested
|
||||
* mode against the descriptor's F_GETFL access mode by masking with
|
||||
* O_RDWR, so a plain O_WRONLY descriptor is misread as O_RDONLY and a
|
||||
* "w" fdopen is rejected — and the pipe write end is exactly that case
|
||||
* (F_SETFL cannot promote a pipe end to O_RDWR). popen knows the access
|
||||
* mode of its own freshly created pipe end, so it goes straight to the
|
||||
* FILE core: stdio_parse_mode + stdio_alloc_file. Only the POSIX modes
|
||||
* "r" and "w" are accepted; the glibc "re"/"we" close-on-exec extension
|
||||
* is not. There is no sys/wait.h yet (todo 23): pclose waits via the
|
||||
* raw SYS_wait4, and the child exits via the raw SYS_exit_group when
|
||||
* the exec fails.
|
||||
*
|
||||
* Registry: a static table mapping FILE* to the shell pid. The todo-15
|
||||
* FILE struct has no spare field for the pid, so pclose looks the
|
||||
* stream up here. FOPEN_MAX (16) slots; a full table fails with EMFILE.
|
||||
* Not thread-safe — fine while there is no thread runtime (todo 45).
|
||||
*
|
||||
* pclose closes the stream first, then waits: for "w" streams the close
|
||||
* flushes the buffered writes and delivers EOF, letting the shell exit;
|
||||
* waiting first could deadlock on a full pipe. A stream not opened by
|
||||
* popen is still closed, and pclose returns -1 as POSIX requires. A wait
|
||||
* failure (ECHILD) returns -1 with errno set.
|
||||
*/
|
||||
|
||||
struct vlibc_popen_entry
|
||||
{
|
||||
FILE *stream;
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
static struct vlibc_popen_entry popen_table[FOPEN_MAX];
|
||||
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
FILE *
|
||||
popen(const char *command, const char *mode)
|
||||
{
|
||||
int fds[2] = {0}; /* written by SYS_pipe; the analyzer cannot model it */
|
||||
int parent_fd;
|
||||
int child_fd;
|
||||
int read_mode;
|
||||
int m;
|
||||
int oflags;
|
||||
pid_t pid;
|
||||
FILE *f;
|
||||
int i;
|
||||
|
||||
if (mode[0] == 'r' && mode[1] == '\0')
|
||||
{
|
||||
read_mode = 1;
|
||||
}
|
||||
else if (mode[0] == 'w' && mode[1] == '\0')
|
||||
{
|
||||
read_mode = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
(void)stdio_parse_mode(mode, &m, &oflags); /* "r"/"w" always parse */
|
||||
|
||||
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (read_mode)
|
||||
{
|
||||
parent_fd = fds[0]; /* parent reads the shell's stdout */
|
||||
child_fd = fds[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
parent_fd = fds[1]; /* parent writes the shell's stdin */
|
||||
child_fd = fds[0];
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0)
|
||||
{
|
||||
char *sh_argv[4];
|
||||
int target = read_mode ? 1 : 0;
|
||||
|
||||
/*
|
||||
* Child: wire the pipe end onto the shell's fd (0 for "w", 1
|
||||
* for "r"), close everything else. The guards cover the
|
||||
* (theoretical) case where the pipe handed us fd 0 or 1.
|
||||
*/
|
||||
if (parent_fd != target)
|
||||
{
|
||||
(void)__syscall1(SYS_close, parent_fd);
|
||||
}
|
||||
if (child_fd != target)
|
||||
{
|
||||
(void)__syscall2(SYS_dup2, child_fd, target);
|
||||
(void)__syscall1(SYS_close, child_fd);
|
||||
}
|
||||
sh_argv[0] = "sh";
|
||||
sh_argv[1] = "-c";
|
||||
sh_argv[2] = (char *)command;
|
||||
sh_argv[3] = 0;
|
||||
execve("/bin/sh", sh_argv, environ);
|
||||
__syscall1(SYS_exit_group, 127); /* exec failed */
|
||||
}
|
||||
|
||||
/* Parent. */
|
||||
(void)__syscall1(SYS_close, child_fd);
|
||||
if (pid < 0)
|
||||
{
|
||||
(void)__syscall1(SYS_close, parent_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
f = stdio_alloc_file(parent_fd, m);
|
||||
if (f == 0)
|
||||
{
|
||||
/* Drop the descriptor and reap the shell we just spawned. */
|
||||
(void)__syscall1(SYS_close, parent_fd);
|
||||
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < FOPEN_MAX; i++)
|
||||
{
|
||||
if (popen_table[i].stream == 0)
|
||||
{
|
||||
popen_table[i].stream = f;
|
||||
popen_table[i].pid = pid;
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Registry full: drop the stream and reap the shell. */
|
||||
(void)fclose(f);
|
||||
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
|
||||
errno = EMFILE;
|
||||
return 0;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
int
|
||||
pclose(FILE *stream)
|
||||
{
|
||||
pid_t pid = 0;
|
||||
int found = 0;
|
||||
int status = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FOPEN_MAX; i++)
|
||||
{
|
||||
if (popen_table[i].stream == stream)
|
||||
{
|
||||
pid = popen_table[i].pid;
|
||||
popen_table[i].stream = 0;
|
||||
popen_table[i].pid = 0;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(void)fclose(stream);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
/* Not a popen stream: POSIX wants -1. The stream is still
|
||||
* closed. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
|
||||
|
||||
if (r < 0 && -r == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
{
|
||||
return syscall_ret(r); /* -1 + errno (e.g. ECHILD) */
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user