mruby-io: refactor popen to use HAL functions

eliminates platform-specific popen implementations by using
mrb_io_hal_pipe and mrb_io_hal_spawn_process. removes io_cloexec_pipe,
io_pipe, and io_process_exec functions. io.pipe now also uses
mrb_io_hal_pipe. reduces platform conditionals and improves portability.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-10-15 14:17:27 +09:00
parent 74ca22f281
commit f4dcc3dc3d
+56 -225
View File
@@ -261,48 +261,6 @@ io_fd_cloexec(mrb_state *mrb, int fd)
#endif
}
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
static int
io_cloexec_pipe(mrb_state *mrb, int fildes[2])
{
int ret = pipe(fildes);
if (ret == -1)
return -1;
io_fd_cloexec(mrb, fildes[0]);
io_fd_cloexec(mrb, fildes[1]);
return ret;
}
static int
io_pipe(mrb_state *mrb, int pipes[2])
{
int ret = io_cloexec_pipe(mrb, pipes);
if (ret == -1) {
if (errno == EMFILE || errno == ENFILE) {
mrb_garbage_collect(mrb);
ret = io_cloexec_pipe(mrb, pipes);
}
}
return ret;
}
static int
io_process_exec(const char *pname)
{
const char *s = pname;
while (*s == ' ' || *s == '\t' || *s == '\n')
s++;
if (!*s) {
errno = ENOENT;
return -1;
}
execl("/bin/sh", "sh", "-c", pname, (char*)NULL);
return -1;
}
#endif
static void
io_free(mrb_state *mrb, void *ptr)
@@ -397,153 +355,92 @@ parse_popen_args(mrb_state *mrb, struct popen_params *p)
p->opt_err = option_to_fd(mrb, kv.opt_err);
}
#if defined(_WIN32)
static mrb_value
io_s_popen(mrb_state *mrb, mrb_value klass)
{
struct popen_params p;
p.klass = klass;
parse_popen_args(mrb, &p);
struct mrb_io *fptr;
int pid = 0;
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE ifd[2];
HANDLE ofd[2];
ifd[0] = INVALID_HANDLE_VALUE;
ifd[1] = INVALID_HANDLE_VALUE;
ofd[0] = INVALID_HANDLE_VALUE;
ofd[1] = INVALID_HANDLE_VALUE;
int pr[2] = { -1, -1 }; /* read pipe: parent reads, child writes */
int pw[2] = { -1, -1 }; /* write pipe: parent writes, child reads */
int readable, writable;
int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1;
mrb->c->ci->mid = 0;
parse_popen_args(mrb, &p);
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
readable = OPEN_READABLE_P(p.flags);
writable = OPEN_WRITABLE_P(p.flags);
if (OPEN_READABLE_P(p.flags)) {
if (!CreatePipe(&ofd[0], &ofd[1], &saAttr, 0)
|| !SetHandleInformation(ofd[0], HANDLE_FLAG_INHERIT, 0)) {
/* Create pipes for communication */
if (readable) {
if (mrb_io_hal_pipe(mrb, pr) == -1) {
mrb_sys_fail(mrb, "pipe");
}
}
if (OPEN_WRITABLE_P(p.flags)) {
if (!CreatePipe(&ifd[0], &ifd[1], &saAttr, 0)
|| !SetHandleInformation(ifd[1], HANDLE_FLAG_INHERIT, 0)) {
if (writable) {
if (mrb_io_hal_pipe(mrb, pw) == -1) {
if (pr[0] != -1) {
mrb_io_hal_close(mrb, pr[0]);
mrb_io_hal_close(mrb, pr[1]);
}
mrb_sys_fail(mrb, "pipe");
}
}
/* Set up child process file descriptors */
if (p.doexec) {
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.dwFlags |= STARTF_USESTDHANDLES;
if (OPEN_READABLE_P(p.flags)) {
si.hStdOutput = ofd[1];
si.hStdError = ofd[1];
}
if (OPEN_WRITABLE_P(p.flags)) {
si.hStdInput = ifd[0];
}
if (!CreateProcess(
NULL, (char*)p.cmd, NULL, NULL,
TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi)) {
CloseHandle(ifd[0]);
CloseHandle(ifd[1]);
CloseHandle(ofd[0]);
CloseHandle(ofd[1]);
/* Child stdin: either write pipe read end or opt_in */
stdin_fd = (p.opt_in != -1) ? p.opt_in : (writable ? pw[0] : -1);
/* Child stdout: either read pipe write end or opt_out */
stdout_fd = (p.opt_out != -1) ? p.opt_out : (readable ? pr[1] : -1);
/* Child stderr: opt_err or stdout */
stderr_fd = (p.opt_err != -1) ? p.opt_err : stdout_fd;
/* Spawn child process using HAL */
if (mrb_io_hal_spawn_process(mrb, p.cmd, stdin_fd, stdout_fd, stderr_fd, &pid) == -1) {
int saved_errno = errno;
if (readable) {
mrb_io_hal_close(mrb, pr[0]);
mrb_io_hal_close(mrb, pr[1]);
}
if (writable) {
mrb_io_hal_close(mrb, pw[0]);
mrb_io_hal_close(mrb, pw[1]);
}
errno = saved_errno;
mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", p.cmd);
}
CloseHandle(pi.hThread);
CloseHandle(ifd[0]);
CloseHandle(ofd[1]);
pid = pi.dwProcessId;
/* Close child ends of pipes in parent */
if (readable) {
mrb_io_hal_close(mrb, pr[1]); /* close write end */
}
if (writable) {
mrb_io_hal_close(mrb, pw[0]); /* close read end */
}
}
/* Set up parent IO object */
mrb_value io = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
fptr = io_alloc(mrb);
fptr->fd = _open_osfhandle((intptr_t)ofd[0], 0);
fptr->fd2 = _open_osfhandle((intptr_t)ifd[1], 0);
fptr->pid = pid;
fptr->readable = OPEN_READABLE_P(p.flags);
fptr->writable = OPEN_WRITABLE_P(p.flags);
io_init_buf(mrb, fptr);
DATA_TYPE(io) = &mrb_io_type;
DATA_PTR(io) = fptr;
return io;
}
#else
static void
popen_child_setup(mrb_state *mrb, int readable, int writable, int *pr, int *pw, struct popen_params *p)
{
if (p->opt_in != -1) {
dup2(p->opt_in, 0);
}
if (p->opt_out != -1) {
dup2(p->opt_out, 1);
}
if (p->opt_err != -1) {
dup2(p->opt_err, 2);
}
if (readable) {
close(pr[0]);
if (pr[1] != 1) {
dup2(pr[1], 1);
close(pr[1]);
}
}
if (writable) {
close(pw[1]);
if (pw[0] != 0) {
dup2(pw[0], 0);
close(pw[0]);
}
}
if (p->doexec) {
for (int fd = 3; fd < NOFILE; fd++) {
close(fd);
}
io_process_exec(p->cmd);
mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", p->cmd);
_exit(127);
}
}
static mrb_value
popen_parent_setup(mrb_state *mrb, int readable, int writable, int *pr, int *pw, int pid, struct popen_params *p)
{
int fd, write_fd = -1;
struct mrb_io *fptr = io_alloc(mrb);
if (readable && writable) {
close(pr[1]);
fd = pr[0];
close(pw[0]);
write_fd = pw[1];
fptr->fd = pr[0]; /* parent reads from here */
fptr->fd2 = pw[1]; /* parent writes to here */
}
else if (readable) {
close(pr[1]);
fd = pr[0];
fptr->fd = pr[0]; /* parent reads from here */
fptr->fd2 = -1;
}
else {
close(pw[0]);
fd = pw[1];
fptr->fd = pw[1]; /* parent writes to here */
fptr->fd2 = -1;
}
mrb_value io = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(p->klass), NULL, &mrb_io_type));
struct mrb_io *fptr = io_alloc(mrb);
fptr->fd = fd;
fptr->fd2 = write_fd;
fptr->pid = pid;
fptr->readable = readable;
fptr->writable = writable;
@@ -553,73 +450,7 @@ popen_parent_setup(mrb_state *mrb, int readable, int writable, int *pr, int *pw,
DATA_PTR(io) = fptr;
return io;
}
static mrb_value
io_s_popen(mrb_state *mrb, mrb_value klass)
{
struct popen_params p;
p.klass = klass;
int pid;
int pr[2] = { -1, -1 };
int pw[2] = { -1, -1 };
mrb->c->ci->mid = 0;
parse_popen_args(mrb, &p);
int readable = OPEN_READABLE_P(p.flags);
int writable = OPEN_WRITABLE_P(p.flags);
if (readable) {
if (pipe(pr) == -1) {
mrb_sys_fail(mrb, "pipe");
}
io_fd_cloexec(mrb, pr[0]);
io_fd_cloexec(mrb, pr[1]);
}
if (writable) {
if (pipe(pw) == -1) {
if (pr[0] != -1) close(pr[0]);
if (pr[1] != -1) close(pr[1]);
mrb_sys_fail(mrb, "pipe");
}
io_fd_cloexec(mrb, pw[0]);
io_fd_cloexec(mrb, pw[1]);
}
if (!p.doexec) {
fflush(stdout);
fflush(stderr);
}
switch (pid = fork()) {
case 0: /* child */
popen_child_setup(mrb, readable, writable, pr, pw, &p);
return mrb_nil_value();
default: /* parent */
return popen_parent_setup(mrb, readable, writable, pr, pw, pid, &p);
case -1: /* error */
{
int saved_errno = errno;
if (readable) {
close(pr[0]);
close(pr[1]);
}
if (writable) {
close(pw[0]);
close(pw[1]);
}
errno = saved_errno;
mrb_sys_fail(mrb, "pipe_open failed");
}
break;
}
return mrb_nil_value(); /* not reached */
}
#endif /* _WIN32 */
#endif /* TARGET_OS_IPHONE */
#endif /* MRB_NO_IO_POPEN */
static int
symdup(mrb_state *mrb, int fd, mrb_bool *failed)
@@ -1410,7 +1241,7 @@ io_s_pipe(mrb_state *mrb, mrb_value klass)
{
int pipes[2];
if (io_pipe(mrb, pipes) == -1) {
if (mrb_io_hal_pipe(mrb, pipes) == -1) {
mrb_sys_fail(mrb, "pipe");
}