Files
vlibc/src/stdio/popen.c
T

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