Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
ruby/io.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
13690 lines (12335 sloc)
346 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************** | |
io.c - | |
$Author$ | |
created at: Fri Oct 15 18:08:59 JST 1993 | |
Copyright (C) 1993-2007 Yukihiro Matsumoto | |
Copyright (C) 2000 Network Applied Communication Laboratory, Inc. | |
Copyright (C) 2000 Information-technology Promotion Agency, Japan | |
**********************************************************************/ | |
#include "ruby/internal/config.h" | |
#include "internal/scheduler.h" | |
#ifdef _WIN32 | |
# include "ruby/ruby.h" | |
# include "ruby/io.h" | |
#endif | |
#include <ctype.h> | |
#include <errno.h> | |
#include <stddef.h> | |
/* non-Linux poll may not work on all FDs */ | |
#if defined(HAVE_POLL) | |
# if defined(__linux__) | |
# define USE_POLL 1 | |
# endif | |
# if defined(__FreeBSD_version) && __FreeBSD_version >= 1100000 | |
# define USE_POLL 1 | |
# endif | |
#endif | |
#ifndef USE_POLL | |
# define USE_POLL 0 | |
#endif | |
#undef free | |
#define free(x) xfree(x) | |
#if defined(DOSISH) || defined(__CYGWIN__) | |
#include <io.h> | |
#endif | |
#include <sys/types.h> | |
#if defined HAVE_NET_SOCKET_H | |
# include <net/socket.h> | |
#elif defined HAVE_SYS_SOCKET_H | |
# include <sys/socket.h> | |
#endif | |
#if defined(__BOW__) || defined(__CYGWIN__) || defined(_WIN32) | |
# define NO_SAFE_RENAME | |
#endif | |
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__sun) || defined(_nec_ews) | |
# define USE_SETVBUF | |
#endif | |
#ifdef __QNXNTO__ | |
#include <unix.h> | |
#endif | |
#include <sys/types.h> | |
#if defined(HAVE_SYS_IOCTL_H) && !defined(_WIN32) | |
#include <sys/ioctl.h> | |
#endif | |
#if defined(HAVE_FCNTL_H) || defined(_WIN32) | |
#include <fcntl.h> | |
#elif defined(HAVE_SYS_FCNTL_H) | |
#include <sys/fcntl.h> | |
#endif | |
#if !HAVE_OFF_T && !defined(off_t) | |
# define off_t long | |
#endif | |
#ifdef HAVE_SYS_TIME_H | |
# include <sys/time.h> | |
#endif | |
#include <sys/stat.h> | |
#if defined(HAVE_SYS_PARAM_H) || defined(__HIUX_MPP__) | |
# include <sys/param.h> | |
#endif | |
#if !defined NOFILE | |
# define NOFILE 64 | |
#endif | |
#ifdef HAVE_UNISTD_H | |
#include <unistd.h> | |
#endif | |
#ifdef HAVE_SYSCALL_H | |
#include <syscall.h> | |
#elif defined HAVE_SYS_SYSCALL_H | |
#include <sys/syscall.h> | |
#endif | |
#ifdef HAVE_SYS_UIO_H | |
#include <sys/uio.h> | |
#endif | |
#ifdef HAVE_SYS_WAIT_H | |
# include <sys/wait.h> /* for WNOHANG on BSD */ | |
#endif | |
#ifdef HAVE_COPYFILE_H | |
# include <copyfile.h> | |
#endif | |
#include "ruby/internal/stdbool.h" | |
#include "ccan/list/list.h" | |
#include "dln.h" | |
#include "encindex.h" | |
#include "id.h" | |
#include "internal.h" | |
#include "internal/encoding.h" | |
#include "internal/error.h" | |
#include "internal/inits.h" | |
#include "internal/io.h" | |
#include "internal/numeric.h" | |
#include "internal/object.h" | |
#include "internal/process.h" | |
#include "internal/thread.h" | |
#include "internal/transcode.h" | |
#include "internal/variable.h" | |
#include "ruby/io.h" | |
#include "ruby/thread.h" | |
#include "ruby/util.h" | |
#include "ruby_atomic.h" | |
#include "ruby/ractor.h" | |
#if !USE_POLL | |
# include "vm_core.h" | |
#endif | |
#include "builtin.h" | |
#ifndef O_ACCMODE | |
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) | |
#endif | |
#if SIZEOF_OFF_T > SIZEOF_LONG && !defined(HAVE_LONG_LONG) | |
# error off_t is bigger than long, but you have no long long... | |
#endif | |
#ifndef PIPE_BUF | |
# ifdef _POSIX_PIPE_BUF | |
# define PIPE_BUF _POSIX_PIPE_BUF | |
# else | |
# define PIPE_BUF 512 /* is this ok? */ | |
# endif | |
#endif | |
#ifndef EWOULDBLOCK | |
# define EWOULDBLOCK EAGAIN | |
#endif | |
#if defined(HAVE___SYSCALL) && (defined(__APPLE__) || defined(__OpenBSD__)) | |
/* Mac OS X and OpenBSD have __syscall but don't define it in headers */ | |
off_t __syscall(quad_t number, ...); | |
#endif | |
#define IO_RBUF_CAPA_MIN 8192 | |
#define IO_CBUF_CAPA_MIN (128*1024) | |
#define IO_RBUF_CAPA_FOR(fptr) (NEED_READCONV(fptr) ? IO_CBUF_CAPA_MIN : IO_RBUF_CAPA_MIN) | |
#define IO_WBUF_CAPA_MIN 8192 | |
/* define system APIs */ | |
#ifdef _WIN32 | |
#undef open | |
#define open rb_w32_uopen | |
#undef rename | |
#define rename(f, t) rb_w32_urename((f), (t)) | |
#endif | |
VALUE rb_cIO; | |
VALUE rb_eEOFError; | |
VALUE rb_eIOError; | |
VALUE rb_mWaitReadable; | |
VALUE rb_mWaitWritable; | |
static VALUE rb_eEAGAINWaitReadable; | |
static VALUE rb_eEAGAINWaitWritable; | |
static VALUE rb_eEWOULDBLOCKWaitReadable; | |
static VALUE rb_eEWOULDBLOCKWaitWritable; | |
static VALUE rb_eEINPROGRESSWaitWritable; | |
static VALUE rb_eEINPROGRESSWaitReadable; | |
VALUE rb_stdin, rb_stdout, rb_stderr; | |
static VALUE orig_stdout, orig_stderr; | |
VALUE rb_output_fs; | |
VALUE rb_rs; | |
VALUE rb_output_rs; | |
VALUE rb_default_rs; | |
static VALUE argf; | |
static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; | |
static VALUE sym_mode, sym_perm, sym_flags, sym_extenc, sym_intenc, sym_encoding, sym_open_args; | |
static VALUE sym_textmode, sym_binmode, sym_autoclose; | |
static VALUE sym_SET, sym_CUR, sym_END; | |
static VALUE sym_wait_readable, sym_wait_writable; | |
#ifdef SEEK_DATA | |
static VALUE sym_DATA; | |
#endif | |
#ifdef SEEK_HOLE | |
static VALUE sym_HOLE; | |
#endif | |
static VALUE rb_io_initialize(int argc, VALUE *argv, VALUE io); | |
static VALUE prep_io(int fd, int fmode, VALUE klass, const char *path); | |
struct argf { | |
VALUE filename, current_file; | |
long last_lineno; /* $. */ | |
long lineno; | |
VALUE argv; | |
VALUE inplace; | |
struct rb_io_enc_t encs; | |
int8_t init_p, next_p, binmode; | |
}; | |
static rb_atomic_t max_file_descriptor = NOFILE; | |
void | |
rb_update_max_fd(int fd) | |
{ | |
rb_atomic_t afd = (rb_atomic_t)fd; | |
rb_atomic_t max_fd = max_file_descriptor; | |
int err; | |
if (fd < 0 || afd <= max_fd) | |
return; | |
#if defined(HAVE_FCNTL) && defined(F_GETFL) | |
err = fcntl(fd, F_GETFL) == -1; | |
#else | |
{ | |
struct stat buf; | |
err = fstat(fd, &buf) != 0; | |
} | |
#endif | |
if (err && errno == EBADF) { | |
rb_bug("rb_update_max_fd: invalid fd (%d) given.", fd); | |
} | |
while (max_fd < afd) { | |
max_fd = ATOMIC_CAS(max_file_descriptor, max_fd, afd); | |
} | |
} | |
void | |
rb_maygvl_fd_fix_cloexec(int fd) | |
{ | |
/* MinGW don't have F_GETFD and FD_CLOEXEC. [ruby-core:40281] */ | |
#if defined(HAVE_FCNTL) && defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) | |
int flags, flags2, ret; | |
flags = fcntl(fd, F_GETFD); /* should not fail except EBADF. */ | |
if (flags == -1) { | |
rb_bug("rb_maygvl_fd_fix_cloexec: fcntl(%d, F_GETFD) failed: %s", fd, strerror(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) { | |
ret = fcntl(fd, F_SETFD, flags2); | |
if (ret != 0) { | |
rb_bug("rb_maygvl_fd_fix_cloexec: fcntl(%d, F_SETFD, %d) failed: %s", fd, flags2, strerror(errno)); | |
} | |
} | |
#endif | |
} | |
void | |
rb_fd_fix_cloexec(int fd) | |
{ | |
rb_maygvl_fd_fix_cloexec(fd); | |
rb_update_max_fd(fd); | |
} | |
/* this is only called once */ | |
static int | |
rb_fix_detect_o_cloexec(int fd) | |
{ | |
#if defined(O_CLOEXEC) && defined(F_GETFD) | |
int flags = fcntl(fd, F_GETFD); | |
if (flags == -1) | |
rb_bug("rb_fix_detect_o_cloexec: fcntl(%d, F_GETFD) failed: %s", fd, strerror(errno)); | |
if (flags & FD_CLOEXEC) | |
return 1; | |
#endif /* fall through if O_CLOEXEC does not work: */ | |
rb_maygvl_fd_fix_cloexec(fd); | |
return 0; | |
} | |
int | |
rb_cloexec_open(const char *pathname, int flags, mode_t mode) | |
{ | |
int ret; | |
static int o_cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */ | |
static const int retry_interval = 0; | |
static const int retry_max_count = 10000; | |
int retry_count = 0; | |
#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 | |
while ((ret = open(pathname, flags, mode)) == -1) { | |
int e = errno; | |
if (e != EAGAIN && e != EWOULDBLOCK) break; | |
if (retry_count++ >= retry_max_count) break; | |
sleep(retry_interval); | |
} | |
if (ret < 0) return ret; | |
if (ret <= 2 || o_cloexec_state == 0) { | |
rb_maygvl_fd_fix_cloexec(ret); | |
} | |
else if (o_cloexec_state > 0) { | |
return ret; | |
} | |
else { | |
o_cloexec_state = rb_fix_detect_o_cloexec(ret); | |
} | |
return ret; | |
} | |
int | |
rb_cloexec_dup(int oldfd) | |
{ | |
/* Don't allocate standard file descriptors: 0, 1, 2 */ | |
return rb_cloexec_fcntl_dupfd(oldfd, 3); | |
} | |
int | |
rb_cloexec_dup2(int oldfd, int newfd) | |
{ | |
int ret; | |
/* When oldfd == newfd, dup2 succeeds but dup3 fails with EINVAL. | |
* rb_cloexec_dup2 succeeds as dup2. */ | |
if (oldfd == newfd) { | |
ret = newfd; | |
} | |
else { | |
#if defined(HAVE_DUP3) && defined(O_CLOEXEC) | |
static int try_dup3 = 1; | |
if (2 < newfd && try_dup3) { | |
ret = dup3(oldfd, newfd, O_CLOEXEC); | |
if (ret != -1) | |
return ret; | |
/* dup3 is available since Linux 2.6.27, glibc 2.9. */ | |
if (errno == ENOSYS) { | |
try_dup3 = 0; | |
ret = dup2(oldfd, newfd); | |
} | |
} | |
else { | |
ret = dup2(oldfd, newfd); | |
} | |
#else | |
ret = dup2(oldfd, newfd); | |
#endif | |
if (ret < 0) return ret; | |
} | |
rb_maygvl_fd_fix_cloexec(ret); | |
return ret; | |
} | |
static int | |
rb_fd_set_nonblock(int fd) | |
{ | |
#ifdef _WIN32 | |
return rb_w32_set_nonblock(fd); | |
#elif defined(F_GETFL) | |
int oflags = fcntl(fd, F_GETFL); | |
if (oflags == -1) | |
return -1; | |
if (oflags & O_NONBLOCK) | |
return 0; | |
oflags |= O_NONBLOCK; | |
return fcntl(fd, F_SETFL, oflags); | |
#endif | |
return 0; | |
} | |
int | |
rb_cloexec_pipe(int descriptors[2]) | |
{ | |
#ifdef HAVE_PIPE2 | |
int result = pipe2(descriptors, O_CLOEXEC | O_NONBLOCK); | |
#else | |
int result = pipe(descriptors); | |
#endif | |
if (result < 0) | |
return result; | |
#ifdef __CYGWIN__ | |
if (result == 0 && descriptors[1] == -1) { | |
close(descriptors[0]); | |
descriptors[0] = -1; | |
errno = ENFILE; | |
return -1; | |
} | |
#endif | |
#ifndef HAVE_PIPE2 | |
rb_maygvl_fd_fix_cloexec(descriptors[0]); | |
rb_maygvl_fd_fix_cloexec(descriptors[1]); | |
#ifndef _WIN32 | |
rb_fd_set_nonblock(descriptors[0]); | |
rb_fd_set_nonblock(descriptors[1]); | |
#endif | |
#endif | |
return result; | |
} | |
int | |
rb_cloexec_fcntl_dupfd(int fd, int minfd) | |
{ | |
int ret; | |
#if defined(HAVE_FCNTL) && defined(F_DUPFD_CLOEXEC) && defined(F_DUPFD) | |
static int try_dupfd_cloexec = 1; | |
if (try_dupfd_cloexec) { | |
ret = fcntl(fd, F_DUPFD_CLOEXEC, minfd); | |
if (ret != -1) { | |
if (ret <= 2) | |
rb_maygvl_fd_fix_cloexec(ret); | |
return ret; | |
} | |
/* F_DUPFD_CLOEXEC is available since Linux 2.6.24. Linux 2.6.18 fails with EINVAL */ | |
if (errno == EINVAL) { | |
ret = fcntl(fd, F_DUPFD, minfd); | |
if (ret != -1) { | |
try_dupfd_cloexec = 0; | |
} | |
} | |
} | |
else { | |
ret = fcntl(fd, F_DUPFD, minfd); | |
} | |
#elif defined(HAVE_FCNTL) && defined(F_DUPFD) | |
ret = fcntl(fd, F_DUPFD, minfd); | |
#elif defined(HAVE_DUP) | |
ret = dup(fd); | |
if (ret >= 0 && ret < minfd) { | |
const int prev_fd = ret; | |
ret = rb_cloexec_fcntl_dupfd(fd, minfd); | |
close(prev_fd); | |
} | |
return ret; | |
#else | |
# error "dup() or fcntl(F_DUPFD) must be supported." | |
#endif | |
if (ret < 0) return ret; | |
rb_maygvl_fd_fix_cloexec(ret); | |
return ret; | |
} | |
#define argf_of(obj) (*(struct argf *)DATA_PTR(obj)) | |
#define ARGF argf_of(argf) | |
#define GetWriteIO(io) rb_io_get_write_io(io) | |
#define READ_DATA_PENDING(fptr) ((fptr)->rbuf.len) | |
#define READ_DATA_PENDING_COUNT(fptr) ((fptr)->rbuf.len) | |
#define READ_DATA_PENDING_PTR(fptr) ((fptr)->rbuf.ptr+(fptr)->rbuf.off) | |
#define READ_DATA_BUFFERED(fptr) READ_DATA_PENDING(fptr) | |
#define READ_CHAR_PENDING(fptr) ((fptr)->cbuf.len) | |
#define READ_CHAR_PENDING_COUNT(fptr) ((fptr)->cbuf.len) | |
#define READ_CHAR_PENDING_PTR(fptr) ((fptr)->cbuf.ptr+(fptr)->cbuf.off) | |
#if defined(_WIN32) | |
#define WAIT_FD_IN_WIN32(fptr) \ | |
(rb_w32_io_cancelable_p((fptr)->fd) ? 0 : rb_thread_wait_fd((fptr)->fd)) | |
#else | |
#define WAIT_FD_IN_WIN32(fptr) | |
#endif | |
#define READ_CHECK(fptr) do {\ | |
if (!READ_DATA_PENDING(fptr)) {\ | |
WAIT_FD_IN_WIN32(fptr);\ | |
rb_io_check_closed(fptr);\ | |
}\ | |
} while(0) | |
#ifndef S_ISSOCK | |
# ifdef _S_ISSOCK | |
# define S_ISSOCK(m) _S_ISSOCK(m) | |
# else | |
# ifdef _S_IFSOCK | |
# define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK) | |
# else | |
# ifdef S_IFSOCK | |
# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) | |
# endif | |
# endif | |
# endif | |
#endif | |
static int io_fflush(rb_io_t *); | |
static rb_io_t *flush_before_seek(rb_io_t *fptr); | |
#define FMODE_PREP (1<<16) | |
#define FMODE_SIGNAL_ON_EPIPE (1<<17) | |
#define fptr_signal_on_epipe(fptr) \ | |
(((fptr)->mode & FMODE_SIGNAL_ON_EPIPE) != 0) | |
#define fptr_set_signal_on_epipe(fptr, flag) \ | |
((flag) ? \ | |
(fptr)->mode |= FMODE_SIGNAL_ON_EPIPE : \ | |
(fptr)->mode &= ~FMODE_SIGNAL_ON_EPIPE) | |
extern ID ruby_static_id_signo; | |
NORETURN(static void raise_on_write(rb_io_t *fptr, int e, VALUE errinfo)); | |
static void | |
raise_on_write(rb_io_t *fptr, int e, VALUE errinfo) | |
{ | |
#if defined EPIPE | |
if (fptr_signal_on_epipe(fptr) && (e == EPIPE)) { | |
const VALUE sig = | |
# if defined SIGPIPE | |
INT2FIX(SIGPIPE) - INT2FIX(0) + | |
# endif | |
INT2FIX(0); | |
rb_ivar_set(errinfo, ruby_static_id_signo, sig); | |
} | |
#endif | |
rb_exc_raise(errinfo); | |
} | |
#define rb_sys_fail_on_write(fptr) \ | |
do { \ | |
int e = errno; \ | |
raise_on_write(fptr, e, rb_syserr_new_path(e, (fptr)->pathv)); \ | |
} while (0) | |
#define NEED_NEWLINE_DECORATOR_ON_READ(fptr) ((fptr)->mode & FMODE_TEXTMODE) | |
#define NEED_NEWLINE_DECORATOR_ON_WRITE(fptr) ((fptr)->mode & FMODE_TEXTMODE) | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
/* Windows */ | |
# define DEFAULT_TEXTMODE FMODE_TEXTMODE | |
# define TEXTMODE_NEWLINE_DECORATOR_ON_WRITE ECONV_CRLF_NEWLINE_DECORATOR | |
/* | |
* CRLF newline is set as default newline decorator. | |
* If only CRLF newline conversion is needed, we use binary IO process | |
* with OS's text mode for IO performance improvement. | |
* If encoding conversion is needed or a user sets text mode, we use encoding | |
* conversion IO process and universal newline decorator by default. | |
*/ | |
#define NEED_READCONV(fptr) ((fptr)->encs.enc2 != NULL || (fptr)->encs.ecflags & ~ECONV_CRLF_NEWLINE_DECORATOR) | |
#define WRITECONV_MASK ( \ | |
(ECONV_DECORATOR_MASK & ~ECONV_CRLF_NEWLINE_DECORATOR)|\ | |
ECONV_STATEFUL_DECORATOR_MASK|\ | |
0) | |
#define NEED_WRITECONV(fptr) ( \ | |
((fptr)->encs.enc != NULL && (fptr)->encs.enc != rb_ascii8bit_encoding()) || \ | |
((fptr)->encs.ecflags & WRITECONV_MASK) || \ | |
0) | |
#define SET_BINARY_MODE(fptr) setmode((fptr)->fd, O_BINARY) | |
#define NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr) do {\ | |
if (NEED_NEWLINE_DECORATOR_ON_READ(fptr)) {\ | |
if (((fptr)->mode & FMODE_READABLE) &&\ | |
!((fptr)->encs.ecflags & ECONV_NEWLINE_DECORATOR_MASK)) {\ | |
setmode((fptr)->fd, O_BINARY);\ | |
}\ | |
else {\ | |
setmode((fptr)->fd, O_TEXT);\ | |
}\ | |
}\ | |
} while(0) | |
#define SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags) do {\ | |
if ((enc2) && ((ecflags) & ECONV_DEFAULT_NEWLINE_DECORATOR)) {\ | |
(ecflags) |= ECONV_UNIVERSAL_NEWLINE_DECORATOR;\ | |
}\ | |
} while(0) | |
/* | |
* IO unread with taking care of removed '\r' in text mode. | |
*/ | |
static void | |
io_unread(rb_io_t *fptr) | |
{ | |
off_t r, pos; | |
ssize_t read_size; | |
long i; | |
long newlines = 0; | |
long extra_max; | |
char *p; | |
char *buf; | |
rb_io_check_closed(fptr); | |
if (fptr->rbuf.len == 0 || fptr->mode & FMODE_DUPLEX) { | |
return; | |
} | |
errno = 0; | |
if (!rb_w32_fd_is_text(fptr->fd)) { | |
r = lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR); | |
if (r < 0 && errno) { | |
if (errno == ESPIPE) | |
fptr->mode |= FMODE_DUPLEX; | |
return; | |
} | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = 0; | |
return; | |
} | |
pos = lseek(fptr->fd, 0, SEEK_CUR); | |
if (pos < 0 && errno) { | |
if (errno == ESPIPE) | |
fptr->mode |= FMODE_DUPLEX; | |
return; | |
} | |
/* add extra offset for removed '\r' in rbuf */ | |
extra_max = (long)(pos - fptr->rbuf.len); | |
p = fptr->rbuf.ptr + fptr->rbuf.off; | |
/* if the end of rbuf is '\r', rbuf doesn't have '\r' within rbuf.len */ | |
if (*(fptr->rbuf.ptr + fptr->rbuf.capa - 1) == '\r') { | |
newlines++; | |
} | |
for (i = 0; i < fptr->rbuf.len; i++) { | |
if (*p == '\n') newlines++; | |
if (extra_max == newlines) break; | |
p++; | |
} | |
buf = ALLOC_N(char, fptr->rbuf.len + newlines); | |
while (newlines >= 0) { | |
r = lseek(fptr->fd, pos - fptr->rbuf.len - newlines, SEEK_SET); | |
if (newlines == 0) break; | |
if (r < 0) { | |
newlines--; | |
continue; | |
} | |
read_size = _read(fptr->fd, buf, fptr->rbuf.len + newlines); | |
if (read_size < 0) { | |
int e = errno; | |
free(buf); | |
rb_syserr_fail_path(e, fptr->pathv); | |
} | |
if (read_size == fptr->rbuf.len) { | |
lseek(fptr->fd, r, SEEK_SET); | |
break; | |
} | |
else { | |
newlines--; | |
} | |
} | |
free(buf); | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = 0; | |
return; | |
} | |
/* | |
* We use io_seek to back cursor position when changing mode from text to binary, | |
* but stdin and pipe cannot seek back. Stdin and pipe read should use encoding | |
* conversion for working properly with mode change. | |
* | |
* Return previous translation mode. | |
*/ | |
static inline int | |
set_binary_mode_with_seek_cur(rb_io_t *fptr) | |
{ | |
if (!rb_w32_fd_is_text(fptr->fd)) return O_BINARY; | |
if (fptr->rbuf.len == 0 || fptr->mode & FMODE_DUPLEX) { | |
return setmode(fptr->fd, O_BINARY); | |
} | |
flush_before_seek(fptr); | |
return setmode(fptr->fd, O_BINARY); | |
} | |
#define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) set_binary_mode_with_seek_cur(fptr) | |
#else | |
/* Unix */ | |
# define DEFAULT_TEXTMODE 0 | |
#define NEED_READCONV(fptr) ((fptr)->encs.enc2 != NULL || NEED_NEWLINE_DECORATOR_ON_READ(fptr)) | |
#define NEED_WRITECONV(fptr) ( \ | |
((fptr)->encs.enc != NULL && (fptr)->encs.enc != rb_ascii8bit_encoding()) || \ | |
NEED_NEWLINE_DECORATOR_ON_WRITE(fptr) || \ | |
((fptr)->encs.ecflags & (ECONV_DECORATOR_MASK|ECONV_STATEFUL_DECORATOR_MASK)) || \ | |
0) | |
#define SET_BINARY_MODE(fptr) (void)(fptr) | |
#define NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr) (void)(fptr) | |
#define SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags) ((void)(enc2), (void)(ecflags)) | |
#define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) (void)(fptr) | |
#endif | |
#if !defined HAVE_SHUTDOWN && !defined shutdown | |
#define shutdown(a,b) 0 | |
#endif | |
#if defined(_WIN32) | |
#define is_socket(fd, path) rb_w32_is_socket(fd) | |
#elif !defined(S_ISSOCK) | |
#define is_socket(fd, path) 0 | |
#else | |
static int | |
is_socket(int fd, VALUE path) | |
{ | |
struct stat sbuf; | |
if (fstat(fd, &sbuf) < 0) | |
rb_sys_fail_path(path); | |
return S_ISSOCK(sbuf.st_mode); | |
} | |
#endif | |
static const char closed_stream[] = "closed stream"; | |
static void | |
io_fd_check_closed(int fd) | |
{ | |
if (fd < 0) { | |
rb_thread_check_ints(); /* check for ruby_error_stream_closed */ | |
rb_raise(rb_eIOError, closed_stream); | |
} | |
} | |
void | |
rb_eof_error(void) | |
{ | |
rb_raise(rb_eEOFError, "end of file reached"); | |
} | |
VALUE | |
rb_io_taint_check(VALUE io) | |
{ | |
rb_check_frozen(io); | |
return io; | |
} | |
void | |
rb_io_check_initialized(rb_io_t *fptr) | |
{ | |
if (!fptr) { | |
rb_raise(rb_eIOError, "uninitialized stream"); | |
} | |
} | |
void | |
rb_io_check_closed(rb_io_t *fptr) | |
{ | |
rb_io_check_initialized(fptr); | |
io_fd_check_closed(fptr->fd); | |
} | |
static rb_io_t * | |
rb_io_get_fptr(VALUE io) | |
{ | |
rb_io_t *fptr = RFILE(io)->fptr; | |
rb_io_check_initialized(fptr); | |
return fptr; | |
} | |
VALUE | |
rb_io_get_io(VALUE io) | |
{ | |
return rb_convert_type_with_id(io, T_FILE, "IO", idTo_io); | |
} | |
VALUE | |
rb_io_check_io(VALUE io) | |
{ | |
return rb_check_convert_type_with_id(io, T_FILE, "IO", idTo_io); | |
} | |
VALUE | |
rb_io_get_write_io(VALUE io) | |
{ | |
VALUE write_io; | |
write_io = rb_io_get_fptr(io)->tied_io_for_writing; | |
if (write_io) { | |
return write_io; | |
} | |
return io; | |
} | |
VALUE | |
rb_io_set_write_io(VALUE io, VALUE w) | |
{ | |
VALUE write_io; | |
rb_io_t *fptr = rb_io_get_fptr(io); | |
if (!RTEST(w)) { | |
w = 0; | |
} | |
else { | |
GetWriteIO(w); | |
} | |
write_io = fptr->tied_io_for_writing; | |
fptr->tied_io_for_writing = w; | |
return write_io ? write_io : Qnil; | |
} | |
/* | |
* call-seq: | |
* IO.try_convert(obj) -> io or nil | |
* | |
* Try to convert <i>obj</i> into an IO, using to_io method. | |
* Returns converted IO or +nil+ if <i>obj</i> cannot be converted | |
* for any reason. | |
* | |
* IO.try_convert(STDOUT) #=> STDOUT | |
* IO.try_convert("STDOUT") #=> nil | |
* | |
* require 'zlib' | |
* f = open("/tmp/zz.gz") #=> #<File:/tmp/zz.gz> | |
* z = Zlib::GzipReader.open(f) #=> #<Zlib::GzipReader:0x81d8744> | |
* IO.try_convert(z) #=> #<File:/tmp/zz.gz> | |
* | |
*/ | |
static VALUE | |
rb_io_s_try_convert(VALUE dummy, VALUE io) | |
{ | |
return rb_io_check_io(io); | |
} | |
#if !(defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32)) | |
static void | |
io_unread(rb_io_t *fptr) | |
{ | |
off_t r; | |
rb_io_check_closed(fptr); | |
if (fptr->rbuf.len == 0 || fptr->mode & FMODE_DUPLEX) | |
return; | |
/* xxx: target position may be negative if buffer is filled by ungetc */ | |
errno = 0; | |
r = lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR); | |
if (r < 0 && errno) { | |
if (errno == ESPIPE) | |
fptr->mode |= FMODE_DUPLEX; | |
return; | |
} | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = 0; | |
return; | |
} | |
#endif | |
static rb_encoding *io_input_encoding(rb_io_t *fptr); | |
static void | |
io_ungetbyte(VALUE str, rb_io_t *fptr) | |
{ | |
long len = RSTRING_LEN(str); | |
if (fptr->rbuf.ptr == NULL) { | |
const int min_capa = IO_RBUF_CAPA_FOR(fptr); | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = 0; | |
#if SIZEOF_LONG > SIZEOF_INT | |
if (len > INT_MAX) | |
rb_raise(rb_eIOError, "ungetbyte failed"); | |
#endif | |
if (len > min_capa) | |
fptr->rbuf.capa = (int)len; | |
else | |
fptr->rbuf.capa = min_capa; | |
fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); | |
} | |
if (fptr->rbuf.capa < len + fptr->rbuf.len) { | |
rb_raise(rb_eIOError, "ungetbyte failed"); | |
} | |
if (fptr->rbuf.off < len) { | |
MEMMOVE(fptr->rbuf.ptr+fptr->rbuf.capa-fptr->rbuf.len, | |
fptr->rbuf.ptr+fptr->rbuf.off, | |
char, fptr->rbuf.len); | |
fptr->rbuf.off = fptr->rbuf.capa-fptr->rbuf.len; | |
} | |
fptr->rbuf.off-=(int)len; | |
fptr->rbuf.len+=(int)len; | |
MEMMOVE(fptr->rbuf.ptr+fptr->rbuf.off, RSTRING_PTR(str), char, len); | |
} | |
static rb_io_t * | |
flush_before_seek(rb_io_t *fptr) | |
{ | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
io_unread(fptr); | |
errno = 0; | |
return fptr; | |
} | |
#define io_seek(fptr, ofs, whence) (errno = 0, lseek(flush_before_seek(fptr)->fd, (ofs), (whence))) | |
#define io_tell(fptr) lseek(flush_before_seek(fptr)->fd, 0, SEEK_CUR) | |
#ifndef SEEK_CUR | |
# define SEEK_SET 0 | |
# define SEEK_CUR 1 | |
# define SEEK_END 2 | |
#endif | |
void | |
rb_io_check_char_readable(rb_io_t *fptr) | |
{ | |
rb_io_check_closed(fptr); | |
if (!(fptr->mode & FMODE_READABLE)) { | |
rb_raise(rb_eIOError, "not opened for reading"); | |
} | |
if (fptr->wbuf.len) { | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
} | |
if (fptr->tied_io_for_writing) { | |
rb_io_t *wfptr; | |
GetOpenFile(fptr->tied_io_for_writing, wfptr); | |
if (io_fflush(wfptr) < 0) | |
rb_sys_fail_on_write(wfptr); | |
} | |
} | |
void | |
rb_io_check_byte_readable(rb_io_t *fptr) | |
{ | |
rb_io_check_char_readable(fptr); | |
if (READ_CHAR_PENDING(fptr)) { | |
rb_raise(rb_eIOError, "byte oriented read for character buffered IO"); | |
} | |
} | |
void | |
rb_io_check_readable(rb_io_t *fptr) | |
{ | |
rb_io_check_byte_readable(fptr); | |
} | |
static rb_encoding* | |
io_read_encoding(rb_io_t *fptr) | |
{ | |
if (fptr->encs.enc) { | |
return fptr->encs.enc; | |
} | |
return rb_default_external_encoding(); | |
} | |
static rb_encoding* | |
io_input_encoding(rb_io_t *fptr) | |
{ | |
if (fptr->encs.enc2) { | |
return fptr->encs.enc2; | |
} | |
return io_read_encoding(fptr); | |
} | |
void | |
rb_io_check_writable(rb_io_t *fptr) | |
{ | |
rb_io_check_closed(fptr); | |
if (!(fptr->mode & FMODE_WRITABLE)) { | |
rb_raise(rb_eIOError, "not opened for writing"); | |
} | |
if (fptr->rbuf.len) { | |
io_unread(fptr); | |
} | |
} | |
int | |
rb_io_read_pending(rb_io_t *fptr) | |
{ | |
/* This function is used for bytes and chars. Confusing. */ | |
if (READ_CHAR_PENDING(fptr)) | |
return 1; /* should raise? */ | |
return READ_DATA_PENDING(fptr); | |
} | |
void | |
rb_io_read_check(rb_io_t *fptr) | |
{ | |
if (!READ_DATA_PENDING(fptr)) { | |
rb_thread_wait_fd(fptr->fd); | |
} | |
return; | |
} | |
int | |
rb_gc_for_fd(int err) | |
{ | |
if (err == EMFILE || err == ENFILE || err == ENOMEM) { | |
rb_gc(); | |
return 1; | |
} | |
return 0; | |
} | |
static int | |
ruby_dup(int orig) | |
{ | |
int fd; | |
fd = rb_cloexec_dup(orig); | |
if (fd < 0) { | |
int e = errno; | |
if (rb_gc_for_fd(e)) { | |
fd = rb_cloexec_dup(orig); | |
} | |
if (fd < 0) { | |
rb_syserr_fail(e, 0); | |
} | |
} | |
rb_update_max_fd(fd); | |
return fd; | |
} | |
static VALUE | |
io_alloc(VALUE klass) | |
{ | |
NEWOBJ_OF(io, struct RFile, klass, T_FILE); | |
io->fptr = 0; | |
return (VALUE)io; | |
} | |
#ifndef S_ISREG | |
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | |
#endif | |
struct io_internal_read_struct { | |
VALUE th; | |
int fd; | |
int nonblock; | |
void *buf; | |
size_t capa; | |
}; | |
struct io_internal_write_struct { | |
int fd; | |
const void *buf; | |
size_t capa; | |
}; | |
#ifdef HAVE_WRITEV | |
struct io_internal_writev_struct { | |
int fd; | |
int iovcnt; | |
const struct iovec *iov; | |
}; | |
#endif | |
static int nogvl_wait_for_single_fd(VALUE th, int fd, short events); | |
static VALUE | |
internal_read_func(void *ptr) | |
{ | |
struct io_internal_read_struct *iis = ptr; | |
ssize_t r; | |
retry: | |
r = read(iis->fd, iis->buf, iis->capa); | |
if (r < 0 && !iis->nonblock) { | |
int e = errno; | |
if (e == EAGAIN || e == EWOULDBLOCK) { | |
if (nogvl_wait_for_single_fd(iis->th, iis->fd, RB_WAITFD_IN) != -1) { | |
goto retry; | |
} | |
errno = e; | |
} | |
} | |
return r; | |
} | |
#if defined __APPLE__ | |
# define do_write_retry(code) do {ret = code;} while (ret == -1 && errno == EPROTOTYPE) | |
#else | |
# define do_write_retry(code) ret = code | |
#endif | |
static VALUE | |
internal_write_func(void *ptr) | |
{ | |
struct io_internal_write_struct *iis = ptr; | |
ssize_t ret; | |
do_write_retry(write(iis->fd, iis->buf, iis->capa)); | |
return (VALUE)ret; | |
} | |
static void* | |
internal_write_func2(void *ptr) | |
{ | |
return (void*)internal_write_func(ptr); | |
} | |
#ifdef HAVE_WRITEV | |
static VALUE | |
internal_writev_func(void *ptr) | |
{ | |
struct io_internal_writev_struct *iis = ptr; | |
ssize_t ret; | |
do_write_retry(writev(iis->fd, iis->iov, iis->iovcnt)); | |
return (VALUE)ret; | |
} | |
#endif | |
static ssize_t | |
rb_read_internal(int fd, void *buf, size_t count) | |
{ | |
struct io_internal_read_struct iis = { | |
.th = rb_thread_current(), | |
.fd = fd, | |
.nonblock = 0, | |
.buf = buf, | |
.capa = count | |
}; | |
return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fd); | |
} | |
static ssize_t | |
rb_write_internal(int fd, const void *buf, size_t count) | |
{ | |
struct io_internal_write_struct iis = { | |
.fd = fd, | |
.buf = buf, | |
.capa = count | |
}; | |
return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fd); | |
} | |
static ssize_t | |
rb_write_internal2(int fd, const void *buf, size_t count) | |
{ | |
struct io_internal_write_struct iis = { | |
.fd = fd, | |
.buf = buf, | |
.capa = count | |
}; | |
return (ssize_t)rb_thread_call_without_gvl2(internal_write_func2, &iis, | |
RUBY_UBF_IO, NULL); | |
} | |
#ifdef HAVE_WRITEV | |
static ssize_t | |
rb_writev_internal(int fd, const struct iovec *iov, int iovcnt) | |
{ | |
struct io_internal_writev_struct iis = { | |
.fd = fd, | |
.iov = iov, | |
.iovcnt = iovcnt, | |
}; | |
return (ssize_t)rb_thread_io_blocking_region(internal_writev_func, &iis, fd); | |
} | |
#endif | |
static VALUE | |
io_flush_buffer_sync(void *arg) | |
{ | |
rb_io_t *fptr = arg; | |
long l = fptr->wbuf.len; | |
ssize_t r = write(fptr->fd, fptr->wbuf.ptr+fptr->wbuf.off, (size_t)l); | |
if (fptr->wbuf.len <= r) { | |
fptr->wbuf.off = 0; | |
fptr->wbuf.len = 0; | |
return 0; | |
} | |
if (0 <= r) { | |
fptr->wbuf.off += (int)r; | |
fptr->wbuf.len -= (int)r; | |
errno = EAGAIN; | |
} | |
return (VALUE)-1; | |
} | |
static void* | |
io_flush_buffer_sync2(void *arg) | |
{ | |
VALUE result = io_flush_buffer_sync(arg); | |
/* | |
* rb_thread_call_without_gvl2 uses 0 as interrupted. | |
* So, we need to avoid to use 0. | |
*/ | |
return !result ? (void*)1 : (void*)result; | |
} | |
static VALUE | |
io_flush_buffer_async(VALUE arg) | |
{ | |
rb_io_t *fptr = (rb_io_t *)arg; | |
return rb_thread_io_blocking_region(io_flush_buffer_sync, fptr, fptr->fd); | |
} | |
static VALUE | |
io_flush_buffer_async2(VALUE arg) | |
{ | |
rb_io_t *fptr = (rb_io_t *)arg; | |
VALUE ret; | |
ret = (VALUE)rb_thread_call_without_gvl2(io_flush_buffer_sync2, fptr, RUBY_UBF_IO, NULL); | |
if (!ret) { | |
/* pending async interrupt is there. */ | |
errno = EAGAIN; | |
return -1; | |
} | |
else if (ret == 1) { | |
return 0; | |
} | |
return ret; | |
} | |
static inline int | |
io_flush_buffer(rb_io_t *fptr) | |
{ | |
if (fptr->write_lock) { | |
if (rb_mutex_owned_p(fptr->write_lock)) | |
return (int)io_flush_buffer_async2((VALUE)fptr); | |
else | |
return (int)rb_mutex_synchronize(fptr->write_lock, io_flush_buffer_async2, (VALUE)fptr); | |
} | |
else { | |
return (int)io_flush_buffer_async((VALUE)fptr); | |
} | |
} | |
static int | |
io_fflush(rb_io_t *fptr) | |
{ | |
rb_io_check_closed(fptr); | |
if (fptr->wbuf.len == 0) | |
return 0; | |
while (fptr->wbuf.len > 0 && io_flush_buffer(fptr) != 0) { | |
if (!rb_io_wait_writable(fptr->fd)) | |
return -1; | |
rb_io_check_closed(fptr); | |
} | |
return 0; | |
} | |
VALUE | |
rb_io_wait(VALUE io, VALUE events, VALUE timeout) | |
{ | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil) { | |
return rb_scheduler_io_wait(scheduler, io, events, timeout); | |
} | |
rb_io_t * fptr = NULL; | |
RB_IO_POINTER(io, fptr); | |
struct timeval tv_storage; | |
struct timeval *tv = NULL; | |
if (timeout != Qnil) { | |
tv_storage = rb_time_interval(timeout); | |
tv = &tv_storage; | |
} | |
int ready = rb_thread_wait_for_single_fd(fptr->fd, RB_NUM2INT(events), tv); | |
if (ready < 0) { | |
rb_sys_fail(0); | |
} | |
// Not sure if this is necessary: | |
rb_io_check_closed(fptr); | |
if (ready > 0) { | |
return RB_INT2NUM(ready); | |
} else { | |
return Qfalse; | |
} | |
} | |
static VALUE | |
rb_io_from_fd(int fd) | |
{ | |
return prep_io(fd, FMODE_PREP, rb_cIO, NULL); | |
} | |
int | |
rb_io_wait_readable(int f) | |
{ | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil) { | |
return RTEST( | |
rb_scheduler_io_wait_readable(scheduler, rb_io_from_fd(f)) | |
); | |
} | |
io_fd_check_closed(f); | |
switch (errno) { | |
case EINTR: | |
#if defined(ERESTART) | |
case ERESTART: | |
#endif | |
rb_thread_check_ints(); | |
return TRUE; | |
case EAGAIN: | |
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN | |
case EWOULDBLOCK: | |
#endif | |
rb_thread_wait_fd(f); | |
return TRUE; | |
default: | |
return FALSE; | |
} | |
} | |
int | |
rb_io_wait_writable(int f) | |
{ | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil) { | |
return RTEST( | |
rb_scheduler_io_wait_writable(scheduler, rb_io_from_fd(f)) | |
); | |
} | |
io_fd_check_closed(f); | |
switch (errno) { | |
case EINTR: | |
#if defined(ERESTART) | |
case ERESTART: | |
#endif | |
/* | |
* In old Linux, several special files under /proc and /sys don't handle | |
* select properly. Thus we need avoid to call if don't use O_NONBLOCK. | |
* Otherwise, we face nasty hang up. Sigh. | |
* e.g. http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 | |
* http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=31b07093c44a7a442394d44423e21d783f5523b8 | |
* In EINTR case, we only need to call RUBY_VM_CHECK_INTS_BLOCKING(). | |
* Then rb_thread_check_ints() is enough. | |
*/ | |
rb_thread_check_ints(); | |
return TRUE; | |
case EAGAIN: | |
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN | |
case EWOULDBLOCK: | |
#endif | |
rb_thread_fd_writable(f); | |
return TRUE; | |
default: | |
return FALSE; | |
} | |
} | |
int | |
rb_wait_for_single_fd(int fd, int events, struct timeval *timeout) | |
{ | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil) { | |
return RTEST( | |
rb_scheduler_io_wait(scheduler, rb_io_from_fd(fd), RB_INT2NUM(events), rb_scheduler_timeout(timeout)) | |
); | |
} | |
return rb_thread_wait_for_single_fd(fd, events, timeout); | |
} | |
static void | |
make_writeconv(rb_io_t *fptr) | |
{ | |
if (!fptr->writeconv_initialized) { | |
const char *senc, *denc; | |
rb_encoding *enc; | |
int ecflags; | |
VALUE ecopts; | |
fptr->writeconv_initialized = 1; | |
ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_READ_MASK; | |
ecopts = fptr->encs.ecopts; | |
if (!fptr->encs.enc || (fptr->encs.enc == rb_ascii8bit_encoding() && !fptr->encs.enc2)) { | |
/* no encoding conversion */ | |
fptr->writeconv_pre_ecflags = 0; | |
fptr->writeconv_pre_ecopts = Qnil; | |
fptr->writeconv = rb_econv_open_opts("", "", ecflags, ecopts); | |
if (!fptr->writeconv) | |
rb_exc_raise(rb_econv_open_exc("", "", ecflags)); | |
fptr->writeconv_asciicompat = Qnil; | |
} | |
else { | |
enc = fptr->encs.enc2 ? fptr->encs.enc2 : fptr->encs.enc; | |
senc = rb_econv_asciicompat_encoding(rb_enc_name(enc)); | |
if (!senc && !(fptr->encs.ecflags & ECONV_STATEFUL_DECORATOR_MASK)) { | |
/* single conversion */ | |
fptr->writeconv_pre_ecflags = ecflags; | |
fptr->writeconv_pre_ecopts = ecopts; | |
fptr->writeconv = NULL; | |
fptr->writeconv_asciicompat = Qnil; | |
} | |
else { | |
/* double conversion */ | |
fptr->writeconv_pre_ecflags = ecflags & ~ECONV_STATEFUL_DECORATOR_MASK; | |
fptr->writeconv_pre_ecopts = ecopts; | |
if (senc) { | |
denc = rb_enc_name(enc); | |
fptr->writeconv_asciicompat = rb_str_new2(senc); | |
} | |
else { | |
senc = denc = ""; | |
fptr->writeconv_asciicompat = rb_str_new2(rb_enc_name(enc)); | |
} | |
ecflags = fptr->encs.ecflags & (ECONV_ERROR_HANDLER_MASK|ECONV_STATEFUL_DECORATOR_MASK); | |
ecopts = fptr->encs.ecopts; | |
fptr->writeconv = rb_econv_open_opts(senc, denc, ecflags, ecopts); | |
if (!fptr->writeconv) | |
rb_exc_raise(rb_econv_open_exc(senc, denc, ecflags)); | |
} | |
} | |
} | |
} | |
/* writing functions */ | |
struct binwrite_arg { | |
rb_io_t *fptr; | |
VALUE str; | |
const char *ptr; | |
long length; | |
}; | |
struct write_arg { | |
VALUE io; | |
VALUE str; | |
int nosync; | |
}; | |
#ifdef HAVE_WRITEV | |
static VALUE | |
io_binwrite_string(VALUE arg) | |
{ | |
struct binwrite_arg *p = (struct binwrite_arg *)arg; | |
rb_io_t *fptr = p->fptr; | |
long r; | |
if (fptr->wbuf.len) { | |
struct iovec iov[2]; | |
iov[0].iov_base = fptr->wbuf.ptr+fptr->wbuf.off; | |
iov[0].iov_len = fptr->wbuf.len; | |
iov[1].iov_base = (char *)p->ptr; | |
iov[1].iov_len = p->length; | |
r = rb_writev_internal(fptr->fd, iov, 2); | |
if (r < 0) | |
return r; | |
if (fptr->wbuf.len <= r) { | |
r -= fptr->wbuf.len; | |
fptr->wbuf.off = 0; | |
fptr->wbuf.len = 0; | |
} | |
else { | |
fptr->wbuf.off += (int)r; | |
fptr->wbuf.len -= (int)r; | |
r = 0L; | |
} | |
} | |
else { | |
r = rb_write_internal(fptr->fd, p->ptr, p->length); | |
} | |
return r; | |
} | |
#else | |
static VALUE | |
io_binwrite_string(VALUE arg) | |
{ | |
struct binwrite_arg *p = (struct binwrite_arg *)arg; | |
rb_io_t *fptr = p->fptr; | |
long l, len; | |
l = len = p->length; | |
if (fptr->wbuf.len) { | |
if (fptr->wbuf.len+len <= fptr->wbuf.capa) { | |
if (fptr->wbuf.capa < fptr->wbuf.off+fptr->wbuf.len+len) { | |
MEMMOVE(fptr->wbuf.ptr, fptr->wbuf.ptr+fptr->wbuf.off, char, fptr->wbuf.len); | |
fptr->wbuf.off = 0; | |
} | |
MEMMOVE(fptr->wbuf.ptr+fptr->wbuf.off+fptr->wbuf.len, p->ptr, char, len); | |
fptr->wbuf.len += (int)len; | |
l = 0; | |
} | |
if (io_fflush(fptr) < 0) | |
return -2L; /* fail in fflush */ | |
if (l == 0) | |
return len; | |
} | |
if (fptr->stdio_file != stderr && !rb_thread_fd_writable(fptr->fd)) | |
rb_io_check_closed(fptr); | |
return rb_write_internal(p->fptr->fd, p->ptr, p->length); | |
} | |
#endif | |
static long | |
io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync) | |
{ | |
long n, r, offset = 0; | |
/* don't write anything if current thread has a pending interrupt. */ | |
rb_thread_check_ints(); | |
if ((n = len) <= 0) return n; | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil && rb_scheduler_supports_io_write(scheduler)) { | |
ssize_t length = RB_NUM2SSIZE( | |
rb_scheduler_io_write(scheduler, fptr->self, str, offset, len) | |
); | |
if (length < 0) rb_sys_fail_path(fptr->pathv); | |
return length; | |
} | |
if (fptr->wbuf.ptr == NULL && !(!nosync && (fptr->mode & FMODE_SYNC))) { | |
fptr->wbuf.off = 0; | |
fptr->wbuf.len = 0; | |
fptr->wbuf.capa = IO_WBUF_CAPA_MIN; | |
fptr->wbuf.ptr = ALLOC_N(char, fptr->wbuf.capa); | |
fptr->write_lock = rb_mutex_new(); | |
rb_mutex_allow_trap(fptr->write_lock, 1); | |
} | |
if ((!nosync && (fptr->mode & (FMODE_SYNC|FMODE_TTY))) || | |
(fptr->wbuf.ptr && fptr->wbuf.capa <= fptr->wbuf.len + len)) { | |
struct binwrite_arg arg; | |
arg.fptr = fptr; | |
arg.str = str; | |
retry: | |
arg.ptr = ptr + offset; | |
arg.length = n; | |
if (fptr->write_lock) { | |
r = rb_mutex_synchronize(fptr->write_lock, io_binwrite_string, (VALUE)&arg); | |
} | |
else { | |
r = io_binwrite_string((VALUE)&arg); | |
} | |
/* xxx: other threads may modify given string. */ | |
if (r == n) return len; | |
if (0 <= r) { | |
offset += r; | |
n -= r; | |
errno = EAGAIN; | |
} | |
if (r == -2L) | |
return -1L; | |
if (rb_io_wait_writable(fptr->fd)) { | |
rb_io_check_closed(fptr); | |
if (offset < len) | |
goto retry; | |
} | |
return -1L; | |
} | |
if (fptr->wbuf.off) { | |
if (fptr->wbuf.len) | |
MEMMOVE(fptr->wbuf.ptr, fptr->wbuf.ptr+fptr->wbuf.off, char, fptr->wbuf.len); | |
fptr->wbuf.off = 0; | |
} | |
MEMMOVE(fptr->wbuf.ptr+fptr->wbuf.off+fptr->wbuf.len, ptr+offset, char, len); | |
fptr->wbuf.len += (int)len; | |
return len; | |
} | |
# define MODE_BTMODE(a,b,c) ((fmode & FMODE_BINMODE) ? (b) : \ | |
(fmode & FMODE_TEXTMODE) ? (c) : (a)) | |
#define MODE_BTXMODE(a, b, c, d, e, f) ((fmode & FMODE_EXCL) ? \ | |
MODE_BTMODE(d, e, f) : \ | |
MODE_BTMODE(a, b, c)) | |
static VALUE | |
do_writeconv(VALUE str, rb_io_t *fptr, int *converted) | |
{ | |
if (NEED_WRITECONV(fptr)) { | |
VALUE common_encoding = Qnil; | |
SET_BINARY_MODE(fptr); | |
make_writeconv(fptr); | |
if (fptr->writeconv) { | |
#define fmode (fptr->mode) | |
if (!NIL_P(fptr->writeconv_asciicompat)) | |
common_encoding = fptr->writeconv_asciicompat; | |
else if (MODE_BTMODE(DEFAULT_TEXTMODE,0,1) && !rb_enc_asciicompat(rb_enc_get(str))) { | |
rb_raise(rb_eArgError, "ASCII incompatible string written for text mode IO without encoding conversion: %s", | |
rb_enc_name(rb_enc_get(str))); | |
} | |
#undef fmode | |
} | |
else { | |
if (fptr->encs.enc2) | |
common_encoding = rb_enc_from_encoding(fptr->encs.enc2); | |
else if (fptr->encs.enc != rb_ascii8bit_encoding()) | |
common_encoding = rb_enc_from_encoding(fptr->encs.enc); | |
} | |
if (!NIL_P(common_encoding)) { | |
str = rb_str_encode(str, common_encoding, | |
fptr->writeconv_pre_ecflags, fptr->writeconv_pre_ecopts); | |
*converted = 1; | |
} | |
if (fptr->writeconv) { | |
str = rb_econv_str_convert(fptr->writeconv, str, ECONV_PARTIAL_INPUT); | |
*converted = 1; | |
} | |
} | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
#define fmode (fptr->mode) | |
else if (MODE_BTMODE(DEFAULT_TEXTMODE,0,1)) { | |
if ((fptr->mode & FMODE_READABLE) && | |
!(fptr->encs.ecflags & ECONV_NEWLINE_DECORATOR_MASK)) { | |
setmode(fptr->fd, O_BINARY); | |
} | |
else { | |
setmode(fptr->fd, O_TEXT); | |
} | |
if (!rb_enc_asciicompat(rb_enc_get(str))) { | |
rb_raise(rb_eArgError, "ASCII incompatible string written for text mode IO without encoding conversion: %s", | |
rb_enc_name(rb_enc_get(str))); | |
} | |
} | |
#undef fmode | |
#endif | |
return str; | |
} | |
static long | |
io_fwrite(VALUE str, rb_io_t *fptr, int nosync) | |
{ | |
int converted = 0; | |
VALUE tmp; | |
long n, len; | |
const char *ptr; | |
#ifdef _WIN32 | |
if (fptr->mode & FMODE_TTY) { | |
long len = rb_w32_write_console(str, fptr->fd); | |
if (len > 0) return len; | |
} | |
#endif | |
str = do_writeconv(str, fptr, &converted); | |
if (converted) | |
OBJ_FREEZE(str); | |
tmp = rb_str_tmp_frozen_acquire(str); | |
RSTRING_GETMEM(tmp, ptr, len); | |
n = io_binwrite(tmp, ptr, len, fptr, nosync); | |
rb_str_tmp_frozen_release(str, tmp); | |
return n; | |
} | |
ssize_t | |
rb_io_bufwrite(VALUE io, const void *buf, size_t size) | |
{ | |
rb_io_t *fptr; | |
GetOpenFile(io, fptr); | |
rb_io_check_writable(fptr); | |
return (ssize_t)io_binwrite(0, buf, (long)size, fptr, 0); | |
} | |
static VALUE | |
io_write(VALUE io, VALUE str, int nosync) | |
{ | |
rb_io_t *fptr; | |
long n; | |
VALUE tmp; | |
io = GetWriteIO(io); | |
str = rb_obj_as_string(str); | |
tmp = rb_io_check_io(io); | |
if (NIL_P(tmp)) { | |
/* port is not IO, call write method for it. */ | |
return rb_funcall(io, id_write, 1, str); | |
} | |
io = tmp; | |
if (RSTRING_LEN(str) == 0) return INT2FIX(0); | |
GetOpenFile(io, fptr); | |
rb_io_check_writable(fptr); | |
n = io_fwrite(str, fptr, nosync); | |
if (n < 0L) rb_sys_fail_on_write(fptr); | |
return LONG2FIX(n); | |
} | |
#ifdef HAVE_WRITEV | |
struct binwritev_arg { | |
rb_io_t *fptr; | |
const struct iovec *iov; | |
int iovcnt; | |
}; | |
static VALUE | |
call_writev_internal(VALUE arg) | |
{ | |
struct binwritev_arg *p = (struct binwritev_arg *)arg; | |
return rb_writev_internal(p->fptr->fd, p->iov, p->iovcnt); | |
} | |
static long | |
io_binwritev(struct iovec *iov, int iovcnt, rb_io_t *fptr) | |
{ | |
int i; | |
long r, total = 0, written_len = 0; | |
/* don't write anything if current thread has a pending interrupt. */ | |
rb_thread_check_ints(); | |
if (iovcnt == 0) return 0; | |
for (i = 1; i < iovcnt; i++) total += iov[i].iov_len; | |
if (fptr->wbuf.ptr == NULL && !(fptr->mode & FMODE_SYNC)) { | |
fptr->wbuf.off = 0; | |
fptr->wbuf.len = 0; | |
fptr->wbuf.capa = IO_WBUF_CAPA_MIN; | |
fptr->wbuf.ptr = ALLOC_N(char, fptr->wbuf.capa); | |
fptr->write_lock = rb_mutex_new(); | |
rb_mutex_allow_trap(fptr->write_lock, 1); | |
} | |
if (fptr->wbuf.ptr && fptr->wbuf.len) { | |
long offset = fptr->wbuf.off + fptr->wbuf.len; | |
if (offset + total <= fptr->wbuf.capa) { | |
for (i = 1; i < iovcnt; i++) { | |
memcpy(fptr->wbuf.ptr+offset, iov[i].iov_base, iov[i].iov_len); | |
offset += iov[i].iov_len; | |
} | |
fptr->wbuf.len += total; | |
return total; | |
} | |
else { | |
iov[0].iov_base = fptr->wbuf.ptr + fptr->wbuf.off; | |
iov[0].iov_len = fptr->wbuf.len; | |
} | |
} | |
else { | |
iov++; | |
if (!--iovcnt) return 0; | |
} | |
retry: | |
if (fptr->write_lock) { | |
struct binwritev_arg arg; | |
arg.fptr = fptr; | |
arg.iov = iov; | |
arg.iovcnt = iovcnt; | |
r = rb_mutex_synchronize(fptr->write_lock, call_writev_internal, (VALUE)&arg); | |
} | |
else { | |
r = rb_writev_internal(fptr->fd, iov, iovcnt); | |
} | |
if (r >= 0) { | |
written_len += r; | |
if (fptr->wbuf.ptr && fptr->wbuf.len) { | |
if (written_len < fptr->wbuf.len) { | |
fptr->wbuf.off += r; | |
fptr->wbuf.len -= r; | |
} | |
else { | |
written_len -= fptr->wbuf.len; | |
fptr->wbuf.off = 0; | |
fptr->wbuf.len = 0; | |
} | |
} | |
if (written_len == total) return total; | |
while (r >= (ssize_t)iov->iov_len) { | |
/* iovcnt > 0 */ | |
r -= iov->iov_len; | |
iov->iov_len = 0; | |
iov++; | |
if (!--iovcnt) return total; | |
/* defensive check: written_len should == total */ | |
} | |
iov->iov_base = (char *)iov->iov_base + r; | |
iov->iov_len -= r; | |
errno = EAGAIN; | |
} | |
if (rb_io_wait_writable(fptr->fd)) { | |
rb_io_check_closed(fptr); | |
goto retry; | |
} | |
return -1L; | |
} | |
static long | |
io_fwritev(int argc, const VALUE *argv, rb_io_t *fptr) | |
{ | |
int i, converted, iovcnt = argc + 1; | |
long n; | |
VALUE v1, v2, str, tmp, *tmp_array; | |
struct iovec *iov; | |
iov = ALLOCV_N(struct iovec, v1, iovcnt); | |
tmp_array = ALLOCV_N(VALUE, v2, argc); | |
for (i = 0; i < argc; i++) { | |
str = rb_obj_as_string(argv[i]); | |
converted = 0; | |
str = do_writeconv(str, fptr, &converted); | |
if (converted) | |
OBJ_FREEZE(str); | |
tmp = rb_str_tmp_frozen_acquire(str); | |
tmp_array[i] = tmp; | |
/* iov[0] is reserved for buffer of fptr */ | |
iov[i+1].iov_base = RSTRING_PTR(tmp); | |
iov[i+1].iov_len = RSTRING_LEN(tmp); | |
} | |
n = io_binwritev(iov, iovcnt, fptr); | |
if (v1) ALLOCV_END(v1); | |
for (i = 0; i < argc; i++) { | |
rb_str_tmp_frozen_release(argv[i], tmp_array[i]); | |
} | |
if (v2) ALLOCV_END(v2); | |
return n; | |
} | |
static int | |
iovcnt_ok(int iovcnt) | |
{ | |
#ifdef IOV_MAX | |
return iovcnt < IOV_MAX; | |
#else /* GNU/Hurd has writev, but no IOV_MAX */ | |
return 1; | |
#endif | |
} | |
#endif /* HAVE_WRITEV */ | |
static VALUE | |
io_writev(int argc, const VALUE *argv, VALUE io) | |
{ | |
rb_io_t *fptr; | |
long n; | |
VALUE tmp, total = INT2FIX(0); | |
int i, cnt = 1; | |
io = GetWriteIO(io); | |
tmp = rb_io_check_io(io); | |
if (NIL_P(tmp)) { | |
/* port is not IO, call write method for it. */ | |
return rb_funcallv(io, id_write, argc, argv); | |
} | |
io = tmp; | |
GetOpenFile(io, fptr); | |
rb_io_check_writable(fptr); | |
for (i = 0; i < argc; i += cnt) { | |
#ifdef HAVE_WRITEV | |
if ((fptr->mode & (FMODE_SYNC|FMODE_TTY)) && iovcnt_ok(cnt = argc - i)) { | |
n = io_fwritev(cnt, &argv[i], fptr); | |
} | |
else | |
#endif | |
{ | |
cnt = 1; | |
/* sync at last item */ | |
n = io_fwrite(rb_obj_as_string(argv[i]), fptr, (i < argc-1)); | |
} | |
if (n < 0L) rb_sys_fail_on_write(fptr); | |
total = rb_fix_plus(LONG2FIX(n), total); | |
} | |
return total; | |
} | |
/* | |
* call-seq: | |
* ios.write(string, ...) -> integer | |
* | |
* Writes the given strings to <em>ios</em>. The stream must be opened | |
* for writing. Arguments that are not a string will be converted | |
* to a string using <code>to_s</code>. Returns the number of bytes | |
* written in total. | |
* | |
* count = $stdout.write("This is", " a test\n") | |
* puts "That was #{count} bytes of data" | |
* | |
* <em>produces:</em> | |
* | |
* This is a test | |
* That was 15 bytes of data | |
*/ | |
static VALUE | |
io_write_m(int argc, VALUE *argv, VALUE io) | |
{ | |
if (argc != 1) { | |
return io_writev(argc, argv, io); | |
} | |
else { | |
VALUE str = argv[0]; | |
return io_write(io, str, 0); | |
} | |
} | |
VALUE | |
rb_io_write(VALUE io, VALUE str) | |
{ | |
return rb_funcallv(io, id_write, 1, &str); | |
} | |
static VALUE | |
rb_io_writev(VALUE io, int argc, const VALUE *argv) | |
{ | |
if (argc > 1 && rb_obj_method_arity(io, id_write) == 1) { | |
if (io != rb_ractor_stderr() && RTEST(ruby_verbose)) { | |
VALUE klass = CLASS_OF(io); | |
char sep = FL_TEST(klass, FL_SINGLETON) ? (klass = io, '.') : '#'; | |
rb_category_warning(RB_WARN_CATEGORY_DEPRECATED, "%+"PRIsVALUE"%c""write is outdated interface" | |
" which accepts just one argument", | |
klass, sep); | |
} | |
do rb_io_write(io, *argv++); while (--argc); | |
return argv[0]; /* unused right now */ | |
} | |
return rb_funcallv(io, id_write, argc, argv); | |
} | |
/* | |
* call-seq: | |
* ios << obj -> ios | |
* | |
* String Output---Writes <i>obj</i> to <em>ios</em>. | |
* <i>obj</i> will be converted to a string using | |
* <code>to_s</code>. | |
* | |
* $stdout << "Hello " << "world!\n" | |
* | |
* <em>produces:</em> | |
* | |
* Hello world! | |
*/ | |
VALUE | |
rb_io_addstr(VALUE io, VALUE str) | |
{ | |
rb_io_write(io, str); | |
return io; | |
} | |
#ifdef HAVE_FSYNC | |
static VALUE | |
nogvl_fsync(void *ptr) | |
{ | |
rb_io_t *fptr = ptr; | |
#ifdef _WIN32 | |
if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) != FILE_TYPE_DISK) | |
return 0; | |
#endif | |
return (VALUE)fsync(fptr->fd); | |
} | |
#endif | |
VALUE | |
rb_io_flush_raw(VALUE io, int sync) | |
{ | |
rb_io_t *fptr; | |
if (!RB_TYPE_P(io, T_FILE)) { | |
return rb_funcall(io, id_flush, 0); | |
} | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
if (fptr->mode & FMODE_WRITABLE) { | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
} | |
if (fptr->mode & FMODE_READABLE) { | |
io_unread(fptr); | |
} | |
return io; | |
} | |
/* | |
* call-seq: | |
* ios.flush -> ios | |
* | |
* Flushes any buffered data within <em>ios</em> to the underlying | |
* operating system (note that this is Ruby internal buffering only; | |
* the OS may buffer the data as well). | |
* | |
* $stdout.print "no newline" | |
* $stdout.flush | |
* | |
* <em>produces:</em> | |
* | |
* no newline | |
*/ | |
VALUE | |
rb_io_flush(VALUE io) | |
{ | |
return rb_io_flush_raw(io, 1); | |
} | |
/* | |
* call-seq: | |
* ios.pos -> integer | |
* ios.tell -> integer | |
* | |
* Returns the current offset (in bytes) of <em>ios</em>. | |
* | |
* f = File.new("testfile") | |
* f.pos #=> 0 | |
* f.gets #=> "This is line one\n" | |
* f.pos #=> 17 | |
*/ | |
static VALUE | |
rb_io_tell(VALUE io) | |
{ | |
rb_io_t *fptr; | |
off_t pos; | |
GetOpenFile(io, fptr); | |
pos = io_tell(fptr); | |
if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv); | |
pos -= fptr->rbuf.len; | |
return OFFT2NUM(pos); | |
} | |
static VALUE | |
rb_io_seek(VALUE io, VALUE offset, int whence) | |
{ | |
rb_io_t *fptr; | |
off_t pos; | |
pos = NUM2OFFT(offset); | |
GetOpenFile(io, fptr); | |
pos = io_seek(fptr, pos, whence); | |
if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv); | |
return INT2FIX(0); | |
} | |
static int | |
interpret_seek_whence(VALUE vwhence) | |
{ | |
if (vwhence == sym_SET) | |
return SEEK_SET; | |
if (vwhence == sym_CUR) | |
return SEEK_CUR; | |
if (vwhence == sym_END) | |
return SEEK_END; | |
#ifdef SEEK_DATA | |
if (vwhence == sym_DATA) | |
return SEEK_DATA; | |
#endif | |
#ifdef SEEK_HOLE | |
if (vwhence == sym_HOLE) | |
return SEEK_HOLE; | |
#endif | |
return NUM2INT(vwhence); | |
} | |
/* | |
* call-seq: | |
* ios.seek(amount, whence=IO::SEEK_SET) -> 0 | |
* | |
* Seeks to a given offset <i>anInteger</i> in the stream according to | |
* the value of <i>whence</i>: | |
* | |
* :CUR or IO::SEEK_CUR | Seeks to _amount_ plus current position | |
* ----------------------+-------------------------------------------------- | |
* :END or IO::SEEK_END | Seeks to _amount_ plus end of stream (you | |
* | probably want a negative value for _amount_) | |
* ----------------------+-------------------------------------------------- | |
* :SET or IO::SEEK_SET | Seeks to the absolute location given by _amount_ | |
* | |
* Example: | |
* | |
* f = File.new("testfile") | |
* f.seek(-13, IO::SEEK_END) #=> 0 | |
* f.readline #=> "And so on...\n" | |
*/ | |
static VALUE | |
rb_io_seek_m(int argc, VALUE *argv, VALUE io) | |
{ | |
VALUE offset, ptrname; | |
int whence = SEEK_SET; | |
if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) { | |
whence = interpret_seek_whence(ptrname); | |
} | |
return rb_io_seek(io, offset, whence); | |
} | |
/* | |
* call-seq: | |
* ios.pos = integer -> integer | |
* | |
* Seeks to the given position (in bytes) in <em>ios</em>. | |
* It is not guaranteed that seeking to the right position when <em>ios</em> | |
* is textmode. | |
* | |
* f = File.new("testfile") | |
* f.pos = 17 | |
* f.gets #=> "This is line two\n" | |
*/ | |
static VALUE | |
rb_io_set_pos(VALUE io, VALUE offset) | |
{ | |
rb_io_t *fptr; | |
off_t pos; | |
pos = NUM2OFFT(offset); | |
GetOpenFile(io, fptr); | |
pos = io_seek(fptr, pos, SEEK_SET); | |
if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv); | |
return OFFT2NUM(pos); | |
} | |
static void clear_readconv(rb_io_t *fptr); | |
/* | |
* call-seq: | |
* ios.rewind -> 0 | |
* | |
* Positions <em>ios</em> to the beginning of input, resetting | |
* #lineno to zero. | |
* | |
* f = File.new("testfile") | |
* f.readline #=> "This is line one\n" | |
* f.rewind #=> 0 | |
* f.lineno #=> 0 | |
* f.readline #=> "This is line one\n" | |
* | |
* Note that it cannot be used with streams such as pipes, ttys, and sockets. | |
*/ | |
static VALUE | |
rb_io_rewind(VALUE io) | |
{ | |
rb_io_t *fptr; | |
GetOpenFile(io, fptr); | |
if (io_seek(fptr, 0L, 0) < 0 && errno) rb_sys_fail_path(fptr->pathv); | |
if (io == ARGF.current_file) { | |
ARGF.lineno -= fptr->lineno; | |
} | |
fptr->lineno = 0; | |
if (fptr->readconv) { | |
clear_readconv(fptr); | |
} | |
return INT2FIX(0); | |
} | |
static int | |
fptr_wait_readable(rb_io_t *fptr) | |
{ | |
int ret = rb_io_wait_readable(fptr->fd); | |
if (ret) | |
rb_io_check_closed(fptr); | |
return ret; | |
} | |
static int | |
io_fillbuf(rb_io_t *fptr) | |
{ | |
ssize_t r; | |
if (fptr->rbuf.ptr == NULL) { | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = 0; | |
fptr->rbuf.capa = IO_RBUF_CAPA_FOR(fptr); | |
fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); | |
#ifdef _WIN32 | |
fptr->rbuf.capa--; | |
#endif | |
} | |
if (fptr->rbuf.len == 0) { | |
retry: | |
{ | |
r = rb_read_internal(fptr->fd, fptr->rbuf.ptr, fptr->rbuf.capa); | |
} | |
if (r < 0) { | |
if (fptr_wait_readable(fptr)) | |
goto retry; | |
{ | |
int e = errno; | |
VALUE path = rb_sprintf("fd:%d ", fptr->fd); | |
if (!NIL_P(fptr->pathv)) { | |
rb_str_append(path, fptr->pathv); | |
} | |
rb_syserr_fail_path(e, path); | |
} | |
} | |
if (r > 0) rb_io_check_closed(fptr); | |
fptr->rbuf.off = 0; | |
fptr->rbuf.len = (int)r; /* r should be <= rbuf_capa */ | |
if (r == 0) | |
return -1; /* EOF */ | |
} | |
return 0; | |
} | |
/* | |
* call-seq: | |
* ios.eof -> true or false | |
* ios.eof? -> true or false | |
* | |
* Returns true if <em>ios</em> is at end of file that means | |
* there are no more data to read. | |
* The stream must be opened for reading or an IOError will be | |
* raised. | |
* | |
* f = File.new("testfile") | |
* dummy = f.readlines | |
* f.eof #=> true | |
* | |
* If <em>ios</em> is a stream such as pipe or socket, IO#eof? | |
* blocks until the other end sends some data or closes it. | |
* | |
* r, w = IO.pipe | |
* Thread.new { sleep 1; w.close } | |
* r.eof? #=> true after 1 second blocking | |
* | |
* r, w = IO.pipe | |
* Thread.new { sleep 1; w.puts "a" } | |
* r.eof? #=> false after 1 second blocking | |
* | |
* r, w = IO.pipe | |
* r.eof? # blocks forever | |
* | |
* Note that IO#eof? reads data to the input byte buffer. So | |
* IO#sysread may not behave as you intend with IO#eof?, unless you | |
* call IO#rewind first (which is not available for some streams). | |
*/ | |
VALUE | |
rb_io_eof(VALUE io) | |
{ | |
rb_io_t *fptr; | |
GetOpenFile(io, fptr); | |
rb_io_check_char_readable(fptr); | |
if (READ_CHAR_PENDING(fptr)) return Qfalse; | |
if (READ_DATA_PENDING(fptr)) return Qfalse; | |
READ_CHECK(fptr); | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
if (!NEED_READCONV(fptr) && NEED_NEWLINE_DECORATOR_ON_READ(fptr)) { | |
return eof(fptr->fd) ? Qtrue : Qfalse; | |
} | |
#endif | |
if (io_fillbuf(fptr) < 0) { | |
return Qtrue; | |
} | |
return Qfalse; | |
} | |
/* | |
* call-seq: | |
* ios.sync -> true or false | |
* | |
* Returns the current ``sync mode'' of <em>ios</em>. When sync mode is | |
* true, all output is immediately flushed to the underlying operating | |
* system and is not buffered by Ruby internally. See also | |
* IO#fsync. | |
* | |
* f = File.new("testfile") | |
* f.sync #=> false | |
*/ | |
static VALUE | |
rb_io_sync(VALUE io) | |
{ | |
rb_io_t *fptr; | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
return (fptr->mode & FMODE_SYNC) ? Qtrue : Qfalse; | |
} | |
#ifdef HAVE_FSYNC | |
/* | |
* call-seq: | |
* ios.sync = boolean -> boolean | |
* | |
* Sets the ``sync mode'' to <code>true</code> or <code>false</code>. | |
* When sync mode is true, all output is immediately flushed to the | |
* underlying operating system and is not buffered internally. Returns | |
* the new state. See also IO#fsync. | |
* | |
* f = File.new("testfile") | |
* f.sync = true | |
*/ | |
static VALUE | |
rb_io_set_sync(VALUE io, VALUE sync) | |
{ | |
rb_io_t *fptr; | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
if (RTEST(sync)) { | |
fptr->mode |= FMODE_SYNC; | |
} | |
else { | |
fptr->mode &= ~FMODE_SYNC; | |
} | |
return sync; | |
} | |
/* | |
* call-seq: | |
* ios.fsync -> 0 or nil | |
* | |
* Immediately writes all buffered data in <em>ios</em> to disk. | |
* Note that #fsync differs from using IO#sync=. The latter ensures | |
* that data is flushed from Ruby's buffers, but does not guarantee | |
* that the underlying operating system actually writes it to disk. | |
* | |
* NotImplementedError is raised | |
* if the underlying operating system does not support <em>fsync(2)</em>. | |
*/ | |
static VALUE | |
rb_io_fsync(VALUE io) | |
{ | |
rb_io_t *fptr; | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
if ((int)rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd) < 0) | |
rb_sys_fail_path(fptr->pathv); | |
return INT2FIX(0); | |
} | |
#else | |
# define rb_io_fsync rb_f_notimplement | |
# define rb_io_sync rb_f_notimplement | |
static VALUE | |
rb_io_set_sync(VALUE io, VALUE sync) | |
{ | |
rb_notimplement(); | |
UNREACHABLE; | |
} | |
#endif | |
#ifdef HAVE_FDATASYNC | |
static VALUE | |
nogvl_fdatasync(void *ptr) | |
{ | |
rb_io_t *fptr = ptr; | |
#ifdef _WIN32 | |
if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) != FILE_TYPE_DISK) | |
return 0; | |
#endif | |
return (VALUE)fdatasync(fptr->fd); | |
} | |
/* | |
* call-seq: | |
* ios.fdatasync -> 0 or nil | |
* | |
* Immediately writes all buffered data in <em>ios</em> to disk. | |
* | |
* If the underlying operating system does not support <em>fdatasync(2)</em>, | |
* IO#fsync is called instead (which might raise a | |
* NotImplementedError). | |
*/ | |
static VALUE | |
rb_io_fdatasync(VALUE io) | |
{ | |
rb_io_t *fptr; | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
if ((int)rb_thread_io_blocking_region(nogvl_fdatasync, fptr, fptr->fd) == 0) | |
return INT2FIX(0); | |
/* fall back */ | |
return rb_io_fsync(io); | |
} | |
#else | |
#define rb_io_fdatasync rb_io_fsync | |
#endif | |
/* | |
* call-seq: | |
* ios.fileno -> integer | |
* ios.to_i -> integer | |
* | |
* Returns an integer representing the numeric file descriptor for | |
* <em>ios</em>. | |
* | |
* $stdin.fileno #=> 0 | |
* $stdout.fileno #=> 1 | |
*/ | |
static VALUE | |
rb_io_fileno(VALUE io) | |
{ | |
rb_io_t *fptr = RFILE(io)->fptr; | |
int fd; | |
rb_io_check_closed(fptr); | |
fd = fptr->fd; | |
return INT2FIX(fd); | |
} | |
/* | |
* call-seq: | |
* ios.pid -> integer | |
* | |
* Returns the process ID of a child process associated with | |
* <em>ios</em>. This will be set by IO.popen. | |
* | |
* pipe = IO.popen("-") | |
* if pipe | |
* $stderr.puts "In parent, child pid is #{pipe.pid}" | |
* else | |
* $stderr.puts "In child, pid is #{$$}" | |
* end | |
* | |
* <em>produces:</em> | |
* | |
* In child, pid is 26209 | |
* In parent, child pid is 26209 | |
*/ | |
static VALUE | |
rb_io_pid(VALUE io) | |
{ | |
rb_io_t *fptr; | |
GetOpenFile(io, fptr); | |
if (!fptr->pid) | |
return Qnil; | |
return PIDT2NUM(fptr->pid); | |
} | |
/* | |
* call-seq: | |
* ios.inspect -> string | |
* | |
* Return a string describing this IO object. | |
*/ | |
static VALUE | |
rb_io_inspect(VALUE obj) | |
{ | |
rb_io_t *fptr; | |
VALUE result; | |
static const char closed[] = " (closed)"; | |
fptr = RFILE(obj)->fptr; | |
if (!fptr) return rb_any_to_s(obj); | |
result = rb_str_new_cstr("#<"); | |
rb_str_append(result, rb_class_name(CLASS_OF(obj))); | |
rb_str_cat2(result, ":"); | |
if (NIL_P(fptr->pathv)) { | |
if (fptr->fd < 0) { | |
rb_str_cat(result, closed+1, strlen(closed)-1); | |
} | |
else { | |
rb_str_catf(result, "fd %d", fptr->fd); | |
} | |
} | |
else { | |
rb_str_append(result, fptr->pathv); | |
if (fptr->fd < 0) { | |
rb_str_cat(result, closed, strlen(closed)); | |
} | |
} | |
return rb_str_cat2(result, ">"); | |
} | |
/* | |
* call-seq: | |
* ios.to_io -> ios | |
* | |
* Returns <em>ios</em>. | |
*/ | |
static VALUE | |
rb_io_to_io(VALUE io) | |
{ | |
return io; | |
} | |
/* reading functions */ | |
static long | |
read_buffered_data(char *ptr, long len, rb_io_t *fptr) | |
{ | |
int n; | |
n = READ_DATA_PENDING_COUNT(fptr); | |
if (n <= 0) return 0; | |
if (n > len) n = (int)len; | |
MEMMOVE(ptr, fptr->rbuf.ptr+fptr->rbuf.off, char, n); | |
fptr->rbuf.off += n; | |
fptr->rbuf.len -= n; | |
return n; | |
} | |
static long | |
io_bufread(char *ptr, long len, rb_io_t *fptr) | |
{ | |
long offset = 0; | |
long n = len; | |
long c; | |
if (READ_DATA_PENDING(fptr) == 0) { | |
while (n > 0) { | |
again: | |
c = rb_read_internal(fptr->fd, ptr+offset, n); | |
if (c == 0) break; | |
if (c < 0) { | |
if (fptr_wait_readable(fptr)) | |
goto again; | |
return -1; | |
} | |
offset += c; | |
if ((n -= c) <= 0) break; | |
} | |
return len - n; | |
} | |
while (n > 0) { | |
c = read_buffered_data(ptr+offset, n, fptr); | |
if (c > 0) { | |
offset += c; | |
if ((n -= c) <= 0) break; | |
} | |
rb_io_check_closed(fptr); | |
if (io_fillbuf(fptr) < 0) { | |
break; | |
} | |
} | |
return len - n; | |
} | |
static int io_setstrbuf(VALUE *str, long len); | |
struct bufread_arg { | |
char *str_ptr; | |
long len; | |
rb_io_t *fptr; | |
}; | |
static VALUE | |
bufread_call(VALUE arg) | |
{ | |
struct bufread_arg *p = (struct bufread_arg *)arg; | |
p->len = io_bufread(p->str_ptr, p->len, p->fptr); | |
return Qundef; | |
} | |
static long | |
io_fread(VALUE str, long offset, long size, rb_io_t *fptr) | |
{ | |
VALUE scheduler = rb_scheduler_current(); | |
if (scheduler != Qnil && rb_scheduler_supports_io_read(scheduler)) { | |
ssize_t length = RB_NUM2SSIZE( | |
rb_scheduler_io_read(scheduler, fptr->self, str, offset, size) | |
); | |
if (length < 0) rb_sys_fail_path(fptr->pathv); | |
return length; | |
} | |
long len; | |
struct bufread_arg arg; | |
io_setstrbuf(&str, offset + size); | |
arg.str_ptr = RSTRING_PTR(str) + offset; | |
arg.len = size; | |
arg.fptr = fptr; | |
rb_str_locktmp_ensure(str, bufread_call, (VALUE)&arg); | |
len = arg.len; | |
if (len < 0) rb_sys_fail_path(fptr->pathv); | |
return len; | |
} | |
static long | |
remain_size(rb_io_t *fptr) | |
{ | |
struct stat st; | |
off_t siz = READ_DATA_PENDING_COUNT(fptr); | |
off_t pos; | |
if (fstat(fptr->fd, &st) == 0 && S_ISREG(st.st_mode) | |
#if defined(__HAIKU__) | |
&& (st.st_dev > 3) | |
#endif | |
) | |
{ | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
pos = lseek(fptr->fd, 0, SEEK_CUR); | |
if (st.st_size >= pos && pos >= 0) { | |
siz += st.st_size - pos; | |
if (siz > LONG_MAX) { | |
rb_raise(rb_eIOError, "file too big for single read"); | |
} | |
} | |
} | |
else { | |
siz += BUFSIZ; | |
} | |
return (long)siz; | |
} | |
static VALUE | |
io_enc_str(VALUE str, rb_io_t *fptr) | |
{ | |
rb_enc_associate(str, io_read_encoding(fptr)); | |
return str; | |
} | |
static void | |
make_readconv(rb_io_t *fptr, int size) | |
{ | |
if (!fptr->readconv) { | |
int ecflags; | |
VALUE ecopts; | |
const char *sname, *dname; | |
ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_WRITE_MASK; | |
ecopts = fptr->encs.ecopts; | |
if (fptr->encs.enc2) { | |
sname = rb_enc_name(fptr->encs.enc2); | |
dname = rb_enc_name(fptr->encs.enc); | |
} | |
else { | |
sname = dname = ""; | |
} | |
fptr->readconv = rb_econv_open_opts(sname, dname, ecflags, ecopts); | |
if (!fptr->readconv) | |
rb_exc_raise(rb_econv_open_exc(sname, dname, ecflags)); | |
fptr->cbuf.off = 0; | |
fptr->cbuf.len = 0; | |
if (size < IO_CBUF_CAPA_MIN) size = IO_CBUF_CAPA_MIN; | |
fptr->cbuf.capa = size; | |
fptr->cbuf.ptr = ALLOC_N(char, fptr->cbuf.capa); | |
} | |
} | |
#define MORE_CHAR_SUSPENDED Qtrue | |
#define MORE_CHAR_FINISHED Qnil | |
static VALUE | |
fill_cbuf(rb_io_t *fptr, int ec_flags) | |
{ | |
const unsigned char *ss, *sp, *se; | |
unsigned char *ds, *dp, *de; | |
rb_econv_result_t res; | |
int putbackable; | |
int cbuf_len0; | |
VALUE exc; | |
ec_flags |= ECONV_PARTIAL_INPUT; | |
if (fptr->cbuf.len == fptr->cbuf.capa) | |
return MORE_CHAR_SUSPENDED; /* cbuf full */ | |
if (fptr->cbuf.len == 0) | |
fptr->cbuf.off = 0; | |
else if (fptr->cbuf.off + fptr->cbuf.len == fptr->cbuf.capa) { | |
memmove(fptr->cbuf.ptr, fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.len); | |
fptr->cbuf.off = 0; | |
} | |
cbuf_len0 = fptr->cbuf.len; | |
while (1) { | |
ss = sp = (const unsigned char *)fptr->rbuf.ptr + fptr->rbuf.off; | |
se = sp + fptr->rbuf.len; | |
ds = dp = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.off + fptr->cbuf.len; | |
de = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.capa; | |
res = rb_econv_convert(fptr->readconv, &sp, se, &dp, de, ec_flags); | |
fptr->rbuf.off += (int)(sp - ss); | |
fptr->rbuf.len -= (int)(sp - ss); | |
fptr->cbuf.len += (int)(dp - ds); | |
putbackable = rb_econv_putbackable(fptr->readconv); | |
if (putbackable) { | |
rb_econv_putback(fptr->readconv, (unsigned char *)fptr->rbuf.ptr + fptr->rbuf.off - putbackable, putbackable); | |
fptr->rbuf.off -= putbackable; | |
fptr->rbuf.len += putbackable; | |
} | |
exc = rb_econv_make_exception(fptr->readconv); | |
if (!NIL_P(exc)) | |
return exc; | |
if (cbuf_len0 != fptr->cbuf.len) | |
return MORE_CHAR_SUSPENDED; | |
if (res == econv_finished) { | |
return MORE_CHAR_FINISHED; | |
} | |
if (res == econv_source_buffer_empty) { | |
if (fptr->rbuf.len == 0) { | |
READ_CHECK(fptr); | |
if (io_fillbuf(fptr) < 0) { | |
if (!fptr->readconv) { | |
return MORE_CHAR_FINISHED; | |
} | |
ds = dp = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.off + fptr->cbuf.len; | |
de = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.capa; | |
res = rb_econv_convert(fptr->readconv, NULL, NULL, &dp, de, 0); | |
fptr->cbuf.len += (int)(dp - ds); | |
rb_econv_check_error(fptr->readconv); | |
break; | |
} | |
} | |
} | |
} | |
if (cbuf_len0 != fptr->cbuf.len) | |
return MORE_CHAR_SUSPENDED; | |
return MORE_CHAR_FINISHED; | |
} | |
static VALUE | |
more_char(rb_io_t *fptr) | |
{ | |
VALUE v; | |
v = fill_cbuf(fptr, ECONV_AFTER_OUTPUT); | |
if (v != MORE_CHAR_SUSPENDED && v != MORE_CHAR_FINISHED) | |
rb_exc_raise(v); | |
return v; | |
} | |
static VALUE | |
io_shift_cbuf(rb_io_t *fptr, int len, VALUE *strp) | |
{ | |
VALUE str = Qnil; | |
if (strp) { | |
str = *strp; | |
if (NIL_P(str)) { | |
*strp = str = rb_str_new(fptr->cbuf.ptr+fptr->cbuf.off, len); | |
} | |
else { | |
rb_str_cat(str, fptr->cbuf.ptr+fptr->cbuf.off, len); | |
} | |
rb_enc_associate(str, fptr->encs.enc); | |
} | |
fptr->cbuf.off += len; | |
fptr->cbuf.len -= len; | |
/* xxx: set coderange */ | |
if (fptr->cbuf.len == 0) | |
fptr->cbuf.off = 0; | |
else if (fptr->cbuf.capa/2 < fptr->cbuf.off) { | |
memmove(fptr->cbuf.ptr, fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.len); | |
fptr->cbuf.off = 0; | |
} | |
return str; | |
} | |
static int | |
io_setstrbuf(VALUE *str, long len) | |
{ | |
#ifdef _WIN32 | |
len = (len + 1) & ~1L; /* round up for wide char */ | |
#endif | |
if (NIL_P(*str)) { | |
*str = rb_str_new(0, len); | |
return TRUE; | |
} | |
else { | |
VALUE s = StringValue(*str); | |
long clen = RSTRING_LEN(s); | |
if (clen >= len) { | |
rb_str_modify(s); | |
return FALSE; | |
} | |
len -= clen; | |
} | |
rb_str_modify_expand(*str, len); | |
return FALSE; | |
} | |
#define MAX_REALLOC_GAP 4096 | |
static void | |
io_shrink_read_string(VALUE str, long n) | |
{ | |
if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) { | |
rb_str_resize(str, n); | |
} | |
} | |
static void | |
io_set_read_length(VALUE str, long n, int shrinkable) | |
{ | |
if (RSTRING_LEN(str) != n) { | |
rb_str_modify(str); | |
rb_str_set_len(str, n); | |
if (shrinkable) io_shrink_read_string(str, n); | |
} | |
} | |
static VALUE | |
read_all(rb_io_t *fptr, long siz, VALUE str) | |
{ | |
long bytes; | |
long n; | |
long pos; | |
rb_encoding *enc; | |
int cr; | |
int shrinkable; | |
if (NEED_READCONV(fptr)) { | |
int first = !NIL_P(str); | |
SET_BINARY_MODE(fptr); | |
shrinkable = io_setstrbuf(&str,0); | |
make_readconv(fptr, 0); | |
while (1) { | |
VALUE v; | |
if (fptr->cbuf.len) { | |
if (first) rb_str_set_len(str, first = 0); | |
io_shift_cbuf(fptr, fptr->cbuf.len, &str); | |
} | |
v = fill_cbuf(fptr, 0); | |
if (v != MORE_CHAR_SUSPENDED && v != MORE_CHAR_FINISHED) { | |
if (fptr->cbuf.len) { | |
if (first) rb_str_set_len(str, first = 0); | |
io_shift_cbuf(fptr, fptr->cbuf.len, &str); | |
} | |
rb_exc_raise(v); | |
} | |
if (v == MORE_CHAR_FINISHED) { | |
clear_readconv(fptr); | |
if (first) rb_str_set_len(str, first = 0); | |
if (shrinkable) io_shrink_read_string(str, RSTRING_LEN(str)); | |
return io_enc_str(str, fptr); | |
} | |
} | |
} | |
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); | |
bytes = 0; | |
pos = 0; | |
enc = io_read_encoding(fptr); | |
cr = 0; | |
if (siz == 0) siz = BUFSIZ; | |
shrinkable = io_setstrbuf(&str, siz); | |
for (;;) { | |
READ_CHECK(fptr); | |
n = io_fread(str, bytes, siz - bytes, fptr); | |
if (n == 0 && bytes == 0) { | |
rb_str_set_len(str, 0); | |
break; | |
} | |
bytes += n; | |
rb_str_set_len(str, bytes); | |
if (cr != ENC_CODERANGE_BROKEN) | |
pos += rb_str_coderange_scan_restartable(RSTRING_PTR(str) + pos, RSTRING_PTR(str) + bytes, enc, &cr); | |
if (bytes < siz) break; | |
siz += BUFSIZ; | |
rb_str_modify_expand(str, BUFSIZ); | |
} | |
if (shrinkable) io_shrink_read_string(str, RSTRING_LEN(str)); | |
str = io_enc_str(str, fptr); | |
ENC_CODERANGE_SET(str, cr); | |
return str; | |
} | |
void | |
rb_io_set_nonblock(rb_io_t *fptr) | |
{ | |
if (rb_fd_set_nonblock(fptr->fd) != 0) { | |
rb_sys_fail_path(fptr->pathv); | |
} | |
} | |
static VALUE | |
read_internal_call(VALUE arg) | |
{ | |
struct io_internal_read_struct *iis = (struct io_internal_read_struct *)arg; | |
return rb_thread_io_blocking_region(internal_read_func, iis, iis->fd); | |
} | |
static long | |
read_internal_locktmp(VALUE str, struct io_internal_read_struct *iis) | |
{ | |
return (long)rb_str_locktmp_ensure(str, read_internal_call, (VALUE)iis); | |
} | |
#define no_exception_p(opts) !rb_opts_exception_p((opts), TRUE) | |
static VALUE | |
io_getpartial(int argc, VALUE *argv, VALUE io, int no_exception, int nonblock) | |
{ | |
rb_io_t *fptr; | |
VALUE length, str; | |
long n, len; | |
struct io_internal_read_struct iis; | |
int shrinkable; | |
rb_scan_args(argc, argv, "11", &length, &str); | |
if ((len = NUM2LONG(length)) < 0) { | |
rb_raise(rb_eArgError, "negative length %ld given", len); | |
} | |
shrinkable = io_setstrbuf(&str, len); | |
GetOpenFile(io, fptr); | |
rb_io_check_byte_readable(fptr); | |
if (len == 0) | |
return str; | |
if (!nonblock) | |
READ_CHECK(fptr); | |
n = read_buffered_data(RSTRING_PTR(str), len, fptr); | |
if (n <= 0) { | |
again: | |
if (nonblock) { | |
rb_io_set_nonblock(fptr); | |
} | |
io_setstrbuf(&str, len); | |
iis.th = rb_thread_current(); | |
iis.fd = fptr->fd; | |
iis.nonblock = nonblock; | |
iis.buf = RSTRING_PTR(str); | |
iis.capa = len; | |
n = read_internal_locktmp(str, &iis); | |
if (n < 0) { | |
int e = errno; | |
if (!nonblock && fptr_wait_readable(fptr)) | |
goto again; | |
if (nonblock && (e == EWOULDBLOCK || e == EAGAIN)) { | |
if (no_exception) | |
return sym_wait_readable; | |
else | |
rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE, | |
e, "read would block"); | |
} | |
rb_syserr_fail_path(e, fptr->pathv); | |
} | |
} | |
io_set_read_length(str, n, shrinkable); | |
if (n == 0) | |
return Qnil; | |
else | |
return str; | |
} | |
/* | |
* call-seq: | |
* ios.readpartial(maxlen) -> string | |
* ios.readpartial(maxlen, outbuf) -> outbuf | |
* | |
* Reads at most <i>maxlen</i> bytes from the I/O stream. | |
* It blocks only if <em>ios</em> has no data immediately available. | |
* It doesn't block if some data available. | |
* | |
* If the optional _outbuf_ argument is present, | |
* it must reference a String, which will receive the data. | |
* The _outbuf_ will contain only the received data after the method call | |
* even if it is not empty at the beginning. | |
* | |
* It raises EOFError on end of file. | |
* | |
* readpartial is designed for streams such as pipe, socket, tty, etc. | |
* It blocks only when no data immediately available. | |
* This means that it blocks only when following all conditions hold. | |
* * the byte buffer in the IO object is empty. | |
* * the content of the stream is empty. | |
* * the stream is not reached to EOF. | |
* | |
* When readpartial blocks, it waits data or EOF on the stream. | |
* If some data is reached, readpartial returns with the data. | |
* If EOF is reached, readpartial raises EOFError. | |
* | |
* When readpartial doesn't blocks, it returns or raises immediately. | |
* If the byte buffer is not empty, it returns the data in the buffer. | |
* Otherwise if the stream has some content, | |
* it returns the data in the stream. | |
* Otherwise if the stream is reached to EOF, it raises EOFError. | |
* | |
* r, w = IO.pipe # buffer pipe content | |
* w << "abc" # "" "abc". | |
* r.readpartial(4096) #=> "abc" "" "" | |
* r.readpartial(4096) # blocks because buffer and pipe is empty. | |
* | |
* r, w = IO.pipe # buffer pipe content | |
* w << "abc" # "" "abc" | |
* w.close # "" "abc" EOF | |
* r.readpartial(4096) #=> "abc" "" EOF | |
* r.readpartial(4096) # raises EOFError | |
* | |
* r, w = IO.pipe # buffer pipe content | |
* w << "abc\ndef\n" # "" "abc\ndef\n" | |
* r.gets #=> "abc\n" "def\n" "" | |
* w << "ghi\n" # "def\n" "ghi\n" | |
* r.readpartial(4096) #=> "def\n" "" "ghi\n" | |
* r.readpartial(4096) #=> "ghi\n" "" "" | |
* | |
* Note that readpartial behaves similar to sysread. | |
* The differences are: | |
* * If the byte buffer is not empty, read from the byte buffer | |
* instead of "sysread for buffered IO (IOError)". | |
* * It doesn't cause Errno::EWOULDBLOCK and Errno::EINTR. When | |
* readpartial meets EWOULDBLOCK and EINTR by read system call, | |
* readpartial retry the system call. | |
* | |
* The latter means that readpartial is nonblocking-flag insensitive. | |
* It blocks on the situation IO#sysread causes Errno::EWOULDBLOCK as | |
* if the fd is blocking mode. | |
* | |
*/ | |
static VALUE | |
io_readpartial(int argc, VALUE *argv, VALUE io) | |
{ | |
VALUE ret; | |
ret = io_getpartial(argc, argv, io, Qnil, 0); | |
if (NIL_P(ret)) | |
rb_eof_error(); | |
return ret; | |
} | |
static VALUE | |
io_nonblock_eof(int no_exception) | |
{ | |
if (!no_exception) { | |
rb_eof_error(); | |
} | |
return Qnil; | |
} | |
/* :nodoc: */ | |
static VALUE | |
io_read_nonblock(rb_execution_context_t *ec, VALUE io, VALUE length, VALUE str, VALUE ex) | |
{ | |
rb_io_t *fptr; | |
long n, len; | |
struct io_internal_read_struct iis; | |
int shrinkable; | |
if ((len = NUM2LONG(length)) < 0) { | |
rb_raise(rb_eArgError, "negative length %ld given", len); | |
} | |
shrinkable = io_setstrbuf(&str, len); | |
rb_bool_expected(ex, "exception"); | |
GetOpenFile(io, fptr); | |
rb_io_check_byte_readable(fptr); | |
if (len == 0) | |
return str; | |
n = read_buffered_data(RSTRING_PTR(str), len, fptr); | |
if (n <= 0) { | |
rb_io_set_nonblock(fptr); | |
shrinkable |= io_setstrbuf(&str, len); | |
iis.fd = fptr->fd; | |
iis.nonblock = 1; | |
iis.buf = RSTRING_PTR(str); | |
iis.capa = len; | |
n = read_internal_locktmp(str, &iis); | |
if (n < 0) { | |
int e = errno; | |
if ((e == EWOULDBLOCK || e == EAGAIN)) { | |
if (!ex) return sym_wait_readable; | |
rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE, | |
e, "read would block"); | |
} | |
rb_syserr_fail_path(e, fptr->pathv); | |
} | |
} | |
io_set_read_length(str, n, shrinkable); | |
if (n == 0) { | |
if (!ex) return Qnil; | |
rb_eof_error(); | |
} | |
return str; | |
} | |
/* :nodoc: */ | |
static VALUE | |
io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex) | |
{ | |
rb_io_t *fptr; | |
long n; | |
if (!RB_TYPE_P(str, T_STRING)) | |
str = rb_obj_as_string(str); | |
rb_bool_expected(ex, "exception"); | |
io = GetWriteIO(io); | |
GetOpenFile(io, fptr); | |
rb_io_check_writable(fptr); | |
if (io_fflush(fptr) < 0) | |
rb_sys_fail_on_write(fptr); | |
rb_io_set_nonblock(fptr); | |
n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); | |
RB_GC_GUARD(str); | |
if (n < 0) { | |
int e = errno; | |
if (e == EWOULDBLOCK || e == EAGAIN) { | |
if (!ex) { | |
return sym_wait_writable; | |
} | |
else { | |
rb_readwrite_syserr_fail(RB_IO_WAIT_WRITABLE, e, "write would block"); | |
} | |
} | |
rb_syserr_fail_path(e, fptr->pathv); | |
} | |
return LONG2FIX(n); | |
} | |
/* | |
* call-seq: | |
* ios.read([length [, outbuf]]) -> string, outbuf, or nil | |
* | |
* Reads _length_ bytes from the I/O stream. | |
* | |
* _length_ must be a non-negative integer or +nil+. | |
* | |
* If _length_ is a positive integer, +read+ tries to read | |
* _length_ bytes without any conversion (binary mode). | |
* It returns +nil+ if an EOF is encountered before anything can be read. | |
* Fewer than _length_ bytes are returned if an EOF is encountered during | |
* the read. | |
* In the case of an integer _length_, the resulting string is always | |
* in ASCII-8BIT encoding. | |
* | |
* If _length_ is omitted or is +nil+, it reads until EOF | |
* and the encoding conversion is applied, if applicable. | |
* A string is returned even if EOF is encountered before any data is read. | |
* | |
* If _length_ is zero, it returns an empty string (<code>""</code>). | |
* | |
* If the optional _outbuf_ argument is present, | |
* it must reference a String, which will receive the data. | |
* The _outbuf_ will contain only the received data after the method call | |
* even if it is not empty at the beginning. | |
* | |
* When this method is called at end of file, it returns +nil+ | |
* or <code>""</code>, depending on _length_: | |
* +read+, <code>read(nil)</code>, and <code>read(0)</code> return | |
* <code>""</code>, | |
* <code>read(<i>positive_integer</i>)</code> returns +nil+. | |
* | |
* f = File.new("testfile") | |
* f.read(16) #=> "This is line one" | |
* | |
* # read whole file | |
* open("file") do |f| | |
* data = f.read # This returns a string even if the file is empty. | |
* # ... | |
* end | |
* | |
* # iterate over fixed length records | |
* open("fixed-record-file") do |f| | |
* while record = f.read(256) | |
* # ... | |
* end | |
* end | |
* | |
* # iterate over variable length records, | |
* # each record is prefixed by its 32-bit length | |
* open("variable-record-file") do |f| | |
* while len = f.read(4) | |
* len = len.unpack("N")[0] # 32-bit length | |
* record = f.read(len) # This returns a string even if len is 0. | |
* end | |
* end | |
* | |
* Note that this method behaves like the fread() function in C. | |
* This means it retries to invoke read(2) system calls to read data | |
* with the specified length (or until EOF). | |
* This behavior is preserved even if <i>ios</i> is in non-blocking mode. | |
* (This method is non-blocking flag insensitive as other methods.) | |
* If you need the behavior like a single read(2) system call, | |
* consider #readpartial, #read_nonblock, and #sysread. | |
*/ | |
static VALUE | |
io_read(int argc, VALUE *argv, VALUE io) | |
{ | |
rb_io_t *fptr; | |
long n, len; | |
VALUE length, str; | |
int shrinkable; | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
int previous_mode; | |
#endif | |
rb_scan_args(argc, argv, "02", &length, &str); | |
if (NIL_P(length)) { | |
GetOpenFile(io, fptr); | |
rb_io_check_char_readable(fptr); | |
return read_all(fptr, remain_size(fptr), str); | |
} | |
len = NUM2LONG(length); | |
if (len < 0) { | |
rb_raise(rb_eArgError, "negative length %ld given", len); | |
} | |
shrinkable = io_setstrbuf(&str,len); | |
GetOpenFile(io, fptr); | |
rb_io_check_byte_readable(fptr); | |
if (len == 0) { | |
io_set_read_length(str, 0, shrinkable); | |
return str; | |
} | |
READ_CHECK(fptr); | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
previous_mode = set_binary_mode_with_seek_cur(fptr); | |
#endif | |
n = io_fread(str, 0, len, fptr); | |
io_set_read_length(str, n, shrinkable); | |
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) | |
if (previous_mode == O_TEXT) { | |
setmode(fptr->fd, O_TEXT); | |
} | |
#endif | |
if (n == 0) return Qnil; | |
return str; | |
} | |
static void | |
rscheck(const char *rsptr, long rslen, VALUE rs) | |
{ | |
if (!rs) return; | |
if (RSTRING_PTR(rs) != rsptr && RSTRING_LEN(rs) != rslen) | |
rb_raise(rb_eRuntimeError, "rs modified"); | |
} | |
static int | |
appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp) | |
{ | |
VALUE str = *strp; | |
long limit = *lp; | |
if (NEED_READCONV(fptr)) { | |
SET_BINARY_MODE(fptr); | |
make_readconv(fptr, 0); | |
do { | |
const char *p, *e; | |
int searchlen = READ_CHAR_PENDING_COUNT(fptr); | |
if (searchlen) { | |
p = READ_CHAR_PENDING_PTR(fptr); | |
if (0 < limit && limit < searchlen) | |
searchlen = (int)limit; | |
e = memchr(p, delim, searchlen); | |
if (e) { | |
int len = (int)(e-p+1); | |
if (NIL_P(str)) | |
*strp = str = rb_str_new(p, len); | |
else | |
rb_str_buf_cat(str, p, len); | |
fptr->cbuf.off += len; | |
fptr->cbuf.len -= len; | |
limit -= len; | |
*lp = limit; | |
return delim; | |
} | |
if (NIL_P(str)) | |
*strp = str = rb_str_new(p, searchlen); | |
else | |
rb_str_buf_cat(str, p, searchlen); | |
fptr->cbuf.off += searchlen; | |
fptr->cbuf.len -= searchlen; | |
limit -= searchlen; | |
if (limit == 0) { | |
*lp = limit; | |
return (unsigned char)RSTRING_PTR(str)[RSTRING_LEN(str)-1]; | |
} | |
} | |
} while (more_char(fptr) != MORE_CHAR_FINISHED); | |
clear_readconv(fptr); | |
*lp = limit; | |
return EOF; | |
} | |
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); | |
do { | |
long pending = READ_DATA_PENDING_COUNT(fptr); | |
if (pending > 0) { | |
const char *p = READ_DATA_PENDING_PTR(fptr); | |
const char *e; | |
long last; | |
if (limit > 0 && pending > limit) pending = limit; | |
e = memchr(p, delim, pending); | |
if (e) pending = e - p + 1; | |
if (!NIL_P(str)) { | |
last = RSTRING_LEN(str); | |
rb_str_resize(str, last + pending); | |
} | |
else { | |
last = 0; | |
*strp = str = rb_str_buf_new(pending); | |
rb_str_set_len(str, pending); | |
} | |
read_buffered_data(RSTRING_PTR(str) + last, pending, fptr); /* must not fail */ | |
limit -= pending; | |
*lp = limit; | |
if (e) return delim; | |
if (limit == 0) | |
return (unsigned char)RSTRING_PTR(str)[RSTRING_LEN(str)-1]; | |
} | |
READ_CHECK(fptr); | |
} while (io_fillbuf(fptr) >= 0); | |
*lp = limit; | |
return EOF; | |
} | |
static inline int | |
swallow(rb_io_t *fptr, int term) | |
{ | |
if (NEED_READCONV(fptr)) { | |
rb_encoding *enc = io_read_encoding(fptr); | |
int needconv = rb_enc_mbminlen(enc) != 1; | |
SET_BINARY_MODE(fptr); | |
make_readconv(fptr, 0); | |
do { | |
size_t cnt; | |
while ((cnt = READ_CHAR_PENDING_COUNT(fptr)) > 0) { | |
const char *p = READ_CHAR_PENDING_PTR(fptr); | |
int i; | |
if (!needconv) { | |
if (*p != term) return TRUE; | |
i = (int)cnt; | |
while (--i && *++p == term); | |
} | |
else { | |
const char *e = p + cnt; | |
if (rb_enc_ascget(p, e, &i, enc) != term) return TRUE; | |
while ((p += i) < e && rb_enc_ascget(p, e, &i, enc) == term); | |
i = (int)(e - p); | |
} | |
io_shift_cbuf(fptr, (int)cnt - i, NULL); | |
} | |
} while (more_char(fptr) != MORE_CHAR_FINISHED); | |
return FALSE; | |
} | |
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); | |
do { | |
size_t cnt; | |
while ((cnt = READ_DATA_PENDING_COUNT(fptr)) > 0) { | |
char buf[1024]; | |
const char *p = READ_DATA_PENDING_PTR(fptr); | |
int i; | |
if (cnt > sizeof buf) cnt = sizeof buf; | |
if (*p != term) return TRUE; | |
i = (int)cnt; | |
while (--i && *++p == term); | |
if (!read_buffered_data(buf, cnt - i, fptr)) /* must not fail */ | |
rb_sys_fail_path(fptr->pathv); | |
} | |
READ_CHECK(fptr); | |
} while (io_fillbuf(fptr) == 0); | |
return FALSE; | |
} | |
static VALUE | |
rb_io_getline_fast(rb_io_t *fptr, rb_encoding *enc, int chomp) | |
{ | |
VALUE str = Qnil; | |
int len = 0; | |
long pos = 0; | |
int cr = 0; | |
do { | |
int pending = READ_DATA_PENDING_COUNT(fptr); | |
if (pending > 0) { | |
const char *p = READ_DATA_PENDING_PTR(fptr); | |
const char *e; | |
int chomplen = 0; | |
e = memchr(p, '\n', pending); | |
if (e) { | |
pending = (int)(e - p + 1); | |
if (chomp) { | |
chomplen = (pending > 1 && *(e-1) == '\r') + 1; | |
} | |
} | |
if (NIL_P(str)) { | |
str = rb_str_new(p, pending - chomplen); | |
fptr->rbuf.off += pending; | |
fptr->rbuf.len -= pending; | |
} | |
else { | |
rb_str_resize(str, len + pending - chomplen); | |
read_buffered_data(RSTRING_PTR(str)+len, pending - chomplen, fptr); | |
fptr->rbuf.off += chomplen; | |
fptr->rbuf.len -= chomplen; | |
if (pending == 1 && chomplen == 1 && len > 0) { | |
if (RSTRING_PTR(str)[len-1] == '\r') { | |
rb_str_resize(str, --len); | |
break; | |
} | |
} | |
} | |
len += pending - chomplen; | |
if (cr != ENC_CODERANGE_BROKEN) | |
pos += rb_str_coderange_scan_restartable(RSTRING_PTR(str) + pos, RSTRING_PTR(str) + len, enc, &cr); | |
if (e) break; | |
} | |
READ_CHECK(fptr); | |
} while (io_fillbuf(fptr) >= 0); | |
if (NIL_P(str)) return Qnil; | |
str = io_enc_str(str, fptr); | |
ENC_CODERANGE_SET(str, cr); | |
fptr->lineno++; | |
return str; | |
} | |
struct getline_arg { | |
VALUE io; | |
VALUE rs; | |
long limit; | |
unsigned int chomp: 1; | |
}; | |
static void | |
extract_getline_opts(VALUE opts, struct getline_arg *args) | |
{ | |
int chomp = FALSE; | |
if (!NIL_P(opts)) { | |
static ID kwds[1]; | |
VALUE vchomp; | |
if (!kwds[0]) { | |
kwds[0] = rb_intern_const("chomp"); | |
} | |
rb_get_kwargs(opts, kwds, 0, -2, &vchomp); | |
chomp = (vchomp != Qundef) && RTEST(vchomp); | |
} | |
args->chomp = chomp; | |
} | |
static void | |
extract_getline_args(int argc, VALUE *argv, struct getline_arg *args) | |
{ | |
VALUE rs = rb_rs, lim = Qnil; | |
if (argc == 1) { | |
VALUE tmp = Qnil; | |
if (NIL_P(argv[0]) || !NIL_P(tmp = rb_check_string_type(argv[0]))) { | |
rs = tmp; | |
} | |
else { | |
lim = argv[0]; | |
} | |
} | |
else if (2 <= argc) { | |
rs = argv[0], lim = argv[1]; | |
if (!NIL_P(rs)) | |
StringValue(rs); | |
} | |
args->rs = rs; | |
args->limit = NIL_P(lim) ? -1L : NUM2LONG(lim); | |
} | |
static void | |
check_getline_args(VALUE *rsp, long *limit, VALUE io) | |
{ | |
rb_io_t *fptr; | |
VALUE rs = *rsp; | |
if (!NIL_P(rs)) { | |
rb_encoding *enc_rs, *enc_io; | |
GetOpenFile(io, fptr); | |
enc_rs = rb_enc_get(rs); | |
enc_io = io_read_encoding(fptr); | |
if (enc_io != enc_rs && | |
(rb_enc_str_coderange(rs) != ENC_CODERANGE_7BIT || | |
(RSTRING_LEN(rs) > 0 && !rb_enc_asciicompat(enc_io)))) { | |
if (rs == rb_default_rs) { | |
rs = rb_enc_str_new(0, 0, enc_io); | |
rb_str_buf_cat_ascii(rs, "\n"); | |
*rsp = rs; | |
} | |
else { | |
rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS", | |
rb_enc_name(enc_io), | |
rb_enc_name(enc_rs)); | |
} | |
} | |
} | |
} | |
static void | |
prepare_getline_args(int argc, VALUE *argv, struct getline_arg *args, VALUE io) | |
{ | |
VALUE opts; | |
argc = rb_scan_args(argc, argv, "02:", NULL, NULL, &opts); | |
extract_getline_args(argc, argv, args); | |
extract_getline_opts(opts, args); | |
check_getline_args(&args->rs, &args->limit, io); | |
} | |
static VALUE | |
rb_io_getline_0(VALUE rs, long limit, int chomp, rb_io_t *fptr) | |
{ | |
VALUE str = Qnil; | |
int nolimit = 0; | |
rb_encoding *enc; | |
rb_io_check_char_readable(fptr); | |
if (NIL_P(rs) && limit < 0) { | |
str = read_all(fptr, 0, Qnil); | |
if (RSTRING_LEN(str) == 0) return Qnil; | |
if (chomp) rb_str_chomp_string(str, rb_default_rs); | |
} | |
else if (limit == 0) { | |
return rb_enc_str_new(0, 0, io_read_encoding(fptr)); | |
} | |
else if (rs == rb_default_rs && limit < 0 && !NEED_READCONV(fptr) && | |
rb_enc_asciicompat(enc = io_read_encoding(fptr))) { | |
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); | |
return rb_io_getline_fast(fptr, enc, chomp); | |
} | |
else { | |
int c, newline = -1; | |
const char *rsptr = 0; | |
long rslen = 0; | |
int rspara = 0; | |
int extra_limit = 16; | |
int chomp_cr = chomp; | |
SET_BINARY_MODE(fptr); | |
enc = io_read_encoding(fptr); | |
< |