Files
mruby-mruby/mrbgems/mruby-io/src/io.c
T
Yukihiro "Matz" Matsumoto dccd66f9ef Support Ruby3.0 keyword arguments.
The Difference

Since Ruby1.9, the keyword arguments were emulated by Ruby using the hash
object at the bottom of the arguments. But we have gradually moved toward
keyword arguments separated from normal (positinal) arguments.

At the same time, we value compatibility, so that Ruby3.0 keyword
arguments are somewhat compromise. Basically, keyword arguments are
separated from positional arguments, except when the method does not
take any formal keyword arguments, given keyword arguments (packed
in the hash object) are considered as the last argument.

And we also allow non symbol keys in the keyword arguments. In that
case, those keys are just passed in the `**` hash (or raise
`ArgumentError` for unknown keys).

The Instruction Changes

We have changed `OP_SEND` instruction. `OP_SEND` instruction used to
take 3 operands, the register, the symbol, the number of (positional)
arguments. The meaning of the third operand has been changed. It is now
considered as `n|(nk<<4)`, where `n` is the number of positional
arguments, and `nk` is the number of keyword arguments, both occupies
4 bits in the operand.

The number `15` in both `n` and `nk` means variable sized arguments are
packed in the object. Positional arguments will be packed in the array,
and keyword arguments will be packed in the hash object. That means
arguments more than 14 values are always packed in the object.

Arguments information for other instructions (`OP_SENDB` and `OP_SUPER`)
are also changed. It works as the third operand of `OP_SEND`. the
difference between `OP_SEND` and `OP_SENDB` is just trivial. It assigns
`nil` to the block hidden arguments (right after arguments).

The instruction `OP_SENDV` and `OP_SENDVB` are removed. Those
instructions are replaced by `OP_SEND` and `OP_SENDB` respectively with
the `15` (variable sized) argument information.

Calling Convention

When calling a method, the stack elements shall be in the order of the
receiver of the method, positional arguments, keyword arguments and the
block argument. If the number of positional or keyword arugument (`n` or
`nk`) is zero, corresponding arguments will be empty. So when `n=0` and
`nk=0` the stack layout (from bottom to top) will be:

+-----------------------+
| recv | block (or nil) |
+-----------------------+

The last elements `block` should be explicitly filled before `OP_SEND`
or assigned to `nil` by `OP_SENDB` internally. In other words, the
following have exactly same behavior:

OP_SENDB clears `block` implicitly:

```
OP_SENDB reg sym 0
```

OP_SEND clears `block` implicitly:

```
OP_LOADNIL  R2
OP_SEND     R2 sym 0
```

When calling a method with only positional arguments (n=0..14) without
keyword arguments, the stack layout will be like following:

+--------------------------------------------+
| recv | arg1 | ... | arg_n | block (or nil) |
+--------------------------------------------+

When calling a method with arguments packed in the array (n=15) which
means argument splat (*) is used in the actual arguments, or more than
14 arguments are passed the stack layout will be like following:

+-------------------------------+
| recv | array | block (or nil) |
+-------------------------------+

The number of the actual arguments is determined by the length of the
argument array.

When keyword arguments are given (nk>0), keyword arguments are passed
between positional arguments and the block argument. For example, when
we pass one positional argument `1` and one keyword argument `a: 2`,
the stack layout will be like:

+------------------------------------+
| recv | 1 | :a | 2 | block (or nil) |
+------------------------------------+

Note that keyword arguments consume `2*nk` elements in the stack when
`nk=0..14` (unpacked).

When calling a method with keyword arguments packed in the hash object
(nk=15) which means keyword argument splat (**) is used or more than
14 keyword arguments in the actual arguments, the stack layout will
be like:

+------------------------------+
| recv | hash | block (or nil) |
+------------------------------+

Note for mruby/c

When mruby/c authors try to support new keyword arguments, they need
to handle the new meaning of the argument information operand. If they
choose not to support keyword arguments in mruby/c, it just raise
error when `nk` (taken by `(c>>4)&0xf`) is not zero. And combine
`OP_SENDV` behavior with `OP_SEND` when `n` is `15`.

If they want to support keyword arguments seriously, contact me at
<matz@ruby.or.jp> or `@yukihiro_matz`. I can help you.
2021-10-12 20:16:36 +09:00

1568 lines
38 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/presym.h"
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(_WIN64)
#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 mrb_io_read_write_size;
#ifndef MRB_MINGW32_LEGACY
#if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) && \
!defined(__have_typedef_ssize_t)
typedef SSIZE_T ssize_t;
#endif
#endif
#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;
typedef suseconds_t fsuseconds_t;
typedef mode_t fmode_t;
typedef ssize_t mrb_io_read_write_size;
#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 mrb_io_free(mrb_state *mrb, void *ptr);
struct mrb_data_type mrb_io_type = { "IO", mrb_io_free };
static struct mrb_io *io_get_open_fptr(mrb_state *mrb, mrb_value self);
static int mrb_io_modestr_to_flags(mrb_state *mrb, const char *modestr);
static int mrb_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 self)
{
struct mrb_io *fptr;
fptr = (struct mrb_io *)mrb_data_get_ptr(mrb, self, &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;
}
static void
io_set_process_status(mrb_state *mrb, pid_t pid, int status)
{
struct RClass *c_process, *c_status;
mrb_value v;
c_status = NULL;
if (mrb_class_defined_id(mrb, MRB_SYM(Process))) {
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);
}
static int
mrb_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:
mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %s", mode);
flags = 0; /* not reached */
}
while (*m) {
switch (*m++) {
case 'b':
#ifdef O_BINARY
flags |= O_BINARY;
#endif
break;
case '+':
flags = (flags & ~OPEN_ACCESS_MODE_FLAGS) | O_RDWR;
break;
case ':':
/* XXX: PASSTHROUGH*/
default:
mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %s", mode);
}
}
return flags;
}
static int
mrb_io_mode_to_flags(mrb_state *mrb, mrb_value mode)
{
if (mrb_nil_p(mode)) {
return mrb_io_modestr_to_flags(mrb, "r");
}
else if (mrb_string_p(mode)) {
return mrb_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
mrb_fd_cloexec(mrb_state *mrb, int fd)
{
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
int flags, flags2;
flags = fcntl(fd, F_GETFD);
if (flags == -1) {
mrb_bug(mrb, "mrb_fd_cloexec: fcntl(%d, F_GETFD) failed: %d", fd, errno);
}
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) == -1) {
mrb_bug(mrb, "mrb_fd_cloexec: fcntl(%d, F_SETFD, %d) failed: %d", fd, flags2, errno);
}
}
#endif
}
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
static int
mrb_cloexec_pipe(mrb_state *mrb, int fildes[2])
{
int ret;
ret = pipe(fildes);
if (ret == -1)
return -1;
mrb_fd_cloexec(mrb, fildes[0]);
mrb_fd_cloexec(mrb, fildes[1]);
return ret;
}
static int
mrb_pipe(mrb_state *mrb, int pipes[2])
{
int ret;
ret = mrb_cloexec_pipe(mrb, pipes);
if (ret == -1) {
if (errno == EMFILE || errno == ENFILE) {
mrb_garbage_collect(mrb);
ret = mrb_cloexec_pipe(mrb, pipes);
}
}
return ret;
}
static int
mrb_proc_exec(const char *pname)
{
const char *s;
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
mrb_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 struct mrb_io *
mrb_io_alloc(mrb_state *mrb)
{
struct mrb_io *fptr;
fptr = (struct mrb_io *)mrb_malloc(mrb, sizeof(struct mrb_io));
fptr->fd = -1;
fptr->fd2 = -1;
fptr->pid = 0;
fptr->readable = 0;
fptr->writable = 0;
fptr->sync = 0;
fptr->is_socket = 0;
return fptr;
}
#ifndef NOFILE
#define NOFILE 64
#endif
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
# define mrb_io_s_popen mrb_notimplement_m
#else
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_DATA: /* 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 mrb_value
mrb_io_s_popen_args(mrb_state *mrb, mrb_value klass,
const char **cmd, int *flags, int *doexec,
int *opt_in, int *opt_out, int *opt_err)
{
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:", cmd, &mode, &kw);
*flags = mrb_io_mode_to_flags(mrb, mode);
*doexec = (strcmp("-", *cmd) != 0);
*opt_in = option_to_fd(mrb, kv.opt_in);
*opt_out = option_to_fd(mrb, kv.opt_out);
*opt_err = option_to_fd(mrb, kv.opt_err);
return mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
}
#ifdef _WIN32
static mrb_value
mrb_io_s_popen(mrb_state *mrb, mrb_value klass)
{
mrb_value io;
int doexec;
int opt_in, opt_out, opt_err;
const char *cmd;
struct mrb_io *fptr;
int pid = 0, flags;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES saAttr;
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;
io = mrb_io_s_popen_args(mrb, klass, &cmd, &flags, &doexec,
&opt_in, &opt_out, &opt_err);
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (OPEN_READABLE_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(flags)) {
if (!CreatePipe(&ifd[0], &ifd[1], &saAttr, 0)
|| !SetHandleInformation(ifd[1], HANDLE_FLAG_INHERIT, 0)) {
mrb_sys_fail(mrb, "pipe");
}
}
if (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(flags)) {
si.hStdOutput = ofd[1];
si.hStdError = ofd[1];
}
if (OPEN_WRITABLE_P(flags)) {
si.hStdInput = ifd[0];
}
if (!CreateProcess(
NULL, (char*)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", cmd);
}
CloseHandle(pi.hThread);
CloseHandle(ifd[0]);
CloseHandle(ofd[1]);
pid = pi.dwProcessId;
}
mrb_iv_set(mrb, io, mrb_intern_lit(mrb, "@buf"), mrb_str_new_cstr(mrb, ""));
fptr = mrb_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(flags);
fptr->writable = OPEN_WRITABLE_P(flags);
fptr->sync = 0;
DATA_TYPE(io) = &mrb_io_type;
DATA_PTR(io) = fptr;
return io;
}
#else
static mrb_value
mrb_io_s_popen(mrb_state *mrb, mrb_value klass)
{
mrb_value io, result;
int doexec;
int opt_in, opt_out, opt_err;
const char *cmd;
struct mrb_io *fptr;
int pid, flags, fd, write_fd = -1;
int pr[2] = { -1, -1 };
int pw[2] = { -1, -1 };
int saved_errno;
mrb->c->ci->mid = 0;
io = mrb_io_s_popen_args(mrb, klass, &cmd, &flags, &doexec,
&opt_in, &opt_out, &opt_err);
if (OPEN_READABLE_P(flags)) {
if (pipe(pr) == -1) {
mrb_sys_fail(mrb, "pipe");
}
mrb_fd_cloexec(mrb, pr[0]);
mrb_fd_cloexec(mrb, pr[1]);
}
if (OPEN_WRITABLE_P(flags)) {
if (pipe(pw) == -1) {
if (pr[0] != -1) close(pr[0]);
if (pr[1] != -1) close(pr[1]);
mrb_sys_fail(mrb, "pipe");
}
mrb_fd_cloexec(mrb, pw[0]);
mrb_fd_cloexec(mrb, pw[1]);
}
if (!doexec) {
// XXX
fflush(stdin);
fflush(stdout);
fflush(stderr);
}
result = mrb_nil_value();
switch (pid = fork()) {
case 0: /* child */
if (opt_in != -1) {
dup2(opt_in, 0);
}
if (opt_out != -1) {
dup2(opt_out, 1);
}
if (opt_err != -1) {
dup2(opt_err, 2);
}
if (OPEN_READABLE_P(flags)) {
close(pr[0]);
if (pr[1] != 1) {
dup2(pr[1], 1);
close(pr[1]);
}
}
if (OPEN_WRITABLE_P(flags)) {
close(pw[1]);
if (pw[0] != 0) {
dup2(pw[0], 0);
close(pw[0]);
}
}
if (doexec) {
for (fd = 3; fd < NOFILE; fd++) {
close(fd);
}
mrb_proc_exec(cmd);
mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", cmd);
_exit(127);
}
result = mrb_nil_value();
break;
default: /* parent */
if (OPEN_RDWR_P(flags)) {
close(pr[1]);
fd = pr[0];
close(pw[0]);
write_fd = pw[1];
} else if (OPEN_RDONLY_P(flags)) {
close(pr[1]);
fd = pr[0];
} else {
close(pw[0]);
fd = pw[1];
}
mrb_iv_set(mrb, io, mrb_intern_lit(mrb, "@buf"), mrb_str_new_cstr(mrb, ""));
fptr = mrb_io_alloc(mrb);
fptr->fd = fd;
fptr->fd2 = write_fd;
fptr->pid = pid;
fptr->readable = OPEN_READABLE_P(flags);
fptr->writable = OPEN_WRITABLE_P(flags);
fptr->sync = 0;
DATA_TYPE(io) = &mrb_io_type;
DATA_PTR(io) = fptr;
result = io;
break;
case -1: /* error */
saved_errno = errno;
if (OPEN_READABLE_P(flags)) {
close(pr[0]);
close(pr[1]);
}
if (OPEN_WRITABLE_P(flags)) {
close(pw[0]);
close(pw[1]);
}
errno = saved_errno;
mrb_sys_fail(mrb, "pipe_open failed");
break;
}
return result;
}
#endif /* _WIN32 */
#endif /* TARGET_OS_IPHONE */
static int
mrb_dup(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
mrb_io_initialize_copy(mrb_state *mrb, mrb_value copy)
{
mrb_value orig = mrb_get_arg1(mrb);
mrb_value buf;
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 *)mrb_io_alloc(mrb);
DATA_TYPE(copy) = &mrb_io_type;
DATA_PTR(copy) = fptr_copy;
buf = mrb_iv_get(mrb, orig, mrb_intern_lit(mrb, "@buf"));
mrb_iv_set(mrb, copy, mrb_intern_lit(mrb, "@buf"), buf);
fptr_copy->fd = mrb_dup(mrb, fptr_orig->fd, &failed);
if (failed) {
mrb_sys_fail(mrb, 0);
}
mrb_fd_cloexec(mrb, fptr_copy->fd);
if (fptr_orig->fd2 != -1) {
fptr_copy->fd2 = mrb_dup(mrb, fptr_orig->fd2, &failed);
if (failed) {
close(fptr_copy->fd);
mrb_sys_fail(mrb, 0);
}
mrb_fd_cloexec(mrb, fptr_copy->fd2);
}
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;
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) {
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()) {
goto badfd;
}
#endif /* _WIN32 */
if (fstat(fdi, &sb) != 0) {
goto badfd;
}
return;
badfd:
mrb_sys_fail(mrb, "bad file descriptor");
}
static mrb_value
mrb_io_initialize(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
mrb_int fd;
mrb_value mode, opt;
int flags;
mode = opt = mrb_nil_value();
mrb_get_args(mrb, "i|oo", &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;
}
if (mrb_nil_p(mode)) {
mode = mrb_str_new_cstr(mrb, "r");
}
if (mrb_nil_p(opt)) {
opt = mrb_hash_new(mrb);
}
flags = mrb_io_mode_to_flags(mrb, mode);
mrb_iv_set(mrb, io, mrb_intern_lit(mrb, "@buf"), mrb_str_new_cstr(mrb, ""));
fptr = (struct mrb_io *)DATA_PTR(io);
if (fptr != NULL) {
fptr_finalize(mrb, fptr, TRUE);
mrb_free(mrb, fptr);
}
fptr = mrb_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);
fptr->sync = 0;
return io;
}
static void
fptr_finalize(mrb_state *mrb, struct mrb_io *fptr, int quiet)
{
int saved_errno = 0;
if (fptr == NULL) {
return;
}
if (fptr->fd > 2) {
#ifdef _WIN32
if (fptr->is_socket) {
if (closesocket(fptr->fd) != 0) {
saved_errno = WSAGetLastError();
}
fptr->fd = -1;
}
#endif
if (fptr->fd != -1) {
if (close(fptr->fd) == -1) {
saved_errno = errno;
}
}
fptr->fd = -1;
}
if (fptr->fd2 > 2) {
if (close(fptr->fd2) == -1) {
if (saved_errno == 0) {
saved_errno = errno;
}
}
fptr->fd2 = -1;
}
if (fptr->pid != 0) {
#if !defined(_WIN32) && !defined(_WIN64)
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 */
}
if (!quiet && saved_errno != 0) {
errno = saved_errno;
mrb_sys_fail(mrb, "fptr_finalize failed");
}
}
static mrb_value
mrb_io_check_readable(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr = io_get_open_fptr(mrb, self);
if (! fptr->readable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for reading");
}
return mrb_nil_value();
}
static mrb_value
mrb_io_isatty(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
fptr = io_get_open_fptr(mrb, self);
if (isatty(fptr->fd) == 0)
return mrb_false_value();
return mrb_true_value();
}
static mrb_value
mrb_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);
mrb_value obj;
/* copied from mrb_instance_alloc() */
if (ttype == 0) ttype = MRB_TT_OBJECT;
obj = mrb_obj_value((struct RObject*)mrb_obj_alloc(mrb, ttype, c));
return mrb_io_initialize(mrb, obj);
}
static mrb_value
mrb_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
mrb_cloexec_open(mrb_state *mrb, const char *pathname, mrb_int flags, mrb_int mode)
{
int fd, retry = FALSE;
char* fname = mrb_locale_from_utf8(pathname, -1);
#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, (int)flags, (fmode_t)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) {
mrb_fd_cloexec(mrb, fd);
}
return fd;
}
static mrb_value
mrb_io_s_sysopen(mrb_state *mrb, mrb_value klass)
{
mrb_value path = mrb_nil_value();
mrb_value mode = mrb_nil_value();
mrb_int fd, perm = -1;
const char *pat;
int flags;
mrb_get_args(mrb, "S|oi", &path, &mode, &perm);
if (perm < 0) {
perm = 0666;
}
pat = RSTRING_CSTR(mrb, path);
flags = mrb_io_mode_to_flags(mrb, mode);
fd = mrb_cloexec_open(mrb, pat, flags, perm);
return mrb_fixnum_value(fd);
}
static mrb_value mrb_io_sysread_common(mrb_state *mrb,
mrb_io_read_write_size (*readfunc)(int, void *, fsize_t, off_t),
mrb_value io, mrb_value buf, mrb_int maxlen, off_t offset);
static mrb_io_read_write_size
mrb_sysread_dummy(int fd, void *buf, fsize_t nbytes, off_t offset)
{
return (mrb_io_read_write_size)read(fd, buf, nbytes);
}
static mrb_value
mrb_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 mrb_io_sysread_common(mrb, mrb_sysread_dummy, io, buf, maxlen, 0);
}
static mrb_value
mrb_io_sysread_common(mrb_state *mrb,
mrb_io_read_write_size (*readfunc)(int, void *, fsize_t, off_t),
mrb_value io, mrb_value buf, mrb_int maxlen, off_t offset)
{
struct mrb_io *fptr;
int ret;
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));
}
fptr = (struct mrb_io *)io_get_open_fptr(mrb, io);
if (!fptr->readable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for reading");
}
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) {
mrb_raise(mrb, E_EOF_ERROR, "sysread failed: End of File");
}
return buf;
}
static mrb_value
mrb_io_sysseek(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
off_t pos;
mrb_int offset, whence = -1;
mrb_get_args(mrb, "i|i", &offset, &whence);
if (whence < 0) {
whence = 0;
}
fptr = io_get_open_fptr(mrb, io);
pos = lseek(fptr->fd, (off_t)offset, (int)whence);
if (pos == -1) {
mrb_sys_fail(mrb, "sysseek");
}
if (pos > MRB_INT_MAX) {
#ifndef MRB_NO_FLOAT
return mrb_float_value(mrb, (mrb_float)pos);
#else
mrb_raise(mrb, E_IO_ERROR, "sysseek reached too far for MRB_NO_FLOAT");
#endif
} else {
return mrb_int_value(mrb, pos);
}
}
static mrb_value
mrb_io_syswrite_common(mrb_state *mrb,
mrb_io_read_write_size (*writefunc)(int, const void *, fsize_t, off_t),
mrb_value io, mrb_value buf, off_t offset)
{
struct mrb_io *fptr;
int fd, length;
fptr = io_get_open_fptr(mrb, io);
if (! fptr->writable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for writing");
}
if (fptr->fd2 == -1) {
fd = fptr->fd;
} else {
fd = fptr->fd2;
}
length = writefunc(fd, RSTRING_PTR(buf), (fsize_t)RSTRING_LEN(buf), offset);
if (length == -1) {
mrb_sys_fail(mrb, 0);
}
return mrb_int_value(mrb, (mrb_int)length);
}
static mrb_io_read_write_size
mrb_syswrite_dummy(int fd, const void *buf, fsize_t nbytes, off_t offset)
{
return (mrb_io_read_write_size)write(fd, buf, nbytes);
}
static mrb_value
mrb_io_syswrite(mrb_state *mrb, mrb_value io)
{
mrb_value buf;
mrb_get_args(mrb, "S", &buf);
return mrb_io_syswrite_common(mrb, mrb_syswrite_dummy, io, buf, 0);
}
static mrb_value
mrb_io_close(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
fptr = io_get_open_fptr(mrb, self);
fptr_finalize(mrb, fptr, FALSE);
return mrb_nil_value();
}
static mrb_value
mrb_io_close_write(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
fptr = io_get_open_fptr(mrb, self);
if (close((int)fptr->fd2) == -1) {
mrb_sys_fail(mrb, "close");
}
return mrb_nil_value();
}
static mrb_value
mrb_io_closed(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
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
mrb_io_pid(mrb_state *mrb, mrb_value io)
{
struct mrb_io *fptr;
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;
}
static int
mrb_io_read_data_pending(mrb_state *mrb, mrb_value io)
{
mrb_value buf = mrb_iv_get(mrb, io, mrb_intern_lit(mrb, "@buf"));
if (mrb_string_p(buf) && RSTRING_LEN(buf) > 0) {
return 1;
}
return 0;
}
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
static mrb_value
mrb_io_s_pipe(mrb_state *mrb, mrb_value klass)
{
mrb_value r = mrb_nil_value();
mrb_value w = mrb_nil_value();
struct mrb_io *fptr_r;
struct mrb_io *fptr_w;
int pipes[2];
if (mrb_pipe(mrb, pipes) == -1) {
mrb_sys_fail(mrb, "pipe");
}
r = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
mrb_iv_set(mrb, r, mrb_intern_lit(mrb, "@buf"), mrb_str_new_cstr(mrb, ""));
fptr_r = mrb_io_alloc(mrb);
fptr_r->fd = pipes[0];
fptr_r->readable = 1;
fptr_r->writable = 0;
fptr_r->sync = 0;
DATA_TYPE(r) = &mrb_io_type;
DATA_PTR(r) = fptr_r;
w = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type));
mrb_iv_set(mrb, w, mrb_intern_lit(mrb, "@buf"), mrb_str_new_cstr(mrb, ""));
fptr_w = mrb_io_alloc(mrb);
fptr_w->fd = pipes[1];
fptr_w->readable = 0;
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 mrb_value
mrb_io_s_select(mrb_state *mrb, mrb_value klass)
{
const mrb_value *argv;
mrb_int argc;
mrb_value read, read_io, write, except, timeout, list;
struct timeval *tp, timerec;
fd_set pset, rset, wset, eset;
fd_set *rp, *wp, *ep;
struct mrb_io *fptr;
int pending = 0;
mrb_value result;
int max = 0;
int interrupt_flag = 0;
int i, n;
mrb_get_args(mrb, "*", &argv, &argc);
if (argc < 1 || argc > 4) {
mrb_argnum_error(mrb, argc, 1, 4);
}
timeout = mrb_nil_value();
except = mrb_nil_value();
write = mrb_nil_value();
if (argc > 3)
timeout = argv[3];
if (argc > 2)
except = argv[2];
if (argc > 1)
write = argv[1];
read = argv[0];
if (mrb_nil_p(timeout)) {
tp = NULL;
} else {
timerec = time2timeval(mrb, timeout);
tp = &timerec;
}
FD_ZERO(&pset);
if (!mrb_nil_p(read)) {
mrb_check_type(mrb, read, MRB_TT_ARRAY);
rp = &rset;
FD_ZERO(rp);
for (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, read_io)) {
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;
}
if (!mrb_nil_p(write)) {
mrb_check_type(mrb, write, MRB_TT_ARRAY);
wp = &wset;
FD_ZERO(wp);
for (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;
}
if (!mrb_nil_p(except)) {
mrb_check_type(mrb, except, MRB_TT_ARRAY);
ep = &eset;
FD_ZERO(ep);
for (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++;
retry:
n = select(max, rp, wp, ep, tp);
if (n < 0) {
if (errno != EINTR)
mrb_sys_fail(mrb, "select failed");
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 (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 (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 (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;
fptr = io_get_open_fptr(mrb, io);
return fptr->fd;
}
static mrb_value
mrb_io_fileno_m(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)
static mrb_value
mrb_io_close_on_exec_p(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
int ret;
fptr = io_get_open_fptr(mrb, self);
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 mrb_io_close_on_exec_p mrb_notimplement_m
#endif
#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
static mrb_value
mrb_io_set_close_on_exec(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
int flag, ret;
mrb_bool b;
fptr = io_get_open_fptr(mrb, self);
mrb_get_args(mrb, "b", &b);
flag = b ? FD_CLOEXEC : 0;
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 mrb_io_set_close_on_exec mrb_notimplement_m
#endif
static mrb_value
mrb_io_set_sync(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
mrb_bool b;
fptr = io_get_open_fptr(mrb, self);
mrb_get_args(mrb, "b", &b);
fptr->sync = b;
return mrb_bool_value(b);
}
static mrb_value
mrb_io_sync(mrb_state *mrb, mrb_value self)
{
struct mrb_io *fptr;
fptr = io_get_open_fptr(mrb, self);
return mrb_bool_value(fptr->sync);
}
#ifndef MRB_WITH_IO_PREAD_PWRITE
# define mrb_io_pread mrb_notimplement_m
# define mrb_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
mrb_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 mrb_io_sysread_common(mrb, pread, io, buf, maxlen, value2off(mrb, off));
}
/*
* call-seq:
* pwrite(buffer, offset) -> wrote_bytes
*/
static mrb_value
mrb_io_pwrite(mrb_state *mrb, mrb_value io)
{
mrb_value buf, off;
mrb_get_args(mrb, "So", &buf, &off);
return mrb_io_syswrite_common(mrb, pwrite, io, buf, value2off(mrb, off));
}
#endif /* MRB_WITH_IO_PREAD_PWRITE */
static mrb_value
io_bufread(mrb_state *mrb, mrb_value str, mrb_int len)
{
mrb_value str2;
mrb_int newlen;
struct RString *s;
char *p;
s = RSTRING(str);
mrb_str_modify(mrb, s);
p = RSTR_PTR(s);
str2 = mrb_str_new(mrb, p, len);
newlen = RSTR_LEN(s)-len;
memmove(p, p+len, newlen);
p[newlen] = '\0';
RSTR_SET_LEN(s, newlen);
return str2;
}
static mrb_value
mrb_io_bufread(mrb_state *mrb, mrb_value self)
{
mrb_value str;
mrb_int len;
mrb->c->ci->mid = 0;
mrb_get_args(mrb, "Si", &str, &len);
mrb_assert(RSTRING_LEN(str) > 0);
mrb_assert(RSTRING_PTR(str) != NULL);
mrb_str_modify(mrb, RSTRING(str));
return io_bufread(mrb, str, len);
}
static mrb_value
mrb_io_readchar(mrb_state *mrb, mrb_value self)
{
mrb_value buf;
mrb_int len = 1;
#ifdef MRB_UTF8_STRING
unsigned char c;
#endif
mrb->c->ci->mid = 0;
mrb_get_args(mrb, "S", &buf);
mrb_assert(RSTRING_LEN(buf) > 0);
mrb_assert(RSTRING_PTR(buf) != NULL);
mrb_str_modify(mrb, RSTRING(buf));
#ifdef MRB_UTF8_STRING
c = RSTRING_PTR(buf)[0];
if (c & 0x80) {
len = mrb_utf8len(RSTRING_PTR(buf), RSTRING_END(buf));
if (len == 1 && RSTRING_LEN(buf) < 4) { /* partial UTF-8 */
mrb_int blen = RSTRING_LEN(buf);
ssize_t n;
struct mrb_io *fptr = (struct mrb_io*)io_get_open_fptr(mrb, self);
if (!fptr->readable) {
mrb_raise(mrb, E_IO_ERROR, "not opened for reading");
}
/* refill the buffer */
mrb_str_resize(mrb, buf, 4096);
n = read(fptr->fd, RSTRING_PTR(buf)+blen, 4096-blen);
if (n < 0) mrb_sys_fail(mrb, "sysread failed");
mrb_str_resize(mrb, buf, blen+n);
}
len = mrb_utf8len(RSTRING_PTR(buf), RSTRING_END(buf));
}
#endif
return io_bufread(mrb, buf, len);
}
void
mrb_init_io(mrb_state *mrb)
{
struct RClass *io;
io = mrb_define_class(mrb, "IO", mrb->object_class);
MRB_SET_INSTANCE_TT(io, MRB_TT_DATA);
mrb_include_module(mrb, io, mrb_module_get(mrb, "Enumerable")); /* 15.2.20.3 */
mrb_define_class_method(mrb, io, "_popen", mrb_io_s_popen, MRB_ARGS_ARG(1,2));
mrb_define_class_method(mrb, io, "_sysclose", mrb_io_s_sysclose, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, io, "for_fd", mrb_io_s_for_fd, MRB_ARGS_ARG(1,2));
mrb_define_class_method(mrb, io, "select", mrb_io_s_select, MRB_ARGS_ARG(1,3));
mrb_define_class_method(mrb, io, "sysopen", mrb_io_s_sysopen, MRB_ARGS_ARG(1,2));
#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
mrb_define_class_method(mrb, io, "_pipe", mrb_io_s_pipe, MRB_ARGS_NONE());
#endif
mrb_define_method(mrb, io, "initialize", mrb_io_initialize, MRB_ARGS_ARG(1,2)); /* 15.2.20.5.21 (x)*/
mrb_define_method(mrb, io, "initialize_copy", mrb_io_initialize_copy, MRB_ARGS_REQ(1));
mrb_define_method(mrb, io, "_check_readable", mrb_io_check_readable, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "isatty", mrb_io_isatty, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "sync", mrb_io_sync, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "sync=", mrb_io_set_sync, MRB_ARGS_REQ(1));
mrb_define_method(mrb, io, "sysread", mrb_io_sysread, MRB_ARGS_ARG(1,1));
mrb_define_method(mrb, io, "sysseek", mrb_io_sysseek, MRB_ARGS_ARG(1,1));
mrb_define_method(mrb, io, "syswrite", mrb_io_syswrite, MRB_ARGS_REQ(1));
mrb_define_method(mrb, io, "close", mrb_io_close, MRB_ARGS_NONE()); /* 15.2.20.5.1 */
mrb_define_method(mrb, io, "close_write", mrb_io_close_write, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "close_on_exec=", mrb_io_set_close_on_exec, MRB_ARGS_REQ(1));
mrb_define_method(mrb, io, "close_on_exec?", mrb_io_close_on_exec_p, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "closed?", mrb_io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */
mrb_define_method(mrb, io, "pid", mrb_io_pid, MRB_ARGS_NONE()); /* 15.2.20.5.2 */
mrb_define_method(mrb, io, "fileno", mrb_io_fileno_m, MRB_ARGS_NONE());
mrb_define_method(mrb, io, "pread", mrb_io_pread, MRB_ARGS_ANY()); /* ruby 2.5 feature */
mrb_define_method(mrb, io, "pwrite", mrb_io_pwrite, MRB_ARGS_ANY()); /* ruby 2.5 feature */
mrb_define_method(mrb, io, "_readchar", mrb_io_readchar, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, io, "_bufread", mrb_io_bufread, MRB_ARGS_REQ(2));
}