#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #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; } }