Files
mruby-mruby/mrbgems/mruby-io/src/io.c
T
HASUMI Hitoshi 4035e42a39 Fix uninitialized variable in io_gets causing segmentation fault
This patch fixs a critical segmentation fault in `io_gets` function caused by an uninitialized limit variable.

This bug may specifically heppen when:
- MicroRuby with task scheduler, which I'm implementing, enabled

## Root Cause Analysis

When `io_gets` is called without arguments (argc=0), the local variable `limit` remains uninitialized on the stack.
I guess that this uninitialized memory often contains leftover heap addresses from previous stack frames.

### The problematic flow:

1. `mrb_get_args(mrb, "|o?i?", &rs, &rs_given, &limit, &limit_given)` with 0 arguments
2. `limit_given = FALSE` but limit contains garbage heap address
3. Looks like later processing truncates this address, creating invalid pointer 0xffff0000
4. This value gets pushed onto VM stack during string operations
5. Garbage collector attempts to mark 0xffff0000 as valid object pointer
6. SIGSEGV in mrb_gc_mark() at gc.c:748

    ```
    Program received signal SIGSEGV, Segmentation fault.
    0x00005c8bebffa95c in mrb_gc_mark (mrb=0x5c8bec2836c8 <heap_pool+728>, obj=0xffff0000)
        at .../gc.c:748
    748       if (!is_white(obj)) return;
    #1  mark_context_stack (mrb=0x5c8bec2836c8 <heap_pool+728>, c=0x5c8bec2b4a50 <heap_pool+202336>)
        at .../gc.c:555
    555       mrb_gc_mark(mrb, mrb_basic_ptr(v));
    ```

## Solution

I couldn't figure out the exact mechanism of the issue. Anyway, initializing the limit variable to zero could prevent invalid garbage stack memory:

```c
mrb_int limit = 0;  // Explicit initialization
```

## Files Changed

- mrbgems/picoruby-mruby/lib/mruby/mrbgems/mruby-io/src/io.c
2025-08-22 17:37:59 +09:00

2335 lines
55 KiB
C

/*
** io.c - IO class
*/
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/hash.h>
#include <mruby/string.h>
#include <mruby/variable.h>
#include <mruby/ext/io.h>
#include <mruby/error.h>
#include <mruby/internal.h>
#include <mruby/presym.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32)
#include <winsock.h>
#include <io.h>
#include <basetsd.h>
#define open _open
#define close _close
#define dup _dup
#define dup2 _dup2
#define read _read
#define write _write
#define lseek _lseek
#define isatty _isatty
#define WEXITSTATUS(x) (x)
typedef int fsize_t;
typedef long ftime_t;
typedef long fsuseconds_t;
typedef int fmode_t;
typedef int fssize_t;
#ifndef O_TMPFILE
#define O_TMPFILE O_TEMPORARY
#endif
#else
#include <sys/wait.h>
#include <sys/time.h>
#include <unistd.h>
typedef size_t fsize_t;
typedef time_t ftime_t;
#ifdef __DJGPP__
typedef long fsuseconds_t;
#else
typedef suseconds_t fsuseconds_t;
#endif
typedef mode_t fmode_t;
typedef ssize_t fssize_t;
#endif
#ifdef _MSC_VER
typedef mrb_int pid_t;
#endif
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define OPEN_ACCESS_MODE_FLAGS (O_RDONLY | O_WRONLY | O_RDWR)
#define OPEN_RDONLY_P(f) ((mrb_bool)(((f) & OPEN_ACCESS_MODE_FLAGS) == O_RDONLY))
#define OPEN_WRONLY_P(f) ((mrb_bool)(((f) & OPEN_ACCESS_MODE_FLAGS) == O_WRONLY))
#define OPEN_RDWR_P(f) ((mrb_bool)(((f) & OPEN_ACCESS_MODE_FLAGS) == O_RDWR))
#define OPEN_READABLE_P(f) ((mrb_bool)(OPEN_RDONLY_P(f) || OPEN_RDWR_P(f)))
#define OPEN_WRITABLE_P(f) ((mrb_bool)(OPEN_WRONLY_P(f) || OPEN_RDWR_P(f)))
static void io_free(mrb_state *mrb, void *ptr);
struct mrb_data_type mrb_io_type = { "IO", io_free };
static int io_modestr_to_flags(mrb_state *mrb, const char *modestr);
static int io_mode_to_flags(mrb_state *mrb, mrb_value mode);
static void fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet);
static struct mrb_io*
io_get_open_fptr(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = (struct mrb_io*)mrb_data_get_ptr(mrb, io, &mrb_io_type);
if (fptr == NULL) {
mrb_raise(mrb, E_IO_ERROR, "uninitialized stream");
}
if (fptr->fd < 0) {
mrb_raise(mrb, E_IO_ERROR, "closed stream");
}
return fptr;
}
#if !defined(MRB_NO_IO_POPEN) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
# define MRB_NO_IO_POPEN 1
#endif
#ifndef MRB_NO_IO_POPEN
static void
io_set_process_status(mrb_state *mrb, pid_t pid, int status)
{
struct RClass *c_status = NULL;
mrb_value v;
if (mrb_class_defined_id(mrb, MRB_SYM(Process))) {
struct RClass *c_process = mrb_module_get_id(mrb, MRB_SYM(Process));
if (mrb_const_defined(mrb, mrb_obj_value(c_process), MRB_SYM(Status))) {
c_status = mrb_class_get_under_id(mrb, c_process, MRB_SYM(Status));
}
}
if (c_status != NULL) {
v = mrb_funcall_id(mrb, mrb_obj_value(c_status), MRB_SYM(new), 2, mrb_fixnum_value(pid), mrb_fixnum_value(status));
}
else {
v = mrb_fixnum_value(WEXITSTATUS(status));
}
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$?"), v);
}
#endif
static int
io_modestr_to_flags(mrb_state *mrb, const char *mode)
{
int flags;
const char *m = mode;
switch (*m++) {
case 'r':
flags = O_RDONLY;
break;
case 'w':
flags = O_WRONLY | O_CREAT | O_TRUNC;
break;
case 'a':
flags = O_WRONLY | O_CREAT | O_APPEND;
break;
default:
goto modeerr;
}
while (*m) {
switch (*m++) {
case 'b':
#ifdef O_BINARY
flags |= O_BINARY;
#endif
break;
case 'x':
if (mode[0] != 'w') goto modeerr;
flags |= O_EXCL;
break;
case '+':
flags = (flags & ~OPEN_ACCESS_MODE_FLAGS) | O_RDWR;
break;
case ':':
/* XXX: PASSTHROUGH*/
default:
goto modeerr;
}
}
return flags;
modeerr:
mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %s", mode);
return 0; /* not reached */
}
static int
io_mode_to_flags(mrb_state *mrb, mrb_value mode)
{
if (mrb_nil_p(mode)) {
return O_RDONLY;
}
else if (mrb_string_p(mode)) {
return io_modestr_to_flags(mrb, RSTRING_CSTR(mrb, mode));
}
else {
int flags = 0;
mrb_int flags0 = mrb_as_int(mrb, mode);
switch (flags0 & MRB_O_ACCMODE) {
case MRB_O_RDONLY:
flags |= O_RDONLY;
break;
case MRB_O_WRONLY:
flags |= O_WRONLY;
break;
case MRB_O_RDWR:
flags |= O_RDWR;
break;
default:
mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %v", mode);
}
if (flags0 & MRB_O_APPEND) flags |= O_APPEND;
if (flags0 & MRB_O_CREAT) flags |= O_CREAT;
if (flags0 & MRB_O_EXCL) flags |= O_EXCL;
if (flags0 & MRB_O_TRUNC) flags |= O_TRUNC;
#ifdef O_NONBLOCK
if (flags0 & MRB_O_NONBLOCK) flags |= O_NONBLOCK;
#endif
#ifdef O_NOCTTY
if (flags0 & MRB_O_NOCTTY) flags |= O_NOCTTY;
#endif
#ifdef O_BINARY
if (flags0 & MRB_O_BINARY) flags |= O_BINARY;
#endif
#ifdef O_SHARE_DELETE
if (flags0 & MRB_O_SHARE_DELETE) flags |= O_SHARE_DELETE;
#endif
#ifdef O_SYNC
if (flags0 & MRB_O_SYNC) flags |= O_SYNC;
#endif
#ifdef O_DSYNC
if (flags0 & MRB_O_DSYNC) flags |= O_DSYNC;
#endif
#ifdef O_RSYNC
if (flags0 & MRB_O_RSYNC) flags |= O_RSYNC;
#endif
#ifdef O_NOFOLLOW
if (flags0 & MRB_O_NOFOLLOW) flags |= O_NOFOLLOW;
#endif
#ifdef O_NOATIME
if (flags0 & MRB_O_NOATIME) flags |= O_NOATIME;
#endif
#ifdef O_DIRECT
if (flags0 & MRB_O_DIRECT) flags |= O_DIRECT;
#endif
#ifdef O_TMPFILE
if (flags0 & MRB_O_TMPFILE) flags |= O_TMPFILE;
#endif
return flags;
}
}
static void
io_fd_cloexec(mrb_state *mrb, int fd)
{
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
int flags = fcntl(fd, F_GETFD);
int flags2;
if (flags < 0) {
mrb_sys_fail(mrb, "cloexec GETFD");
}
if (fd <= 2) {
flags2 = flags & ~FD_CLOEXEC; /* Clear CLOEXEC for standard file descriptors: 0, 1, 2. */
}
else {
flags2 = flags | FD_CLOEXEC; /* Set CLOEXEC for non-standard file descriptors: 3, 4, 5, ... */
}
if (flags != flags2) {
if (fcntl(fd, F_SETFD, flags2) < 0) {
mrb_sys_fail(mrb, "cloexec SETFD");
}
}
#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)
{
struct mrb_io *io = (struct mrb_io*)ptr;
if (io != NULL) {
fptr_finalize(mrb, io, TRUE);
mrb_free(mrb, io);
}
}
static void
io_init_buf(mrb_state *mrb, struct mrb_io *fptr)
{
if (fptr->readable) {
fptr->buf = (struct mrb_io_buf*)mrb_malloc(mrb, sizeof(struct mrb_io_buf));
fptr->buf->start = 0;
fptr->buf->len = 0;
}
}
static struct mrb_io *
io_alloc(mrb_state *mrb)
{
struct mrb_io *fptr = (struct mrb_io*)mrb_malloc(mrb, sizeof(struct mrb_io));
fptr->fd = -1;
fptr->fd2 = -1;
fptr->pid = 0;
fptr->buf = 0;
fptr->readable = 0;
fptr->writable = 0;
fptr->sync = 0;
fptr->eof = 0;
fptr->is_socket = 0;
fptr->close_fd = 1;
fptr->close_fd2 = 1;
return fptr;
}
#ifndef NOFILE
#define NOFILE 64
#endif
#ifdef MRB_NO_IO_POPEN
# define io_s_popen mrb_notimplement_m
#else
struct popen_params {
mrb_value klass;
const char *cmd;
int flags;
int doexec;
int opt_in, opt_out, opt_err;
};
static int
option_to_fd(mrb_state *mrb, mrb_value v)
{
if (mrb_undef_p(v)) return -1;
if (mrb_nil_p(v)) return -1;
switch (mrb_type(v)) {
case MRB_TT_CDATA: /* IO */
return mrb_io_fileno(mrb, v);
case MRB_TT_INTEGER:
return (int)mrb_integer(v);
default:
mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong exec redirect action");
break;
}
return -1; /* never reached */
}
static void
parse_popen_args(mrb_state *mrb, struct popen_params *p)
{
mrb_value mode = mrb_nil_value();
struct { mrb_value opt_in, opt_out, opt_err; } kv;
mrb_sym knames[3] = {MRB_SYM(in), MRB_SYM(out), MRB_SYM(err)};
const mrb_kwargs kw = {
3, 0,
knames,
&kv.opt_in,
NULL,
};
mrb_get_args(mrb, "zo:", &p->cmd, &mode, &kw);
p->flags = io_mode_to_flags(mrb, mode);
p->doexec = (strcmp("-", p->cmd) != 0);
p->opt_in = option_to_fd(mrb, kv.opt_in);
p->opt_out = option_to_fd(mrb, kv.opt_out);
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;
mrb->c->ci->mid = 0;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (OPEN_READABLE_P(p.flags)) {
if (!CreatePipe(&ofd[0], &ofd[1], &saAttr, 0)
|| !SetHandleInformation(ofd[0], HANDLE_FLAG_INHERIT, 0)) {
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)) {
mrb_sys_fail(mrb, "pipe");
}
}
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]);
mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", p.cmd);
}
CloseHandle(pi.hThread);
CloseHandle(ifd[0]);
CloseHandle(ofd[1]);
pid = pi.dwProcessId;
}
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;
if (readable && writable) {
close(pr[1]);
fd = pr[0];
close(pw[0]);
write_fd = pw[1];
}
else if (readable) {
close(pr[1]);
fd = pr[0];
}
else {
close(pw[0]);
fd = pw[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;
io_init_buf(mrb, fptr);
DATA_TYPE(io) = &mrb_io_type;
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 */
static int
symdup(mrb_state *mrb, int fd, mrb_bool *failed)
{
int new_fd;
*failed = TRUE;
if (fd < 0)
return fd;
new_fd = dup(fd);
if (new_fd > 0) *failed = FALSE;
return new_fd;
}
static mrb_value
io_init_copy(mrb_state *mrb, mrb_value copy)
{
mrb_value orig = mrb_get_arg1(mrb);
struct mrb_io *fptr_copy;
struct mrb_io *fptr_orig;
mrb_bool failed = TRUE;
fptr_orig = io_get_open_fptr(mrb, orig);
fptr_copy = (struct mrb_io*)DATA_PTR(copy);
if (fptr_orig == fptr_copy) return copy;
if (fptr_copy != NULL) {
fptr_finalize(mrb, fptr_copy, FALSE);
mrb_free(mrb, fptr_copy);
}
fptr_copy = (struct mrb_io*)io_alloc(mrb);
fptr_copy->pid = fptr_orig->pid;
fptr_copy->readable = fptr_orig->readable;
fptr_copy->writable = fptr_orig->writable;
fptr_copy->sync = fptr_orig->sync;
fptr_copy->is_socket = fptr_orig->is_socket;
io_init_buf(mrb, fptr_copy);
DATA_TYPE(copy) = &mrb_io_type;
DATA_PTR(copy) = fptr_copy;
fptr_copy->fd = symdup(mrb, fptr_orig->fd, &failed);
if (failed) {
mrb_sys_fail(mrb, 0);
}
io_fd_cloexec(mrb, fptr_copy->fd);
if (fptr_orig->fd2 != -1) {
fptr_copy->fd2 = symdup(mrb, fptr_orig->fd2, &failed);
if (failed) {
close(fptr_copy->fd);
mrb_sys_fail(mrb, 0);
}
io_fd_cloexec(mrb, fptr_copy->fd2);
}
return copy;
}
static void
check_file_descriptor(mrb_state *mrb, mrb_int fd)
{
struct stat sb;
int fdi = (int)fd;
#if MRB_INT_MIN < INT_MIN || MRB_INT_MAX > INT_MAX
if (fdi != fd) {
errno = EBADF;
goto badfd;
}
#endif
#ifdef _WIN32
{
DWORD err;
int len = sizeof(err);
if (getsockopt(fdi, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0) {
return;
}
}
if (fdi < 0 || fdi > _getmaxstdio()) {
errno = EBADF;
goto badfd;
}
#endif /* _WIN32 */
if (fstat(fdi, &sb) == 0) return;
if (errno == EBADF) goto badfd;
return;
badfd:
mrb_sys_fail(mrb, "bad file descriptor");
}
static mrb_value
io_init(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
mrb_int fd;
mrb_value mode, opt; /* opt (Hash) will be ignored */
int flags;
mode = opt = mrb_nil_value();
if (mrb_block_given_p(mrb)) {
mrb_warn(mrb, "File.new() does not take block; use File.open() instead");
}
mrb_get_args(mrb, "i|oH", &fd, &mode, &opt);
switch (fd) {
case 0: /* STDIN_FILENO */
case 1: /* STDOUT_FILENO */
case 2: /* STDERR_FILENO */
break;
default:
check_file_descriptor(mrb, fd);
break;
}
flags = io_mode_to_flags(mrb, mode);
fptr = (struct mrb_io*)DATA_PTR(io);
if (fptr != NULL) {
fptr_finalize(mrb, fptr, TRUE);
mrb_free(mrb, fptr);
}
fptr = io_alloc(mrb);
DATA_TYPE(io) = &mrb_io_type;
DATA_PTR(io) = fptr;
fptr->fd = (int)fd;
fptr->readable = OPEN_READABLE_P(flags);
fptr->writable = OPEN_WRITABLE_P(flags);
io_init_buf(mrb, fptr);
return io;
}
static void
fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet)
{
int saved_errno = 0;
int limit = quiet ? 3 : 0;
if (fptr == NULL) {
return;
}
if (fptr->fd >= limit) {
#ifdef _WIN32
if (fptr->is_socket) {
if (fptr->close_fd && closesocket(fptr->fd) != 0) {
saved_errno = WSAGetLastError();
}
fptr->fd = -1;
}
#endif
if (fptr->fd != -1 && fptr->close_fd) {
if (close(fptr->fd) == -1) {
saved_errno = errno;
}
}
fptr->fd = -1;
}
if (fptr->fd2 >= limit) {
if (fptr->close_fd2 && close(fptr->fd2) == -1) {
if (saved_errno == 0) {
saved_errno = errno;
}
}
fptr->fd2 = -1;
}
#ifndef MRB_NO_IO_POPEN
if (fptr->pid != 0) {
#if !defined(_WIN32)
pid_t pid;
int status;
do {
pid = waitpid(fptr->pid, &status, 0);
} while (pid == -1 && errno == EINTR);
if (!quiet && pid == fptr->pid) {
io_set_process_status(mrb, pid, status);
}
#else
HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, fptr->pid);
DWORD status;
if (WaitForSingleObject(h, INFINITE) && GetExitCodeProcess(h, &status))
if (!quiet)
io_set_process_status(mrb, fptr->pid, (int)status);
CloseHandle(h);
#endif
fptr->pid = 0;
/* Note: we don't raise an exception when waitpid(3) fails */
}
#endif
if (fptr->buf) {
mrb_free(mrb, fptr->buf);
fptr->buf = NULL;
}
if (!quiet && saved_errno != 0) {
errno = saved_errno;
mrb_sys_fail(mrb, "fptr_finalize failed");
}
}
static struct mrb_io*
io_get_read_fptr(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (!fptr->readable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for reading");
}
return fptr;
}
static struct mrb_io*
io_get_write_fptr(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (!fptr->writable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for writing");
}
return fptr;
}
static int
io_get_write_fd(struct mrb_io *fptr)
{
if (fptr->fd2 == -1) {
return fptr->fd;
}
else {
return fptr->fd2;
}
}
static mrb_value
io_isatty(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (isatty(fptr->fd) == 0)
return mrb_false_value();
return mrb_true_value();
}
static mrb_value
io_s_for_fd(mrb_state *mrb, mrb_value klass)
{
struct RClass *c = mrb_class_ptr(klass);
enum mrb_vtype ttype = MRB_INSTANCE_TT(c);
/* copied from mrb_instance_alloc() */
if (ttype == 0) ttype = MRB_TT_OBJECT;
mrb_value obj = mrb_obj_value((struct RObject*)mrb_obj_alloc(mrb, ttype, c));
return io_init(mrb, obj);
}
static mrb_value
io_s_sysclose(mrb_state *mrb, mrb_value klass)
{
mrb_int fd;
mrb->c->ci->mid = 0;
mrb_get_args(mrb, "i", &fd);
if (close((int)fd) == -1) {
mrb_sys_fail(mrb, "close");
}
return mrb_fixnum_value(0);
}
static int
io_cloexec_open(mrb_state *mrb, const char *pathname, int flags, fmode_t mode)
{
int retry = FALSE;
char *fname = mrb_locale_from_utf8(pathname, -1);
int fd;
#ifdef O_CLOEXEC
/* O_CLOEXEC is available since Linux 2.6.23. Linux 2.6.18 silently ignore it. */
flags |= O_CLOEXEC;
#elif defined O_NOINHERIT
flags |= O_NOINHERIT;
#endif
reopen:
fd = open(fname, flags, mode);
if (fd == -1) {
if (!retry) {
switch (errno) {
case ENFILE:
case EMFILE:
mrb_garbage_collect(mrb);
retry = TRUE;
goto reopen;
}
}
mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "open %s", pathname)));
}
mrb_locale_free(fname);
if (fd <= 2) {
io_fd_cloexec(mrb, fd);
}
return fd;
}
static mrb_value
io_s_sysopen(mrb_state *mrb, mrb_value klass)
{
mrb_value path = mrb_nil_value();
mrb_value mode = mrb_nil_value();
mrb_int perm = -1;
mrb_get_args(mrb, "S|oi", &path, &mode, &perm);
if (perm < 0) {
perm = 0666;
}
const char *pat = RSTRING_CSTR(mrb, path);
int flags = io_mode_to_flags(mrb, mode);
mrb_int fd = io_cloexec_open(mrb, pat, flags, (fmode_t)perm);
return mrb_fixnum_value(fd);
}
static void
eof_error(mrb_state *mrb)
{
mrb_raise(mrb, E_EOF_ERROR, "end of file reached");
}
static mrb_value
io_read_common(mrb_state *mrb,
fssize_t (*readfunc)(int, void*, fsize_t, off_t),
mrb_value io, mrb_value buf, mrb_int maxlen, off_t offset)
{
if (maxlen < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "negative expanding string size");
}
else if (maxlen == 0) {
return mrb_str_new(mrb, NULL, maxlen);
}
if (mrb_nil_p(buf)) {
buf = mrb_str_new(mrb, NULL, maxlen);
}
if (RSTRING_LEN(buf) != maxlen) {
buf = mrb_str_resize(mrb, buf, maxlen);
}
else {
mrb_str_modify(mrb, RSTRING(buf));
}
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
int ret = readfunc(fptr->fd, RSTRING_PTR(buf), (fsize_t)maxlen, offset);
if (ret < 0) {
mrb_sys_fail(mrb, "sysread failed");
}
if (RSTRING_LEN(buf) != ret) {
buf = mrb_str_resize(mrb, buf, ret);
}
if (ret == 0 && maxlen > 0) {
fptr->eof = 1;
eof_error(mrb);
}
return buf;
}
static fssize_t
sysread(int fd, void *buf, fsize_t nbytes, off_t offset)
{
return (fssize_t)read(fd, buf, nbytes);
}
static mrb_value
io_sysread(mrb_state *mrb, mrb_value io)
{
mrb_value buf = mrb_nil_value();
mrb_int maxlen;
mrb_get_args(mrb, "i|S", &maxlen, &buf);
return io_read_common(mrb, sysread, io, buf, maxlen, 0);
}
static mrb_value
io_sysseek(mrb_state *mrb, mrb_value io)
{
mrb_int offset, whence = -1;
mrb_get_args(mrb, "i|i", &offset, &whence);
if (whence < 0) {
whence = 0;
}
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
off_t pos = lseek(fptr->fd, (off_t)offset, (int)whence);
if (pos == -1) {
mrb_sys_fail(mrb, "sysseek");
}
fptr->eof = 0;
if (sizeof(off_t) > sizeof(mrb_int) && pos > (off_t)MRB_INT_MAX) {
mrb_raise(mrb, E_IO_ERROR, "sysseek reached too far for mrb_int");
}
return mrb_int_value(mrb, (mrb_int)pos);
}
static mrb_value
io_seek(mrb_state *mrb, mrb_value io)
{
mrb_value pos = io_sysseek(mrb, io);
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (fptr->buf) {
fptr->buf->start = 0;
fptr->buf->len = 0;
}
return pos;
}
static mrb_value
io_write_common(mrb_state *mrb,
fssize_t (*writefunc)(int, const void*, fsize_t, off_t),
struct mrb_io *fptr, const void *buf, mrb_ssize blen, off_t offset)
{
int fd = io_get_write_fd(fptr);
fssize_t length = writefunc(fd, buf, (fsize_t)blen, offset);
if (length == -1) {
mrb_sys_fail(mrb, "syswrite");
}
return mrb_int_value(mrb, (mrb_int)length);
}
static fssize_t
syswrite(int fd, const void *buf, fsize_t nbytes, off_t offset)
{
return (fssize_t)write(fd, buf, nbytes);
}
static mrb_value
io_syswrite(mrb_state *mrb, mrb_value io)
{
mrb_value buf;
mrb_get_args(mrb, "S", &buf);
return io_write_common(mrb, syswrite, io_get_write_fptr(mrb, io), RSTRING_PTR(buf), RSTRING_LEN(buf), 0);
}
/* def write(string) */
/* str = string.is_a?(String) ? string : string.to_s */
/* return 0 if str.empty? */
/* len = syswrite(str) */
/* len */
/* end */
static mrb_int
fd_write(mrb_state *mrb, int fd, mrb_value str)
{
fssize_t n;
str = mrb_obj_as_string(mrb, str);
fssize_t len = (fssize_t)RSTRING_LEN(str);
if (len == 0) return 0;
const char *ptr = RSTRING_PTR(str);
fssize_t sum = 0;
while (sum < len) {
n = write(fd, ptr + sum, len - sum);
if (n == -1) {
if (errno == EINTR) continue;
mrb_sys_fail(mrb, "syswrite");
}
sum += n;
}
return len;
}
/* Helper function to prepare IO object for writing by adjusting buffer state */
static void
io_prepare_write(mrb_state *mrb, struct mrb_io *fptr)
{
if (fptr->buf && fptr->buf->len > 0) {
int fd = io_get_write_fd(fptr);
off_t n;
/* get current position */
n = lseek(fd, 0, SEEK_CUR);
if (n == -1) mrb_sys_fail(mrb, "lseek");
/* move cursor */
n = lseek(fd, n - fptr->buf->len, SEEK_SET);
if (n == -1) mrb_sys_fail(mrb, "lseek(2)");
fptr->buf->start = fptr->buf->len = 0;
}
}
static mrb_value
io_write(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_write_fptr(mrb, io);
int fd = io_get_write_fd(fptr);
io_prepare_write(mrb, fptr);
mrb_int len = 0;
if (mrb_get_argc(mrb) == 1) {
len = fd_write(mrb, fd, mrb_get_arg1(mrb));
}
else {
mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
while (argc--) {
len += fd_write(mrb, fd, *argv++);
}
}
return mrb_int_value(mrb, len);
}
/* Helper function to write a string followed by newline if needed */
static void
io_puts_str(mrb_state *mrb, int fd, mrb_value str)
{
str = mrb_obj_as_string(mrb, str);
const char *ptr = RSTRING_PTR(str);
mrb_int len = RSTRING_LEN(str);
/* Write the original string */
fd_write(mrb, fd, str);
/* Add newline if string doesn't end with one */
if (len == 0 || ptr[len-1] != '\n') {
mrb_value newline = mrb_str_new_lit(mrb, "\n");
fd_write(mrb, fd, newline);
}
}
/* Recursive helper for puts with arrays */
static void
io_puts_ary(mrb_state *mrb, int fd, mrb_value ary)
{
mrb_int len = RARRAY_LEN(ary);
if (len == 0) {
/* Empty array - write a single newline */
mrb_value newline = mrb_str_new_lit(mrb, "\n");
fd_write(mrb, fd, newline);
return;
}
for (mrb_int i = 0; i < len; i++) {
mrb_value elem = RARRAY_PTR(ary)[i];
if (mrb_array_p(elem)) {
io_puts_ary(mrb, fd, elem); /* Recursive call for nested arrays */
}
else {
io_puts_str(mrb, fd, elem);
}
}
}
static mrb_value
io_puts(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_write_fptr(mrb, io);
int fd = io_get_write_fd(fptr);
/* Prepare IO for writing (handle read buffer adjustment) */
io_prepare_write(mrb, fptr);
mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
if (argc == 0) {
/* No arguments - just write a newline */
mrb_value newline = mrb_str_new_lit(mrb, "\n");
fd_write(mrb, fd, newline);
return mrb_nil_value();
}
/* Process each argument */
for (mrb_int i = 0; i < argc; i++) {
mrb_value arg = argv[i];
if (mrb_array_p(arg)) {
io_puts_ary(mrb, fd, arg);
}
else {
io_puts_str(mrb, fd, arg);
}
}
return mrb_nil_value();
}
/*
* call-seq:
* ios.print() -> nil
* ios.print(obj, ...) -> nil
*
* Writes the given object(s) to ios. Objects that aren't strings will be
* converted by calling their to_s method.
*/
static mrb_value
io_print(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_write_fptr(mrb, io);
int fd = io_get_write_fd(fptr);
/* Prepare IO for writing (handle read buffer adjustment) */
io_prepare_write(mrb, fptr);
mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
/* Convert each argument to string and write it */
for (mrb_int i = 0; i < argc; i++) {
mrb_value str = mrb_obj_as_string(mrb, argv[i]);
fd_write(mrb, fd, str);
}
return mrb_nil_value();
}
/*
* call-seq:
* ios << obj -> ios
*
* String Output - Writes obj to ios. obj will be converted to a string using
* to_s.
*/
static mrb_value
io_lshift(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_write_fptr(mrb, io);
int fd = io_get_write_fd(fptr);
/* Prepare IO for writing (handle read buffer adjustment) */
io_prepare_write(mrb, fptr);
mrb_value str = mrb_get_arg1(mrb);
str = mrb_obj_as_string(mrb, str);
fd_write(mrb, fd, str);
return io;
}
static mrb_value
io_close(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
fptr = io_get_open_fptr(mrb, io);
fptr_finalize(mrb, fptr, FALSE);
return mrb_nil_value();
}
/*
* call-seq:
* ios.close_write -> nil
*
* Closes the write end of a duplex I/O stream (i.e., a pipe).
* It will raise an `IOError` if the stream is not duplex.
*
* r, w = IO.pipe
* w.close_write
* r.read #=> ""
*/
static mrb_value
io_close_write(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (close((int)fptr->fd2) == -1) {
mrb_sys_fail(mrb, "close");
}
return mrb_nil_value();
}
/*
* call-seq:
* ios.closed? -> true or false
*
* Returns `true` if the stream is closed, `false` otherwise.
*
* f = File.new("testfile")
* f.close #=> nil
* f.closed? #=> true
*/
static mrb_value
io_closed(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = (struct mrb_io*)mrb_data_get_ptr(mrb, io, &mrb_io_type);
if (fptr == NULL || fptr->fd >= 0) {
return mrb_false_value();
}
return mrb_true_value();
}
static mrb_value
io_pos(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
off_t pos = lseek(fptr->fd, 0, SEEK_CUR);
if (pos == -1) mrb_sys_fail(mrb, 0);
if (fptr->buf) {
return mrb_int_value(mrb, pos - fptr->buf->len);
}
else {
return mrb_int_value(mrb, pos);
}
}
/*
* call-seq:
* ios.pid -> integer or nil
*
* Returns the process ID of a child process on a pipe, or `nil` if the
* stream is not a pipe.
*
* r, w = IO.pipe
* fork do
* r.close
* w.write "hello"
* w.close
* end
* w.close
* p r.pid #=> 2056
* r.read #=> "hello"
* r.close
*/
static mrb_value
io_pid(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
if (fptr->pid > 0) {
return mrb_fixnum_value(fptr->pid);
}
return mrb_nil_value();
}
static struct timeval
time2timeval(mrb_state *mrb, mrb_value time)
{
struct timeval t = { 0, 0 };
switch (mrb_type(time)) {
case MRB_TT_INTEGER:
t.tv_sec = (ftime_t)mrb_integer(time);
t.tv_usec = 0;
break;
#ifndef MRB_NO_FLOAT
case MRB_TT_FLOAT:
t.tv_sec = (ftime_t)mrb_float(time);
t.tv_usec = (fsuseconds_t)((mrb_float(time) - t.tv_sec) * 1000000.0);
break;
#endif
default:
mrb_raise(mrb, E_TYPE_ERROR, "wrong argument class");
}
return t;
}
/*
* call-seq:
* IO.new(fd, mode="r") -> io
*
* Returns a new `IO` object for the given integer file descriptor `fd` and
* `mode` string.
*
* f = IO.new(1, "w") # STDOUT
* f.puts "hello"
*/
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
static mrb_value
io_s_pipe(mrb_state *mrb, mrb_value klass)
{
int pipes[2];
if (io_pipe(mrb, pipes) == -1) {
mrb_sys_fail(mrb, "pipe");
}
mrb_value r = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
struct mrb_io *fptr_r = io_alloc(mrb);
fptr_r->fd = pipes[0];
fptr_r->readable = 1;
DATA_TYPE(r) = &mrb_io_type;
DATA_PTR(r) = fptr_r;
io_init_buf(mrb, fptr_r);
mrb_value w = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
struct mrb_io *fptr_w = io_alloc(mrb);
fptr_w->fd = pipes[1];
fptr_w->writable = 1;
fptr_w->sync = 1;
DATA_TYPE(w) = &mrb_io_type;
DATA_PTR(w) = fptr_w;
return mrb_assoc_new(mrb, r, w);
}
#endif
static int
mrb_io_read_data_pending(mrb_state *mrb, struct mrb_io *fptr)
{
if (fptr->buf && fptr->buf->len > 0) return 1;
return 0;
}
/*
* call-seq:
* IO.select(read_array, write_array=nil, error_array=nil, timeout=nil) -> array or nil
*
* Performs a `select(2)` system call on the given arrays of `IO` objects.
*
* For each array, it can contain `IO` objects or `nil`.
*
* The `timeout` argument is a number of seconds.
*
* It returns a three-element array containing the `IO` objects that are
* ready for reading, writing, or have an error, respectively.
*
* If the `timeout` is reached, it returns `nil`.
*
* r, w = IO.pipe
* IO.select([r], [w]) #=> [[#<IO:fd 6>], [#<IO:fd 7>], []]
*/
static mrb_value
io_s_select(mrb_state *mrb, mrb_value klass)
{
const mrb_value *argv;
mrb_int argc;
mrb_value read_io, list;
struct mrb_io *fptr;
int pending = 0;
mrb_value result;
int max = 0;
int interrupt_flag = 0;
mrb_get_args(mrb, "*", &argv, &argc);
if (argc < 1 || argc > 4) {
mrb_argnum_error(mrb, argc, 1, 4);
}
mrb_value timeout = mrb_nil_value();
mrb_value except = mrb_nil_value();
mrb_value write = mrb_nil_value();
if (argc > 3)
timeout = argv[3];
if (argc > 2)
except = argv[2];
if (argc > 1)
write = argv[1];
mrb_value read = argv[0];
struct timeval *tp, timerec;
if (mrb_nil_p(timeout)) {
tp = NULL;
}
else {
timerec = time2timeval(mrb, timeout);
tp = &timerec;
}
fd_set pset, rset, *rp;
FD_ZERO(&pset);
if (!mrb_nil_p(read)) {
mrb_check_type(mrb, read, MRB_TT_ARRAY);
rp = &rset;
FD_ZERO(rp);
for (int i = 0; i < RARRAY_LEN(read); i++) {
read_io = RARRAY_PTR(read)[i];
fptr = io_get_open_fptr(mrb, read_io);
if (fptr->fd >= FD_SETSIZE) continue;
FD_SET(fptr->fd, rp);
if (mrb_io_read_data_pending(mrb, fptr)) {
pending++;
FD_SET(fptr->fd, &pset);
}
if (max < fptr->fd)
max = fptr->fd;
}
if (pending) {
timerec.tv_sec = timerec.tv_usec = 0;
tp = &timerec;
}
}
else {
rp = NULL;
}
fd_set wset, *wp;
if (!mrb_nil_p(write)) {
mrb_check_type(mrb, write, MRB_TT_ARRAY);
wp = &wset;
FD_ZERO(wp);
for (int i = 0; i < RARRAY_LEN(write); i++) {
fptr = io_get_open_fptr(mrb, RARRAY_PTR(write)[i]);
if (fptr->fd >= FD_SETSIZE) continue;
FD_SET(fptr->fd, wp);
if (max < fptr->fd)
max = fptr->fd;
if (fptr->fd2 >= 0) {
FD_SET(fptr->fd2, wp);
if (max < fptr->fd2)
max = fptr->fd2;
}
}
}
else {
wp = NULL;
}
fd_set eset, *ep;
if (!mrb_nil_p(except)) {
mrb_check_type(mrb, except, MRB_TT_ARRAY);
ep = &eset;
FD_ZERO(ep);
for (int i = 0; i < RARRAY_LEN(except); i++) {
fptr = io_get_open_fptr(mrb, RARRAY_PTR(except)[i]);
if (fptr->fd >= FD_SETSIZE) continue;
FD_SET(fptr->fd, ep);
if (max < fptr->fd)
max = fptr->fd;
if (fptr->fd2 >= 0) {
FD_SET(fptr->fd2, ep);
if (max < fptr->fd2)
max = fptr->fd2;
}
}
}
else {
ep = NULL;
}
max++;
int n;
retry:
n = select(max, rp, wp, ep, tp);
if (n < 0) {
#ifdef _WIN32
errno = WSAGetLastError();
if (errno != WSAEINTR)
mrb_sys_fail(mrb, "select failed");
#else
if (errno != EINTR)
mrb_sys_fail(mrb, "select failed");
#endif
if (tp == NULL)
goto retry;
interrupt_flag = 1;
}
if (!pending && n == 0)
return mrb_nil_value();
result = mrb_ary_new_capa(mrb, 3);
mrb_ary_push(mrb, result, rp ? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0));
mrb_ary_push(mrb, result, wp ? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0));
mrb_ary_push(mrb, result, ep ? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0));
if (interrupt_flag == 0) {
if (rp) {
list = RARRAY_PTR(result)[0];
for (int i = 0; i < RARRAY_LEN(read); i++) {
fptr = io_get_open_fptr(mrb, RARRAY_PTR(read)[i]);
if (FD_ISSET(fptr->fd, rp) ||
FD_ISSET(fptr->fd, &pset)) {
mrb_ary_push(mrb, list, RARRAY_PTR(read)[i]);
}
}
}
if (wp) {
list = RARRAY_PTR(result)[1];
for (int i = 0; i < RARRAY_LEN(write); i++) {
fptr = io_get_open_fptr(mrb, RARRAY_PTR(write)[i]);
if (FD_ISSET(fptr->fd, wp)) {
mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]);
}
else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, wp)) {
mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]);
}
}
}
if (ep) {
list = RARRAY_PTR(result)[2];
for (int i = 0; i < RARRAY_LEN(except); i++) {
fptr = io_get_open_fptr(mrb, RARRAY_PTR(except)[i]);
if (FD_ISSET(fptr->fd, ep)) {
mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]);
}
else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, ep)) {
mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]);
}
}
}
}
return result;
}
int
mrb_io_fileno(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
return fptr->fd;
}
/*
* call-seq:
* ios.fileno -> integer
*
* Returns the integer file descriptor number for the `IO` object.
*
* $stdin.fileno #=> 0
* $stdout.fileno #=> 1
*/
static mrb_value
io_fileno(mrb_state *mrb, mrb_value io)
{
int fd = mrb_io_fileno(mrb, io);
return mrb_fixnum_value(fd);
}
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
/*
* call-seq:
* ios.close_on_exec? -> true or false
*
* Returns `true` if the `FD_CLOEXEC` flag is set for the `IO` object, `false`
* otherwise.
*
* f = IO.new(1, "w")
* f.close_on_exec? #=> true
*/
static mrb_value
io_close_on_exec_p(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
int ret;
if (fptr->fd2 >= 0) {
if ((ret = fcntl(fptr->fd2, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed");
if (!(ret & FD_CLOEXEC)) return mrb_false_value();
}
if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed");
if (!(ret & FD_CLOEXEC)) return mrb_false_value();
return mrb_true_value();
}
#else
# define io_close_on_exec_p mrb_notimplement_m
#endif
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
/*
* call-seq:
* ios.close_on_exec = bool -> bool
*
* Sets the `FD_CLOEXEC` flag on the `IO` object.
*
* f = IO.new(1, "w")
* f.close_on_exec = false
* f.close_on_exec? #=> false
*/
static mrb_value
io_set_close_on_exec(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
mrb_bool b;
mrb_get_args(mrb, "b", &b);
int flag = b ? FD_CLOEXEC : 0;
int ret;
if (fptr->fd2 >= 0) {
if ((ret = fcntl(fptr->fd2, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed");
if ((ret & FD_CLOEXEC) != flag) {
ret = (ret & ~FD_CLOEXEC) | flag;
ret = fcntl(fptr->fd2, F_SETFD, ret);
if (ret == -1) mrb_sys_fail(mrb, "F_SETFD failed");
}
}
if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) mrb_sys_fail(mrb, "F_GETFD failed");
if ((ret & FD_CLOEXEC) != flag) {
ret = (ret & ~FD_CLOEXEC) | flag;
ret = fcntl(fptr->fd, F_SETFD, ret);
if (ret == -1) mrb_sys_fail(mrb, "F_SETFD failed");
}
return mrb_bool_value(b);
}
#else
# define io_set_close_on_exec mrb_notimplement_m
#endif
/*
* call-seq:
* ios.sync = bool -> bool
*
* Sets the sync mode for the `IO` object.
*
* If `true`, all output is immediately flushed to the underlying operating
* system and is not buffered internally.
*
* f = File.new("testfile", "w")
* f.sync = true
*/
static mrb_value
io_set_sync(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
mrb_bool b;
mrb_get_args(mrb, "b", &b);
fptr->sync = b;
return mrb_bool_value(b);
}
/*
* call-seq:
* ios.sync -> true or false
*
* Returns the sync mode for the `IO` object.
*
* f = File.new("testfile", "w")
* f.sync #=> false
*/
static mrb_value
io_sync(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, io);
return mrb_bool_value(fptr->sync);
}
#ifndef MRB_USE_IO_PREAD_PWRITE
# define io_pread mrb_notimplement_m
# define io_pwrite mrb_notimplement_m
#else
static off_t
value2off(mrb_state *mrb, mrb_value offv)
{
return (off_t)mrb_as_int(mrb, offv);
}
/*
* call-seq:
* pread(maxlen, offset, outbuf = "") -> outbuf
*/
static mrb_value
io_pread(mrb_state *mrb, mrb_value io)
{
mrb_value buf = mrb_nil_value();
mrb_value off;
mrb_int maxlen;
mrb_get_args(mrb, "io|S!", &maxlen, &off, &buf);
return io_read_common(mrb, pread, io, buf, maxlen, value2off(mrb, off));
}
/*
* call-seq:
* pwrite(buffer, offset) -> wrote_bytes
*/
static mrb_value
io_pwrite(mrb_state *mrb, mrb_value io)
{
mrb_value buf, off;
mrb_get_args(mrb, "So", &buf, &off);
return io_write_common(mrb, pwrite, io_get_write_fptr(mrb, io), RSTRING_PTR(buf), RSTRING_LEN(buf), value2off(mrb, off));
}
#endif /* MRB_USE_IO_PREAD_PWRITE */
/*
* call-seq:
* ios.ungetc(string) -> nil
*
* Pushes back characters (passed as a parameter) onto ios, such that a
* subsequent buffered character read will return it. Has no effect with
* unbuffered reads (such as IO#sysread).
*
* f = File.new("testfile") #=> #<File:testfile>
* c = f.getc #=> "H"
* f.ungetc(c) #=> nil
* f.getc #=> "H"
*/
/* Helper function for ungetc operations with raw data */
static void
io_unget_data(mrb_state *mrb, struct mrb_io *fptr, const char *ptr, mrb_int len)
{
struct mrb_io_buf *buf = fptr->buf;
if (len > SHRT_MAX) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "string too long to ungetc");
}
if (len > MRB_IO_BUF_SIZE - buf->len) {
fptr->buf = (struct mrb_io_buf*)mrb_realloc(mrb, buf, sizeof(struct mrb_io_buf)+buf->len+len-MRB_IO_BUF_SIZE);
buf = fptr->buf;
}
memmove(buf->mem+len, buf->mem+buf->start, buf->len);
memcpy(buf->mem, ptr, len);
buf->start = 0;
buf->len += (short)len;
}
static mrb_value
io_ungetc(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
mrb_value str;
mrb_get_args(mrb, "S", &str);
io_unget_data(mrb, fptr, RSTRING_PTR(str), RSTRING_LEN(str));
return mrb_nil_value();
}
/*
* call-seq:
* ios.ungetbyte(string) -> nil
* ios.ungetbyte(integer) -> nil
*
* Pushes back bytes (passed as a parameter) onto ios, such that a subsequent
* buffered character read will return it. Only one byte may be pushed back
* before a subsequent read operation (that is, you will be able to read only
* the last of several bytes that have been pushed back). Has no effect with
* unbuffered reads (such as IO#sysread).
*/
static mrb_value
io_ungetbyte(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
mrb_value c = mrb_get_arg1(mrb);
unsigned char byte_val;
if (mrb_string_p(c)) {
if (RSTRING_LEN(c) == 0) {
return mrb_nil_value(); /* Empty string, do nothing */
}
byte_val = (unsigned char)RSTRING_PTR(c)[0];
}
else {
mrb_int val = mrb_integer(c);
byte_val = (unsigned char)(val & 0xff);
}
/* Use helper function with single byte */
io_unget_data(mrb, fptr, (const char*)&byte_val, 1);
return mrb_nil_value();
}
static void
io_buf_reset(struct mrb_io_buf *buf)
{
buf->start = 0;
buf->len = 0;
}
static void
io_buf_shift(struct mrb_io_buf *buf, mrb_int n)
{
mrb_assert(n <= SHRT_MAX);
buf->start += (short)n;
buf->len -= (short)n;
}
#ifdef MRB_UTF8_STRING
static void
io_fill_buf_comp(mrb_state *mrb, struct mrb_io *fptr)
{
struct mrb_io_buf *buf = fptr->buf;
int keep = buf->len;
memmove(buf->mem, buf->mem+buf->start, keep);
int n = read(fptr->fd, buf->mem+keep, MRB_IO_BUF_SIZE-keep);
if (n < 0) mrb_sys_fail(mrb, 0);
if (n == 0) fptr->eof = 1;
buf->start = 0;
buf->len += (short)n;
}
#endif
static void
io_fill_buf(mrb_state *mrb, struct mrb_io *fptr)
{
struct mrb_io_buf *buf = fptr->buf;
if (buf->len > 0) return;
int n = read(fptr->fd, buf->mem, MRB_IO_BUF_SIZE);
if (n < 0) mrb_sys_fail(mrb, 0);
if (n == 0) fptr->eof = 1;
buf->start = 0;
buf->len = (short)n;
}
static mrb_value
io_eof(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
if (fptr->eof) return mrb_true_value();
if (fptr->buf->len > 0) return mrb_false_value();
io_fill_buf(mrb, fptr);
return mrb_bool_value(fptr->eof);
}
static void
io_buf_cat(mrb_state *mrb, mrb_value outbuf, struct mrb_io_buf *buf, mrb_int n)
{
mrb_assert(n <= buf->len);
mrb_str_cat(mrb, outbuf, buf->mem+buf->start, n);
io_buf_shift(buf, n);
}
static void
io_buf_cat_all(mrb_state *mrb, mrb_value outbuf, struct mrb_io_buf *buf)
{
mrb_str_cat(mrb, outbuf, buf->mem+buf->start, buf->len);
io_buf_reset(buf);
}
static mrb_value
io_read_all(mrb_state *mrb, struct mrb_io *fptr, mrb_value outbuf)
{
for (;;) {
io_fill_buf(mrb, fptr);
if (fptr->eof) {
return outbuf;
}
io_buf_cat_all(mrb, outbuf, fptr->buf);
}
}
static mrb_value
io_reset_outbuf(mrb_state *mrb, mrb_value outbuf, mrb_int len)
{
if (mrb_nil_p(outbuf)) {
outbuf = mrb_str_new(mrb, NULL, 0);
}
else {
mrb_str_modify(mrb, mrb_str_ptr(outbuf));
RSTR_SET_LEN(mrb_str_ptr(outbuf), 0);
}
return outbuf;
}
/*
* call-seq:
* ios.read(length = nil, outbuf = "") -> string, outbuf, or nil
*
* Reads `length` bytes from the I/O stream.
*
* If `length` is `nil`, it reads until end of file.
* If `outbuf` is given, it will be used as the buffer.
*
* f = File.new("testfile")
* f.read(16) #=> "This is line one"
*/
static mrb_value
io_read(mrb_state *mrb, mrb_value io)
{
mrb_value outbuf = mrb_nil_value();
mrb_value len;
mrb_int length = 0;
mrb_bool length_given;
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
mrb_get_args(mrb, "|o?S", &len, &length_given, &outbuf);
if (length_given) {
if (mrb_nil_p(len)) {
length_given = FALSE;
}
else {
length = mrb_as_int(mrb, len);
if (length < 0) {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "negative length %i given", length);
}
if (length == 0) {
return io_reset_outbuf(mrb, outbuf, 0);
}
}
}
outbuf = io_reset_outbuf(mrb, outbuf, MRB_IO_BUF_SIZE);
if (!length_given) { /* read as much as possible */
return io_read_all(mrb, fptr, outbuf);
}
struct mrb_io_buf *buf = fptr->buf;
for (;;) {
io_fill_buf(mrb, fptr);
if (fptr->eof || length == 0) {
if (RSTRING_LEN(outbuf) == 0)
return mrb_nil_value();
return outbuf;
}
if (buf->len < length) {
length -= buf->len;
io_buf_cat_all(mrb, outbuf, buf);
}
else {
io_buf_cat(mrb, outbuf, buf, length);
return outbuf;
}
}
}
static mrb_int
io_find_index(struct mrb_io *fptr, const char *rs, mrb_int rslen)
{
struct mrb_io_buf *buf = fptr->buf;
mrb_assert(rslen > 0);
const char c = rs[0];
const mrb_int limit = buf->len - rslen + 1;
const char *p = buf->mem+buf->start;
for (mrb_int i=0; i<limit; i++) {
if (p[i] == c && (rslen == 1 || memcmp(p+i, rs, rslen) == 0)) {
return i;
}
}
return -1;
}
static mrb_value
io_gets(mrb_state *mrb, mrb_value io)
{
mrb_value rs = mrb_nil_value();
mrb_bool rs_given = FALSE; /* newline break */
mrb_int limit = 0;
mrb_bool limit_given = FALSE; /* no limit */
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
struct mrb_io_buf *buf = fptr->buf;
mrb_get_args(mrb, "|o?i?", &rs, &rs_given, &limit, &limit_given);
if (limit_given == FALSE) {
if (rs_given) {
if (mrb_nil_p(rs)) {
rs_given = FALSE;
}
else if (mrb_integer_p(rs)) {
limit = mrb_integer(rs);
limit_given = TRUE;
rs = mrb_nil_value();
}
else if (!mrb_string_p(rs)) {
mrb_ensure_int_type(mrb, rs);
}
}
}
if (rs_given) {
if (mrb_nil_p(rs)) {
rs_given = FALSE;
}
else {
mrb_ensure_string_type(mrb, rs);
if (RSTRING_LEN(rs) == 0) { /* paragraph mode */
rs = mrb_str_new_lit(mrb, "\n\n");
}
}
}
else {
rs = mrb_str_new_lit(mrb, "\n");
rs_given = TRUE;
}
/* from now on rs_given==FALSE means no RS */
if (mrb_nil_p(rs) && !limit_given) {
return io_read_all(mrb, fptr, mrb_str_new_capa(mrb, MRB_IO_BUF_SIZE));
}
io_fill_buf(mrb, fptr);
if (fptr->eof) return mrb_nil_value();
mrb_value outbuf;
if (limit_given) {
if (limit == 0) return mrb_str_new(mrb, NULL, 0);
outbuf = mrb_str_new_capa(mrb, limit);
}
else {
outbuf = mrb_str_new(mrb, NULL, 0);
}
for (;;) {
if (rs_given) { /* with RS */
mrb_int rslen = RSTRING_LEN(rs);
mrb_int idx = io_find_index(fptr, RSTRING_PTR(rs), rslen);
if (idx >= 0) { /* found */
mrb_int n = idx+rslen;
if (limit_given && limit < n) {
n = limit;
}
io_buf_cat(mrb, outbuf, buf, n);
return outbuf;
}
}
if (limit_given) {
if (limit <= buf->len) {
io_buf_cat(mrb, outbuf, buf, limit);
return outbuf;
}
limit -= buf->len;
}
io_buf_cat_all(mrb, outbuf, buf);
io_fill_buf(mrb, fptr);
if (fptr->eof) {
if (RSTRING_LEN(outbuf) == 0) return mrb_nil_value();
return outbuf;
}
}
}
static mrb_value
io_readline(mrb_state *mrb, mrb_value io)
{
mrb_value result = io_gets(mrb, io);
if (mrb_nil_p(result)) {
eof_error(mrb);
}
return result;
}
static mrb_value
io_readlines(mrb_state *mrb, mrb_value io)
{
mrb_value ary = mrb_ary_new(mrb);
for (;;) {
mrb_value line = io_gets(mrb, io);
if (mrb_nil_p(line)) return ary;
mrb_ary_push(mrb, ary, line);
}
}
static mrb_value
io_getc(mrb_state *mrb, mrb_value io)
{
mrb_int len = 1;
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
struct mrb_io_buf *buf = fptr->buf;
io_fill_buf(mrb, fptr);
if (fptr->eof) return mrb_nil_value();
#ifdef MRB_UTF8_STRING
const char *p = &buf->mem[buf->start];
if ((*p) & 0x80) {
len = mrb_utf8len(p, p+buf->len);
if (len == 1 && buf->len < 4) { /* partial UTF-8 */
io_fill_buf_comp(mrb, fptr);
p = &buf->mem[buf->start];
len = mrb_utf8len(p, p+buf->len);
}
}
#endif
mrb_value str = mrb_str_new(mrb, buf->mem+buf->start, len);
io_buf_shift(buf, len);
return str;
}
static mrb_value
io_readchar(mrb_state *mrb, mrb_value io)
{
mrb_value result = io_getc(mrb, io);
if (mrb_nil_p(result)) {
eof_error(mrb);
}
return result;
}
/*
* call-seq:
* ios.getbyte -> integer or nil
*
* Reads a byte from the `IO` stream.
*
* Returns the byte as an integer, or `nil` at end of file.
*
* f = File.new("testfile")
* f.getbyte #=> 72
*/
static mrb_value
io_getbyte(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr = io_get_read_fptr(mrb, io);
struct mrb_io_buf *buf = fptr->buf;
io_fill_buf(mrb, fptr);
if (fptr->eof) return mrb_nil_value();
unsigned char c = buf->mem[buf->start];
io_buf_shift(buf, 1);
return mrb_int_value(mrb, (mrb_int)c);
}
/*
* call-seq:
* ios.readbyte -> integer
*
* Reads a byte from the `IO` stream.
*
* Returns the byte as an integer. Raises `EOFError` at end of file.
*
* f = File.new("testfile")
* f.readbyte #=> 72
*/
static mrb_value
io_readbyte(mrb_state *mrb, mrb_value io)
{
mrb_value result = io_getbyte(mrb, io);
if (mrb_nil_p(result)) {
eof_error(mrb);
}
return result;
}
/*
* call-seq:
* ios.flush -> ios
*
* Flushes any buffered data within the `IO` object to the underlying
* operating system.
*
* $stdout.print "no newline"
* $stdout.flush
*/
static mrb_value
io_flush(mrb_state *mrb, mrb_value io)
{
io_get_open_fptr(mrb, io);
return io;
}
void
mrb_init_io(mrb_state *mrb)
{
struct RClass *io = mrb_define_class_id(mrb, MRB_SYM(IO), mrb->object_class);
MRB_SET_INSTANCE_TT(io, MRB_TT_CDATA);
mrb_include_module(mrb, io, mrb_module_get_id(mrb, MRB_SYM(Enumerable))); /* 15.2.20.3 */
mrb_define_class_method_id(mrb, io, MRB_SYM(_popen), io_s_popen, MRB_ARGS_ARG(1,2));
mrb_define_class_method_id(mrb, io, MRB_SYM(_sysclose), io_s_sysclose, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, io, MRB_SYM(for_fd), io_s_for_fd, MRB_ARGS_ARG(1,2));
mrb_define_class_method_id(mrb, io, MRB_SYM(select), io_s_select, MRB_ARGS_ARG(1,3));
mrb_define_class_method_id(mrb, io, MRB_SYM(sysopen), io_s_sysopen, MRB_ARGS_ARG(1,2));
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
mrb_define_class_method_id(mrb, io, MRB_SYM(_pipe), io_s_pipe, MRB_ARGS_NONE());
#endif
mrb_define_method_id(mrb, io, MRB_SYM(initialize), io_init, MRB_ARGS_ARG(1,2));
mrb_define_private_method_id(mrb, io, MRB_SYM(initialize_copy), io_init_copy, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(isatty), io_isatty, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM_Q(eof), io_eof, MRB_ARGS_NONE()); /* 15.2.20.5.6 */
mrb_define_method_id(mrb, io, MRB_SYM(getc), io_getc, MRB_ARGS_NONE()); /* 15.2.20.5.8 */
mrb_define_method_id(mrb, io, MRB_SYM(gets), io_gets, MRB_ARGS_OPT(2)); /* 15.2.20.5.9 */
mrb_define_method_id(mrb, io, MRB_SYM(read), io_read, MRB_ARGS_OPT(2)); /* 15.2.20.5.14 */
mrb_define_method_id(mrb, io, MRB_SYM(readchar), io_readchar, MRB_ARGS_NONE()); /* 15.2.20.5.15 */
mrb_define_method_id(mrb, io, MRB_SYM(readline), io_readline, MRB_ARGS_OPT(2)); /* 15.2.20.5.16 */
mrb_define_method_id(mrb, io, MRB_SYM(readlines), io_readlines, MRB_ARGS_OPT(2)); /* 15.2.20.5.17 */
mrb_define_method_id(mrb, io, MRB_SYM(sync), io_sync, MRB_ARGS_NONE()); /* 15.2.20.5.18 */
mrb_define_method_id(mrb, io, MRB_SYM_E(sync), io_set_sync, MRB_ARGS_REQ(1)); /* 15.2.20.5.19 */
mrb_define_method_id(mrb, io, MRB_SYM(sysread), io_sysread, MRB_ARGS_ARG(1,1));
mrb_define_method_id(mrb, io, MRB_SYM(sysseek), io_sysseek, MRB_ARGS_ARG(1,1));
mrb_define_method_id(mrb, io, MRB_SYM(syswrite), io_syswrite, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(seek), io_seek, MRB_ARGS_ARG(1,1));
mrb_define_method_id(mrb, io, MRB_SYM(close), io_close, MRB_ARGS_NONE()); /* 15.2.20.5.1 */
mrb_define_method_id(mrb, io, MRB_SYM(close_write), io_close_write, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM_E(close_on_exec), io_set_close_on_exec, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM_Q(close_on_exec), io_close_on_exec_p, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM_Q(closed), io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */
mrb_define_method_id(mrb, io, MRB_SYM(flush), io_flush, MRB_ARGS_NONE()); /* 15.2.20.5.7 */
mrb_define_method_id(mrb, io, MRB_SYM(ungetc), io_ungetc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(ungetbyte), io_ungetbyte, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(pos), io_pos, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(pid), io_pid, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(fileno), io_fileno, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(write), io_write, MRB_ARGS_ANY()); /* 15.2.20.5.20 */
mrb_define_method_id(mrb, io, MRB_SYM(puts), io_puts, MRB_ARGS_ANY());
mrb_define_method_id(mrb, io, MRB_SYM(print), io_print, MRB_ARGS_ANY());
mrb_define_method_id(mrb, io, MRB_OPSYM(lshift), io_lshift, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, io, MRB_SYM(pread), io_pread, MRB_ARGS_ANY()); /* Ruby 2.5 feature */
mrb_define_method_id(mrb, io, MRB_SYM(pwrite), io_pwrite, MRB_ARGS_ANY()); /* Ruby 2.5 feature */
mrb_define_method_id(mrb, io, MRB_SYM(getbyte), io_getbyte, MRB_ARGS_NONE());
mrb_define_method_id(mrb, io, MRB_SYM(readbyte), io_readbyte, MRB_ARGS_NONE());
mrb_define_const_id(mrb, io, MRB_SYM(SEEK_SET), mrb_fixnum_value(SEEK_SET));
mrb_define_const_id(mrb, io, MRB_SYM(SEEK_CUR), mrb_fixnum_value(SEEK_CUR));
mrb_define_const_id(mrb, io, MRB_SYM(SEEK_END), mrb_fixnum_value(SEEK_END));
}