202 lines
5.2 KiB
C
202 lines
5.2 KiB
C
#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;
|
|
}
|
|
}
|