Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

11712 lines (10597 sloc) 303.505 kb
/**********************************************************************
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/ruby.h"
#include "ruby/io.h"
#include "ruby/thread.h"
#include "dln.h"
#include "internal.h"
#include <ctype.h>
#include <errno.h>
#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
# ifndef __native_client__
# include <sys/socket.h>
# endif
#endif
#if defined(__BOW__) || defined(__CYGWIN__) || defined(_WIN32) || defined(__EMX__) || defined(__BEOS__) || defined(__HAIKU__)
# 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(__native_client__) && defined(NACL_NEWLIB)
# include "nacl/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
#include <sys/stat.h>
/* EMX has sys/param.h, but.. */
#if defined(HAVE_SYS_PARAM_H) && !(defined(__EMX__) || 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
#if defined(__BEOS__) || defined(__HAIKU__)
# ifndef NOFILE
# define NOFILE (OPEN_MAX)
# endif
#endif
#include "ruby/util.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
#define numberof(array) (int)(sizeof(array) / sizeof((array)[0]))
#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
#endif
VALUE rb_cIO;
VALUE rb_eEOFError;
VALUE rb_eIOError;
VALUE rb_mWaitReadable;
VALUE rb_mWaitWritable;
VALUE rb_stdin, rb_stdout, rb_stderr;
VALUE rb_deferr; /* rescue VIM plugin */
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_extenc, sym_intenc, sym_encoding, sym_open_args;
static VALUE sym_textmode, sym_binmode, sym_autoclose;
struct argf {
VALUE filename, current_file;
long last_lineno; /* $. */
long lineno;
VALUE argv;
char *inplace;
struct rb_io_enc_t encs;
int8_t init_p, next_p, binmode;
};
static int max_file_descriptor = NOFILE;
void
rb_update_max_fd(int fd)
{
struct stat buf;
if (fstat(fd, &buf) != 0 && errno == EBADF) {
rb_bug("rb_update_max_fd: invalid fd (%d) given.", fd);
}
if (max_file_descriptor < fd) max_file_descriptor = fd;
}
void
rb_maygvl_fd_fix_cloexec(int fd)
{
/* MinGW don't have F_GETFD and FD_CLOEXEC. [ruby-core:40281] */
#if defined(F_GETFD) && !defined(__native_client__)
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 == -1) {
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);
if (max_file_descriptor < fd) max_file_descriptor = fd;
}
int
rb_cloexec_open(const char *pathname, int flags, mode_t mode)
{
int ret;
#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
ret = open(pathname, flags, mode);
if (ret == -1) return -1;
rb_maygvl_fd_fix_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 == -1) return -1;
}
rb_maygvl_fd_fix_cloexec(ret);
return ret;
}
int
rb_cloexec_pipe(int fildes[2])
{
int ret;
#if defined(HAVE_PIPE2)
static int try_pipe2 = 1;
if (try_pipe2) {
ret = pipe2(fildes, O_CLOEXEC);
if (ret != -1)
return ret;
/* pipe2 is available since Linux 2.6.27, glibc 2.9. */
if (errno == ENOSYS) {
try_pipe2 = 0;
ret = pipe(fildes);
}
}
else {
ret = pipe(fildes);
}
#else
ret = pipe(fildes);
#endif
if (ret == -1) return -1;
#ifdef __CYGWIN__
if (ret == 0 && fildes[1] == -1) {
close(fildes[0]);
fildes[0] = -1;
errno = ENFILE;
return -1;
}
#endif
rb_maygvl_fd_fix_cloexec(fildes[0]);
rb_maygvl_fd_fix_cloexec(fildes[1]);
return ret;
}
int
rb_cloexec_fcntl_dupfd(int fd, int minfd)
{
int ret;
#if defined(HAVE_FCNTL) && defined(F_DUPFD_CLOEXEC)
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);
}
#else
ret = fcntl(fd, F_DUPFD, minfd);
#endif
if (ret == -1) return -1;
rb_maygvl_fd_fix_cloexec(ret);
return ret;
}
#define argf_of(obj) (*(struct argf *)DATA_PTR(obj))
#define ARGF argf_of(argf)
#ifdef _STDIO_USES_IOSTREAM /* GNU libc */
# ifdef _IO_fpos_t
# define STDIO_READ_DATA_PENDING(fp) ((fp)->_IO_read_ptr != (fp)->_IO_read_end)
# else
# define STDIO_READ_DATA_PENDING(fp) ((fp)->_gptr < (fp)->_egptr)
# endif
#elif defined(FILE_COUNT)
# define STDIO_READ_DATA_PENDING(fp) ((fp)->FILE_COUNT > 0)
#elif defined(FILE_READEND)
# define STDIO_READ_DATA_PENDING(fp) ((fp)->FILE_READPTR < (fp)->FILE_READEND)
#elif defined(__BEOS__) || defined(__HAIKU__)
# define STDIO_READ_DATA_PENDING(fp) ((fp)->_state._eof == 0)
#else
# define STDIO_READ_DATA_PENDING(fp) (!feof(fp))
#endif
#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
#define rb_sys_fail_path(path) rb_sys_fail_str(path)
static int io_fflush(rb_io_t *);
static rb_io_t *flush_before_seek(rb_io_t *fptr);
#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 NEED_WRITECONV(fptr) (((fptr)->encs.enc != NULL && (fptr)->encs.enc != rb_ascii8bit_encoding()) || ((fptr)->encs.ecflags & ((ECONV_DECORATOR_MASK & ~ECONV_CRLF_NEWLINE_DECORATOR)|ECONV_STATEFUL_DECORATOR_MASK)))
#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) {
free(buf);
rb_sys_fail_path(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)))
#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
void
rb_eof_error(void)
{
rb_raise(rb_eEOFError, "end of file reached");
}
VALUE
rb_io_taint_check(VALUE io)
{
if (!OBJ_UNTRUSTED(io) && rb_safe_level() >= 4)
rb_raise(rb_eSecurityError, "Insecure: operation on trusted 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);
if (fptr->fd < 0) {
rb_raise(rb_eIOError, "closed stream");
}
}
VALUE
rb_io_get_io(VALUE io)
{
return rb_convert_type(io, T_FILE, "IO", "to_io");
}
VALUE
rb_io_check_io(VALUE io)
{
return rb_check_convert_type(io, T_FILE, "IO", "to_io");
}
VALUE
rb_io_get_write_io(VALUE io)
{
VALUE write_io;
rb_io_check_initialized(RFILE(io)->fptr);
write_io = RFILE(io)->fptr->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_check_initialized(RFILE(io)->fptr);
if (!RTEST(w)) {
w = 0;
}
else {
GetWriteIO(w);
}
write_io = RFILE(io)->fptr->tied_io_for_writing;
RFILE(io)->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(0);
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
#define FMODE_SYNCWRITE (FMODE_SYNC|FMODE_WRITABLE)
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(0);
}
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(0);
}
}
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_read_check(FILE *fp)
{
if (!STDIO_READ_DATA_PENDING(fp)) {
rb_thread_wait_fd(fileno(fp));
}
}
void
rb_io_read_check(rb_io_t *fptr)
{
if (!READ_DATA_PENDING(fptr)) {
rb_thread_wait_fd(fptr->fd);
}
return;
}
static int
ruby_dup(int orig)
{
int fd;
fd = rb_cloexec_dup(orig);
if (fd < 0) {
if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
rb_gc();
fd = rb_cloexec_dup(orig);
}
if (fd < 0) {
rb_sys_fail(0);
}
}
rb_update_max_fd(fd);
return fd;
}
static VALUE
io_alloc(VALUE klass)
{
NEWOBJ(io, struct RFile);
OBJSETUP(io, klass, T_FILE);
io->fptr = 0;
return (VALUE)io;
}
#ifndef S_ISREG
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
static int
wsplit_p(rb_io_t *fptr)
{
#if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
int r;
#endif
if (!(fptr->mode & FMODE_WSPLIT_INITIALIZED)) {
struct stat buf;
if (fstat(fptr->fd, &buf) == 0 &&
!S_ISREG(buf.st_mode)
#if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK)
&& (r = fcntl(fptr->fd, F_GETFL)) != -1 &&
!(r & O_NONBLOCK)
#endif
) {
fptr->mode |= FMODE_WSPLIT;
}
fptr->mode |= FMODE_WSPLIT_INITIALIZED;
}
return fptr->mode & FMODE_WSPLIT;
}
struct io_internal_read_struct {
int fd;
void *buf;
size_t capa;
};
struct io_internal_write_struct {
int fd;
const void *buf;
size_t capa;
};
static VALUE
internal_read_func(void *ptr)
{
struct io_internal_read_struct *iis = ptr;
return read(iis->fd, iis->buf, iis->capa);
}
static VALUE
internal_write_func(void *ptr)
{
struct io_internal_write_struct *iis = ptr;
return write(iis->fd, iis->buf, iis->capa);
}
static ssize_t
rb_read_internal(int fd, void *buf, size_t count)
{
struct io_internal_read_struct iis;
iis.fd = fd;
iis.buf = buf;
iis.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;
iis.fd = fd;
iis.buf = buf;
iis.capa = count;
return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fd);
}
static long
io_writable_length(rb_io_t *fptr, long l)
{
if (PIPE_BUF < l &&
!rb_thread_alone() &&
wsplit_p(fptr)) {
l = PIPE_BUF;
}
return l;
}
static VALUE
io_flush_buffer_sync(void *arg)
{
rb_io_t *fptr = arg;
long l = io_writable_length(fptr, 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 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 inline int
io_flush_buffer(rb_io_t *fptr)
{
if (fptr->write_lock) {
return (int)rb_mutex_synchronize(fptr->write_lock, io_flush_buffer_async, (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;
rb_io_check_closed(fptr);
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;
}
int
rb_io_wait_readable(int f)
{
if (f < 0) {
rb_raise(rb_eIOError, "closed stream");
}
switch (errno) {
case EINTR:
#if defined(ERESTART)
case ERESTART:
#endif
rb_thread_wait_fd(f);
return TRUE;
case EAGAIN:
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
rb_wait_for_single_fd(f, RB_WAITFD_IN, NULL);
return TRUE;
default:
return FALSE;
}
}
int
rb_io_wait_writable(int f)
{
if (f < 0) {
rb_raise(rb_eIOError, "closed stream");
}
switch (errno) {
case EINTR:
#if defined(ERESTART)
case ERESTART:
#endif
rb_thread_fd_writable(f);
return TRUE;
case EAGAIN:
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
rb_wait_for_single_fd(f, RB_WAITFD_OUT, NULL);
return TRUE;
default:
return FALSE;
}
}
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;
};
static VALUE
io_binwrite_string(VALUE arg)
{
struct binwrite_arg *p = (struct binwrite_arg *)arg;
long l = io_writable_length(p->fptr, p->length);
return rb_write_internal(p->fptr->fd, p->ptr, l);
}
static long
io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync)
{
long n, r, offset = 0;
if ((n = len) <= 0) return n;
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();
}
if ((!nosync && (fptr->mode & (FMODE_SYNC|FMODE_TTY))) ||
(fptr->wbuf.ptr && fptr->wbuf.capa <= fptr->wbuf.len + len)) {
struct binwrite_arg arg;
/*
* xxx: use writev to avoid double write if available
* writev may help avoid context switch between "a" and "\n" in
* STDERR.puts "a" [ruby-dev:25080] (rebroken since native threads
* introduced in 1.9)
*/
if (fptr->wbuf.len && 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, ptr+offset, char, len);
fptr->wbuf.len += (int)len;
n = 0;
}
if (io_fflush(fptr) < 0)
return -1L;
if (n == 0)
return len;
rb_io_check_closed(fptr);
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 {
long l = io_writable_length(fptr, n);
r = rb_write_internal(fptr->fd, ptr+offset, l);
}
/* xxx: other threads may modify given string. */
if (r == n) return len;
if (0 <= r) {
offset += r;
n -= r;
errno = EAGAIN;
}
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))
static VALUE
do_writeconv(VALUE str, rb_io_t *fptr)
{
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);
}
if (fptr->writeconv) {
str = rb_econv_str_convert(fptr->writeconv, str, ECONV_PARTIAL_INPUT);
}
}
#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)
{
#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);
return io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str),
fptr, nosync);
}
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;
rb_secure(4);
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);
str = rb_str_new_frozen(str);
GetOpenFile(io, fptr);
rb_io_check_writable(fptr);
n = io_fwrite(str, fptr, nosync);
if (n == -1L) rb_sys_fail_path(fptr->pathv);
return LONG2FIX(n);
}
/*
* call-seq:
* ios.write(string) -> integer
*
* Writes the given string to <em>ios</em>. The stream must be opened
* for writing. If the argument is not a string, it will be converted
* to a string using <code>to_s</code>. Returns the number of bytes
* written.
*
* 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(VALUE io, VALUE str)
{
return io_write(io, str, 0);
}
VALUE
rb_io_write(VALUE io, VALUE str)
{
return rb_funcall(io, id_write, 1, str);
}
/*
* 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;
return (VALUE)fsync(fptr->fd);
}
#endif
/*
* 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)
{
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(0);
#ifdef _WIN32
if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) == FILE_TYPE_DISK) {
rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd);
}
#endif
}
if (fptr->mode & FMODE_READABLE) {
io_unread(fptr);
}
return io;
}
/*
* 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);
}
/*
* 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>:
*
* IO::SEEK_CUR | Seeks to _amount_ plus current position
* --------------+----------------------------------------------------
* IO::SEEK_END | Seeks to _amount_ plus end of stream (you probably
* | want a negative value for _amount_)
* --------------+----------------------------------------------------
* 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 = NUM2INT(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 guranteed 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
* <code>lineno</code> 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);
#ifdef _WIN32
if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) == FILE_TYPE_DISK) {
fsync(fptr->fd);
}
#endif
if (io == ARGF.current_file) {
ARGF.lineno -= fptr->lineno;
}
fptr->lineno = 0;
if (fptr->readconv) {
clear_readconv(fptr);
}
return INT2FIX(0);
}
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 (rb_io_wait_readable(fptr->fd))
goto retry;
rb_sys_fail_path(fptr->pathv);
}
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 <code>IOError</code> 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, <code>IO#eof?</code>
* 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 <code>IO#eof?</code> reads data to the input byte buffer.
* So <code>IO#sysread</code> may not behave as you intend with
* <code>IO#eof?</code>, unless you call <code>IO#rewind</code>
* 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
* <code>IO#fsync</code>.
*
* 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 <code>IO#fsync</code>.
*
* f = File.new("testfile")
* f.sync = true
*
* <em>(produces no output)</em>
*/
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 <code>fsync</code> differs from
* using <code>IO#sync=</code>. 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.
*
* <code>NotImplementedError</code> 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(0);
# ifndef _WIN32 /* already called in io_fflush() */
if ((int)rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd) < 0)
rb_sys_fail_path(fptr->pathv);
# endif
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;
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>,
* <code>IO#fsync</code> is called instead (which might raise a
* <code>NotImplementedError</code>).
*/
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(0);
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 -> fixnum
* ios.to_i -> fixnum
*
* 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;
int fd;
GetOpenFile(io, fptr);
fd = fptr->fd;
return INT2FIX(fd);
}
/*
* call-seq:
* ios.pid -> fixnum
*
* Returns the process ID of a child process associated with
* <em>ios</em>. This will be set by <code>IO.popen</code>.
*
* 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(rb_io_taint_check(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 (rb_io_wait_readable(fptr->fd))
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 void io_setstrbuf(VALUE *str, long len);
static long
io_fread(VALUE str, long offset, long size, rb_io_t *fptr)
{
long len;
io_setstrbuf(&str, offset + size);
rb_str_locktmp(str);
len = io_bufread(RSTRING_PTR(str) + offset, size, fptr);
rb_str_unlocktmp(str);
if (len < 0) rb_sys_fail_path(fptr->pathv);
return len;
}
ssize_t
rb_io_bufread(VALUE io, void *buf, size_t size)
{
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_check_readable(fptr);
return (ssize_t)io_bufread(buf, (long)size, fptr);
}
#define SMALLBUF 100
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(__BEOS__) || defined(__HAIKU__)
&& (st.st_dev > 3)
#endif
)
{
if (io_fflush(fptr) < 0)
rb_sys_fail(0);
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)
{
OBJ_TAINT(str);
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) == -1) {
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);
}
OBJ_TAINT(str);
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 void
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, 0);
}
else {
VALUE s = StringValue(*str);
long clen = RSTRING_LEN(s);
if (clen >= len) {
if (clen != len) {
rb_str_modify(s);
rb_str_set_len(s, len);
}
return;
}
len -= clen;
}
rb_str_modify_expand(*str, len);
}
static void
io_set_read_length(VALUE str, long n)
{
if (RSTRING_LEN(str) != n) {
rb_str_modify(str);
rb_str_set_len(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;
if (NEED_READCONV(fptr)) {
SET_BINARY_MODE(fptr);
io_setstrbuf(&str,0);
make_readconv(fptr, 0);
while (1) {
VALUE v;
if (fptr->cbuf.len) {
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) {
io_shift_cbuf(fptr, fptr->cbuf.len, &str);
}
rb_exc_raise(v);
}
if (v == MORE_CHAR_FINISHED) {
clear_readconv(fptr);
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;
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);
}
str = io_enc_str(str, fptr);
ENC_CODERANGE_SET(str, cr);
return str;
}
void
rb_io_set_nonblock(rb_io_t *fptr)
{
int oflags;
#ifdef F_GETFL
oflags = fcntl(fptr->fd, F_GETFL);
if (oflags == -1) {
rb_sys_fail_path(fptr->pathv);
}
#else
oflags = 0;
#endif
if ((oflags & O_NONBLOCK) == 0) {
oflags |= O_NONBLOCK;
if (fcntl(fptr->fd, F_SETFL, oflags) == -1) {
rb_sys_fail_path(fptr->pathv);
}
}
}
static VALUE
io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock)
{
rb_io_t *fptr;
VALUE length, str;
long n, len;
rb_scan_args(argc, argv, "11", &length, &str);
if ((len = NUM2LONG(length)) < 0) {
rb_raise(rb_eArgError, "negative length %ld given", len);
}
io_setstrbuf(&str,len);
OBJ_TAINT(str);
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);
rb_str_locktmp(str);
n = rb_read_internal(fptr->fd, RSTRING_PTR(str), len);
rb_str_unlocktmp(str);
if (n < 0) {
if (!nonblock && rb_io_wait_readable(fptr->fd))
goto again;
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
rb_mod_sys_fail(rb_mWaitReadable, "read would block");
rb_sys_fail_path(fptr->pathv);
}
}
io_set_read_length(str, n);
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 <i>outbuf</i> argument is present,
* it must reference a String, which will receive the data.
* The <i>outbuf</i> will contain only the received data after the method call
* even if it is not empty at the beginning.
* It raises <code>EOFError</code> 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 later 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, 0);
if (NIL_P(ret))
rb_eof_error();
return ret;
}
/*
* call-seq:
* ios.read_nonblock(maxlen) -> string
* ios.read_nonblock(maxlen, outbuf) -> outbuf
*
* Reads at most <i>maxlen</i> bytes from <em>ios</em> using
* the read(2) system call after O_NONBLOCK is set for
* the underlying file descriptor.
*
* If the optional <i>outbuf</i> argument is present,
* it must reference a String, which will receive the data.
* The <i>outbuf</i> will contain only the received data after the method call
* even if it is not empty at the beginning.
*
* read_nonblock just calls the read(2) system call.
* It causes all errors the read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc.
* The caller should care such errors.
*
* If the exception is Errno::EWOULDBLOCK or Errno::AGAIN,
* it is extended by IO::WaitReadable.
* So IO::WaitReadable can be used to rescue the exceptions for retrying read_nonblock.
*
* read_nonblock causes EOFError on EOF.
*
* If the read byte buffer is not empty,
* read_nonblock reads from the buffer like readpartial.
* In this case, the read(2) system call is not called.
*
* When read_nonblock raises an exception kind of IO::WaitReadable,
* read_nonblock should not be called
* until io is readable for avoiding busy loop.
* This can be done as follows.
*
* # emulates blocking read (readpartial).
* begin
* result = io.read_nonblock(maxlen)
* rescue IO::WaitReadable
* IO.select([io])
* retry
* end
*
* Although IO#read_nonblock doesn't raise IO::WaitWritable.
* OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable.
* If IO and SSL should be used polymorphically,
* IO::WaitWritable should be rescued too.
* See the document of OpenSSL::Buffering#read_nonblock for sample code.
*
* Note that this method is identical to readpartial
* except the non-blocking flag is set.
*/
static VALUE
io_read_nonblock(int argc, VALUE *argv, VALUE io)
{
VALUE ret;
ret = io_getpartial(argc, argv, io, 1);
if (NIL_P(ret))
rb_eof_error();
return ret;
}
/*
* call-seq:
* ios.write_nonblock(string) -> integer
*
* Writes the given string to <em>ios</em> using
* the write(2) system call after O_NONBLOCK is set for
* the underlying file descriptor.
*
* It returns the number of bytes written.
*
* write_nonblock just calls the write(2) system call.
* It causes all errors the write(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc.
* The result may also be smaller than string.length (partial write).
* The caller should care such errors and partial write.
*
* If the exception is Errno::EWOULDBLOCK or Errno::AGAIN,
* it is extended by IO::WaitWritable.
* So IO::WaitWritable can be used to rescue the exceptions for retrying write_nonblock.
*
* # Creates a pipe.
* r, w = IO.pipe
*
* # write_nonblock writes only 65536 bytes and return 65536.
* # (The pipe size is 65536 bytes on this environment.)
* s = "a" * 100000
* p w.write_nonblock(s) #=> 65536
*
* # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN).
* p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN)
*
* If the write buffer is not empty, it is flushed at first.
*
* When write_nonblock raises an exception kind of IO::WaitWritable,
* write_nonblock should not be called
* until io is writable for avoiding busy loop.
* This can be done as follows.
*
* begin
* result = io.write_nonblock(string)
* rescue IO::WaitWritable, Errno::EINTR
* IO.select(nil, [io])
* retry
* end
*
* Note that this doesn't guarantee to write all data in string.
* The length written is reported as result and it should be checked later.
*
* On some platforms such as Windows, write_nonblock is not supported
* according to the kind of the IO object.
* In such cases, write_nonblock raises <code>Errno::EBADF</code>.
*
*/
static VALUE
rb_io_write_nonblock(VALUE io, VALUE str)
{
rb_io_t *fptr;
long n;
rb_secure(4);
if (!RB_TYPE_P(str, T_STRING))
str = rb_obj_as_string(str);
io = GetWriteIO(io);
GetOpenFile(io, fptr);
rb_io_check_writable(fptr);
if (io_fflush(fptr) < 0)
rb_sys_fail(0);
rb_io_set_nonblock(fptr);
n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str));
if (n == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
rb_mod_sys_fail(rb_mWaitWritable, "write would block");
rb_sys_fail_path(fptr->pathv);
}
return LONG2FIX(n);
}
/*
* call-seq:
* ios.read([length [, outbuf]]) -> string, outbuf, or nil
*
* Reads <i>length</i> bytes from the I/O stream.
*
* <i>length</i> must be a non-negative integer or <code>nil</code>.
*
* If <i>length</i> is a positive integer,
* it try to read <i>length</i> bytes without any conversion (binary mode).
* It returns <code>nil</code> or a string whose length is 1 to <i>length</i> bytes.
* <code>nil</code> means it met EOF at beginning.
* The 1 to <i>length</i>-1 bytes string means it met EOF after reading the result.
* The <i>length</i> bytes string means it doesn't meet EOF.
* The resulted string is always ASCII-8BIT encoding.
*
* If <i>length</i> is omitted or is <code>nil</code>,
* it reads until EOF and the encoding conversion is applied.
* It returns a string even if EOF is met at beginning.
*
* If <i>length</i> is zero, it returns <code>""</code>.
*
* If the optional <i>outbuf</i> argument is present, it must reference
* a String, which will receive the data.
* The <i>outbuf</i> will contain only the received data after the method call
* even if it is not empty at the beginning.
*
* At end of file, it returns <code>nil</code> or <code>""</code>
* depend on <i>length</i>.
* <code><i>ios</i>.read()</code> and
* <code><i>ios</i>.read(nil)</code> returns <code>""</code>.
* <code><i>ios</i>.read(<i>positive-integer</i>)</code> returns <code>nil</code>.
*
* f = File.new("testfile")
* f.read(16) #=> "This is line one"
*
* # reads whole file
* open("file") {|f|
* data = f.read # This returns a string even if the file is empty.
* ...
* }
*
* # iterate over fixed length records.
* open("fixed-record-file") {|f|
* while record = f.read(256)
* ...
* end
* }
*
* # iterate over variable length records.
* # record is prefixed by 32-bit length.
* open("variable-record-file") {|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
* }
*
* Note that this method behaves like fread() function in C.
* This means it retry to invoke read(2) system call to read data with the specified length (or until EOF).
* This behavior is preserved even if <i>ios</i> is non-blocking mode.
* (This method is non-blocking flag insensitive as other methods.)
* If you need the behavior like 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;
#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);
}
io_setstrbuf(&str,len);
GetOpenFile(io, fptr);
rb_io_check_byte_readable(fptr);
if (len == 0) 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);
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32)
if (previous_mode == O_TEXT) {
setmode(fptr->fd, O_TEXT);
}
#endif
if (n == 0) return Qnil;
OBJ_TAINT(str);
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;
if (fptr->cbuf.len) {
p = fptr->cbuf.ptr+fptr->cbuf.off;
searchlen = fptr->cbuf.len;
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, VALUE io)
{
VALUE str = Qnil;
int len = 0;
long pos = 0;
int cr = 0;
for (;;) {
int pending = READ_DATA_PENDING_COUNT(fptr);
if (pending > 0) {
const char *p = READ_DATA_PENDING_PTR(fptr);
const char *e;
e = memchr(p, '\n', pending);
if (e) {
pending = (int)(e - p + 1);
}
if (NIL_P(str)) {
str = rb_str_new(p, pending);
fptr->rbuf.off += pending;
fptr->rbuf.len -= pending;
}
else {
rb_str_resize(str, len + pending);
read_buffered_data(RSTRING_PTR(str)+len, pending, fptr);
}
len += pending;
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);
if (io_fillbuf(fptr) < 0) {
if (NIL_P(str)) return Qnil;
break;
}
}
str = io_enc_str(str, fptr);
ENC_CODERANGE_SET(str, cr);
fptr->lineno++;
if (io == ARGF.current_file) {
ARGF.lineno++;
ARGF.last_lineno = ARGF.lineno;
}
else {
ARGF.last_lineno = fptr->lineno;
}
return str;
}
static void
prepare_getline_args(int argc, VALUE *argv, VALUE *rsp, long *limit, VALUE io)
{
VALUE rs = rb_rs, lim = Qnil;
rb_io_t *fptr;
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) {
rb_scan_args(argc, argv, "2", &rs, &lim);
if (!NIL_P(rs))
StringValue(rs);
}
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");
}
else {
rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS",
rb_enc_name(enc_io),
rb_enc_name(enc_rs));
}
}
}
*rsp = rs;
*limit = NIL_P(lim) ? -1L : NUM2LONG(lim);
}
static VALUE
rb_io_getline_1(VALUE rs, long limit, VALUE io)
{
VALUE str = Qnil;
rb_io_t *fptr;
int nolimit = 0;
rb_encoding *enc;
GetOpenFile(io, fptr);
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;
}
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, io);
}
else {
int c, newline = -1;
const char *rsptr = 0;
long rslen = 0;
int rspara = 0;
int extra_limit = 16;
SET_BINARY_MODE(fptr);
enc = io_read_encoding(fptr);
if (!NIL_P(rs)) {
rslen = RSTRING_LEN(rs);
if (rslen == 0) {
rsptr = "\n\n";
rslen = 2;
rspara = 1;
swallow(fptr, '\n');
rs = 0;
if (!rb_enc_asciicompat(enc)) {
rs = rb_usascii_str_new(rsptr, rslen);
rs = rb_str_encode(rs, rb_enc_from_encoding(enc), 0, Qnil);
OBJ_FREEZE(rs);
rsptr = RSTRING_PTR(rs);
rslen = RSTRING_LEN(rs);
}
}
else {
rsptr = RSTRING_PTR(rs);
}
newline = (unsigned char)rsptr[rslen - 1];
}
/* MS - Optimisation */
while ((c = appendline(fptr, newline, &str, &limit)) != EOF) {
const char *s, *p, *pp, *e;
if (c == newline) {
if (RSTRING_LEN(str) < rslen) continue;
s = RSTRING_PTR(str);
e = s + RSTRING_LEN(str);
p = e - rslen;
pp = rb_enc_left_char_head(s, p, e, enc);
if (pp != p) continue;
if (!rspara) rscheck(rsptr, rslen, rs);
if (memcmp(p, rsptr, rslen) == 0) break;
}
if (limit == 0) {
s = RSTRING_PTR(str);
p = s + RSTRING_LEN(str);
pp = rb_enc_left_char_head(s, p-1, p, enc);
if (extra_limit &&
MBCLEN_NEEDMORE_P(rb_enc_precise_mbclen(pp, p, enc))) {
/* relax the limit while incomplete character.
* extra_limit limits the relax length */
limit = 1;
extra_limit--;
}
else {
nolimit = 1;
break;
}
}
}
if (rspara) {
if (c != EOF) {
swallow(fptr, '\n');
}
}
if (!NIL_P(str))
str = io_enc_str(str, fptr);
}
if (!NIL_P(str)) {
if (!nolimit) {
fptr->lineno++;
if (io == ARGF.current_file) {
ARGF.lineno++;
ARGF.last_lineno = ARGF.lineno;
}
else {
ARGF.last_lineno = fptr->lineno;
}
}
}
return str;
}
static VALUE
rb_io_getline(int argc, VALUE *argv, VALUE io)
{
VALUE rs;
long limit;
prepare_getline_args(argc, argv, &rs, &limit, io);
return rb_io_getline_1(rs, limit, io);
}
VALUE
rb_io_gets(VALUE io)
{
return rb_io_getline_1(rb_default_rs, -1, io);
}
/*
* call-seq:
* ios.gets(sep=$/) -> string or nil
* ios.gets(limit) -> string or nil
* ios.gets(sep, limit) -> string or nil
*
* Reads the next ``line'' from the I/O stream; lines are separated by
* <i>sep</i>. A separator of <code>nil</code> reads the entire
* contents, and a zero-length separator reads the input a paragraph at
* a time (two successive newlines in the input separate paragraphs).
* The stream must be opened for reading or an <code>IOError</code>
* will be raised. The line read in will be returned and also assigned
* to <code>$_</code>. Returns <code>nil</code> if called at end of
* file. If the first argument is an integer, or optional second
* argument is given, the returning string would not be longer than the
* given value in bytes.
*
* File.new("testfile").gets #=> "This is line one\n"
* $_ #=> "This is line one\n"
*/
static VALUE
rb_io_gets_m(int argc, VALUE *argv, VALUE io)
{
VALUE str;
str = rb_io_getline(argc, argv, io);
rb_lastline_set(str);
return str;
}
/*
* call-seq:
* ios.lineno -> integer
*
* Returns the current line number in <em>ios</em>. The stream must be
* opened for reading. <code>lineno</code> counts the number of times
* #gets is called rather than the number of newlines encountered. The two
* values will differ if #gets is called with a separator other than newline.
*
* Methods that use <code>$/</code> like #each, #lines and #readline will
* also increment <code>lineno</code>.
*
* See also the <code>$.</code> variable.
*
* f = File.new("testfile")
* f.lineno #=> 0
* f.gets #=> "This is line one\n"
* f.lineno #=> 1
* f.gets #=> "This is line two\n"
* f.lineno #=> 2
*/
static VALUE
rb_io_lineno(VALUE io)
{
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
return INT2NUM(fptr->lineno);
}
/*
* call-seq:
* ios.lineno = integer -> integer
*
* Manually sets the current line number to the given value.
* <code>$.</code> is updated only on the next read.
*
* f = File.new("testfile")
* f.gets #=> "This is line one\n"
* $. #=> 1
* f.lineno = 1000
* f.lineno #=> 1000
* $. #=> 1 # lineno of last read
* f.gets #=> "This is line two\n"
* $. #=> 1001 # lineno of last read
*/
static VALUE
rb_io_set_lineno(VALUE io, VALUE lineno)
{
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
fptr->lineno = NUM2INT(lineno);
return lineno;
}
/*
* call-seq:
* ios.readline(sep=$/) -> string
* ios.readline(limit) -> string
* ios.readline(sep, limit) -> string
*
* Reads a line as with <code>IO#gets</code>, but raises an
* <code>EOFError</code> on end of file.
*/
static VALUE
rb_io_readline(int argc, VALUE *argv, VALUE io)
{
VALUE line = rb_io_gets_m(argc, argv, io);
if (NIL_P(line)) {
rb_eof_error();
}
return line;
}
/*
* call-seq:
* ios.readlines(sep=$/) -> array
* ios.readlines(limit) -> array
* ios.readlines(sep, limit) -> array
*
* Reads all of the lines in <em>ios</em>, and returns them in
* <i>anArray</i>. Lines are separated by the optional <i>sep</i>. If
* <i>sep</i> is <code>nil</code>, the rest of the stream is returned
* as a single record. If the first argument is an integer, or
* optional second argument is given, the returning string would not be
* longer than the given value in bytes. The stream must be opened for
* reading or an <code>IOError</code> will be raised.
*
* f = File.new("testfile")
* f.readlines[0] #=> "This is line one\n"
*/
static VALUE
rb_io_readlines(int argc, VALUE *argv, VALUE io)
{
VALUE line, ary, rs;
long limit;
prepare_getline_args(argc, argv, &rs, &limit, io);
if (limit == 0)
rb_raise(rb_eArgError, "invalid limit: 0 for readlines");
ary = rb_ary_new();
while (!NIL_P(line = rb_io_getline_1(rs, limit, io))) {
rb_ary_push(ary, line);
}
return ary;
}
/*
* call-seq:
* ios.each(sep=$/) {|line| block } -> ios
* ios.each(limit) {|line| block } -> ios
* ios.each(sep,limit) {|line| block } -> ios
* ios.each(...) -> an_enumerator
*
* ios.each_line(sep=$/) {|line| block } -> ios
* ios.each_line(limit) {|line| block } -> ios
* ios.each_line(sep,limit) {|line| block } -> ios
* ios.each_line(...) -> an_enumerator
*
* ios.lines(sep=$/) {|line| block } -> ios
* ios.lines(limit) {|line| block } -> ios
* ios.lines(sep,limit) {|line| block } -> ios
* ios.lines(...) -> an_enumerator
*
* Executes the block for every line in <em>ios</em>, where lines are
* separated by <i>sep</i>. <em>ios</em> must be opened for
* reading or an <code>IOError</code> will be raised.
*
* If no block is given, an enumerator is returned instead.
*
* f = File.new("testfile")
* f.each {|line| puts "#{f.lineno}: #{line}" }
*
* <em>produces:</em>
*
* 1: This is line one
* 2: This is line two
* 3: This is line three
* 4: And so on...
*/
static VALUE
rb_io_each_line(int argc, VALUE *argv, VALUE io)
{
VALUE str, rs;
long limit;
RETURN_ENUMERATOR(io, argc, argv);
prepare_getline_args(argc, argv, &rs, &limit, io);
if (limit == 0)
rb_raise(rb_eArgError, "invalid limit: 0 for each_line");
while (!NIL_P(str = rb_io_getline_1(rs, limit, io))) {
rb_yield(str);
}
return io;
}
/*
* call-seq:
* ios.bytes {|byte| block } -> ios
* ios.bytes -> an_enumerator
*
* ios.each_byte {|byte| block } -> ios
* ios.each_byte -> an_enumerator
*
* Calls the given block once for each byte (0..255) in <em>ios</em>,
* passing the byte as an argument. The stream must be opened for
* reading or an <code>IOError</code> will be raised.
*
* If no block is given, an enumerator is returned instead.
*
* f = File.new("testfile")
* checksum = 0
* f.each_byte {|x| checksum ^= x } #=> #<File:testfile>
* checksum #=> 12
*/
static VALUE
rb_io_each_byte(VALUE io)
{
rb_io_t *fptr;
RETURN_ENUMERATOR(io, 0, 0);
GetOpenFile(io, fptr);
for (;;) {
while (fptr->rbuf.len > 0) {
char *p = fptr->rbuf.ptr + fptr->rbuf.off++;
fptr->rbuf.len--;
rb_yield(INT2FIX(*p & 0xff));
errno = 0;
}
rb_io_check_byte_readable(fptr);
READ_CHECK(fptr);
if (io_fillbuf(fptr) < 0) {
break;
}
}
return io;
}
static VALUE
io_getc(rb_io_t *fptr, rb_encoding *enc)
{
int r, n, cr = 0;
VALUE str;
if (NEED_READCONV(fptr)) {
VALUE str = Qnil;
rb_encoding *read_enc = io_read_encoding(fptr);
SET_BINARY_MODE(fptr);
make_readconv(fptr, 0);
while (1) {
if (fptr->cbuf.len) {
r = rb_enc_precise_mbclen(fptr->cbuf.ptr+fptr->cbuf.off,
fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
read_enc);
if (!MBCLEN_NEEDMORE_P(r))
break;
if (fptr->cbuf.len == fptr->cbuf.capa) {
rb_raise(rb_eIOError, "too long character");
}
}
if (more_char(fptr) == MORE_CHAR_FINISHED) {
if (fptr->cbuf.len == 0) {
clear_readconv(fptr);
return Qnil;
}
/* return an unit of an incomplete character just before EOF */
str = rb_enc_str_new(fptr->cbuf.ptr+fptr->cbuf.off, 1, read_enc);
fptr->cbuf.off += 1;
fptr->cbuf.len -= 1;
if (fptr->cbuf.len == 0) clear_readconv(fptr);
ENC_CODERANGE_SET(str, ENC_CODERANGE_BROKEN);
return str;
}
}
if (MBCLEN_INVALID_P(r)) {
r = rb_enc_mbclen(fptr->cbuf.ptr+fptr->cbuf.off,
fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
read_enc);
io_shift_cbuf(fptr, r, &str);
cr = ENC_CODERANGE_BROKEN;
}
else {
io_shift_cbuf(fptr, MBCLEN_CHARFOUND_LEN(r), &str);
cr = ISASCII(r) ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID;
}
str = io_enc_str(str, fptr);
ENC_CODERANGE_SET(str, cr);
return str;
}
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr);
if (io_fillbuf(fptr) < 0) {
return Qnil;
}
if (rb_enc_asciicompat(enc) && ISASCII(fptr->rbuf.ptr[fptr->rbuf.off])) {
str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, 1);
fptr->rbuf.off += 1;
fptr->rbuf.len -= 1;
cr = ENC_CODERANGE_7BIT;
}
else {
r = rb_enc_precise_mbclen(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc);
if (MBCLEN_CHARFOUND_P(r) &&
(n = MBCLEN_CHARFOUND_LEN(r)) <= fptr->rbuf.len) {
str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, n);
fptr->rbuf.off += n;
fptr->rbuf.len -= n;
cr = ENC_CODERANGE_VALID;
}
else if (MBCLEN_NEEDMORE_P(r)) {
str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.len);
fptr->rbuf.len = 0;
getc_needmore:
if (io_fillbuf(fptr) != -1) {
rb_str_cat(str, fptr->rbuf.ptr+fptr->rbuf.off, 1);
fptr->rbuf.off++;
fptr->rbuf.len--;
r = rb_enc_precise_mbclen(RSTRING_PTR(str), RSTRING_PTR(str)+RSTRING_LEN(str), enc);
if (MBCLEN_NEEDMORE_P(r)) {
goto getc_needmore;
}
else if (MBCLEN_CHARFOUND_P(r)) {
cr = ENC_CODERANGE_VALID;
}
}
}
else {
str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, 1);
fptr->rbuf.off++;
fptr->rbuf.len--;
}
}
if (!cr) cr = ENC_CODERANGE_BROKEN;
str = io_enc_str(str, fptr);
ENC_CODERANGE_SET(str, cr);
return str;
}
/*
* call-seq:
* ios.chars {|c| block } -> ios
* ios.chars -> an_enumerator
*
* ios.each_char {|c| block } -> ios
* ios.each_char -> an_enumerator
*
* Calls the given block once for each character in <em>ios</em>,
* passing the character as an argument. The stream must be opened for
* reading or an <code>IOError</code> will be raised.
*
* If no block is given, an enumerator is returned instead.
*
* f = File.new("testfile")
* f.each_char {|c| print c, ' ' } #=> #<File:testfile>
*/
static VALUE
rb_io_each_char(VALUE io)
{
rb_io_t *fptr;
rb_encoding *enc;
VALUE c;
RETURN_ENUMERATOR(io, 0, 0);
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
enc = io_input_encoding(fptr);
READ_CHECK(fptr);
while (!NIL_P(c = io_getc(fptr, enc))) {
rb_yield(c);
}
return io;
}
/*
* call-seq:
* ios.each_codepoint {|c| block } -> ios
* ios.codepoints {|c| block } -> ios
* ios.each_codepoint -> an_enumerator
* ios.codepoints -> an_enumerator
*
* Passes the <code>Integer</code> ordinal of each character in <i>ios</i>,
* passing the codepoint as an argument. The stream must be opened for
* reading or an <code>IOError</code> will be raised.
*
* If no block is given, an enumerator is returned instead.
*
*/
static VALUE
rb_io_each_codepoint(VALUE io)
{
rb_io_t *fptr;
rb_encoding *enc;
unsigned int c;
int r, n;
RETURN_ENUMERATOR(io, 0, 0);
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
READ_CHECK(fptr);
if (NEED_READCONV(fptr)) {
SET_BINARY_MODE(fptr);
for (;;) {
make_readconv(fptr, 0);
for (;;) {
if (fptr->cbuf.len) {
if (fptr->encs.enc)
r = rb_enc_precise_mbclen(fptr->cbuf.ptr+fptr->cbuf.off,
fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
fptr->encs.enc);
else
r = ONIGENC_CONSTRUCT_MBCLEN_CHARFOUND(1);
if (!MBCLEN_NEEDMORE_P(r))
break;
if (fptr->cbuf.len == fptr->cbuf.capa) {
rb_raise(rb_eIOError, "too long character");
}
}
if (more_char(fptr) == MORE_CHAR_FINISHED) {
clear_readconv(fptr);
/* ignore an incomplete character before EOF */
return io;
}
}
if (MBCLEN_INVALID_P(r)) {
rb_raise(rb_eArgError, "invalid byte sequence in %s",
rb_enc_name(fptr->encs.enc));
}
n = MBCLEN_CHARFOUND_LEN(r);
if (fptr->encs.enc) {
c = rb_enc_codepoint(fptr->cbuf.ptr+fptr->cbuf.off,
fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
fptr->encs.enc);
}
else {
c = (unsigned char)fptr->cbuf.ptr[fptr->cbuf.off];
}
fptr->cbuf.off += n;
fptr->cbuf.len -= n;
rb_yield(UINT2NUM(c));
}
}
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr);
enc = io_input_encoding(fptr);
for (;;) {
if (io_fillbuf(fptr) < 0) {
return io;
}
r = rb_enc_precise_mbclen(fptr->rbuf.ptr+fptr->rbuf.off,
fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc);
if (MBCLEN_CHARFOUND_P(r) &&
(n = MBCLEN_CHARFOUND_LEN(r)) <= fptr->rbuf.len) {
c = rb_enc_codepoint(fptr->rbuf.ptr+fptr->rbuf.off,
fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc);
fptr->rbuf.off += n;
fptr->rbuf.len -= n;
rb_yield(UINT2NUM(c));
}
else if (MBCLEN_INVALID_P(r)) {
rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(enc));
}
else {
continue;
}
}
return io;
}
/*
* call-seq:
* ios.getc -> string or nil
*
* Reads a one-character string from <em>ios</em>. Returns
* <code>nil</code> if called at end of file.
*
* f = File.new("testfile")
* f.getc #=> "h"
* f.getc #=> "e"
*/
static VALUE
rb_io_getc(VALUE io)
{
rb_io_t *fptr;
rb_encoding *enc;
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
enc = io_input_encoding(fptr);
READ_CHECK(fptr);
return io_getc(fptr, enc);
}
/*
* call-seq:
* ios.readchar -> string
*
* Reads a one-character string from <em>ios</em>. Raises an
* <code>EOFError</code> on end of file.
*
* f = File.new("testfile")
* f.readchar #=> "h"
* f.readchar #=> "e"
*/
static VALUE
rb_io_readchar(VALUE io)
{
VALUE c = rb_io_getc(io);
if (NIL_P(c)) {
rb_eof_error();
}
return c;
}
/*
* call-seq:
* ios.getbyte -> fixnum or nil
*
* Gets the next 8-bit byte (0..255) from <em>ios</em>. Returns
* <code>nil</code> if called at end of file.
*
* f = File.new("testfile")
* f.getbyte #=> 84
* f.getbyte #=> 104
*/
VALUE
rb_io_getbyte(VALUE io)
{
rb_io_t *fptr;
int c;
GetOpenFile(io, fptr);
rb_io_check_byte_readable(fptr);
READ_CHECK(fptr);
if (fptr->fd == 0 && (fptr->mode & FMODE_TTY) && RB_TYPE_P(rb_stdout, T_FILE)) {
rb_io_t *ofp;
GetOpenFile(rb_stdout, ofp);
if (ofp->mode & FMODE_TTY) {
rb_io_flush(rb_stdout);
}
}
if (io_fillbuf(fptr) < 0) {
return Qnil;
}
fptr->rbuf.off++;
fptr->rbuf.len--;
c = (unsigned char)fptr->rbuf.ptr[fptr->rbuf.off-1];
return INT2FIX(c & 0xff);
}
/*
* call-seq:
* ios.readbyte -> fixnum
*
* Reads a byte as with <code>IO#getbyte</code>, but raises an
* <code>EOFError</code> on end of file.
*/
static VALUE
rb_io_readbyte(VALUE io)
{
VALUE c = rb_io_getbyte(io);
if (NIL_P(c)) {
rb_eof_error();
}
return c;
}
/*
* call-seq:
* ios.ungetbyte(string) -> nil
* ios.ungetbyte(integer) -> nil
*
* Pushes back bytes (passed as a parameter) onto <em>ios</em>,
* such that a subsequent buffered read will return it. Only one byte
* may be pushed back before a subsequent read operation (that is,
* you will be able to read only the last of several bytes that have been pushed
* back). Has no effect with unbuffered reads (such as <code>IO#sysread</code>).
*
* f = File.new("testfile") #=> #<File:testfile>
* b = f.getbyte #=> 0x38
* f.ungetbyte(b) #=> nil
* f.getbyte #=> 0x38
*/
VALUE
rb_io_ungetbyte(VALUE io, VALUE b)
{
rb_io_t *fptr;
GetOpenFile(io, fptr);
rb_io_check_byte_readable(fptr);
if (NIL_P(b)) return Qnil;
if (FIXNUM_P(b)) {
char cc = FIX2INT(b);
b = rb_str_new(&cc, 1);
}
else {
SafeStringValue(b);
}
io_ungetbyte(b, fptr);
return Qnil;
}
/*
* call-seq:
* ios.ungetc(string) -> nil
*
* Pushes back one character (passed as a parameter) onto <em>ios</em>,
* such that a subsequent buffered character read will return it. Only one character
* may be pushed back before a subsequent read operation (that is,
* you will be able to read only the last of several characters that have been pushed
* back). Has no effect with unbuffered reads (such as <code>IO#sysread</code>).
*
* f = File.new("testfile") #=> #<File:testfile>
* c = f.getc #=> "8"
* f.ungetc(c) #=> nil
* f.getc #=> "8"
*/
VALUE
rb_io_ungetc(VALUE io, VALUE c)
{
rb_io_t *fptr;
long len;
GetOpenFile(io, fptr);
rb_io_check_char_readable(fptr);
if (NIL_P(c)) return Qnil;
if (FIXNUM_P(c)) {
c = rb_enc_uint_chr(FIX2UINT(c), io_read_encoding(fptr));
}
else if (RB_TYPE_P(c, T_BIGNUM)) {
c = rb_enc_uint_chr(NUM2UINT(c), io_read_encoding(fptr));
}
else {
SafeStringValue(c);
}
if (NEED_READCONV(fptr)) {
SET_BINARY_MODE(fptr);
len = RSTRING_LEN(c);
#if SIZEOF_LONG > SIZEOF_INT
if (len > INT_MAX)
rb_raise(rb_eIOError, "ungetc failed");
#endif
make_readconv(fptr, (int)len);
if (fptr->cbuf.capa - fptr->cbuf.len < len)
rb_raise(rb_eIOError, "ungetc failed");
if (fptr->cbuf.off < len) {
MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.capa-fptr->cbuf.len,
fptr->cbuf.ptr+fptr->cbuf.off,
char, fptr->cbuf.len);
fptr->cbuf.off = fptr->cbuf.capa-fptr->cbuf.len;
}
fptr->cbuf.off -= (int)len;
fptr->cbuf.len += (int)len;
MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.off, RSTRING_PTR(c), char, len);
}
else {
NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr);
io_ungetbyte(c, fptr);
}
return Qnil;
}
/*
* call-seq:
* ios.isatty -> true or false
* ios.tty? -> true or false
*
* Returns <code>true</code> if <em>ios</em> is associated with a
* terminal device (tty), <code>false</code> otherwise.
*
* File.new("testfile").isatty #=> false
* File.new("/dev/tty").isatty #=> true
*/
static VALUE
rb_io_isatty(VALUE io)
{
rb_io_t *fptr;
GetOpenFile(io, fptr);
if (isatty(fptr->fd) == 0)
return Qfalse;
return Qtrue;
}
#if defined(HAVE_FCNTL) && defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
/*
* call-seq:
* ios.close_on_exec? -> true or false
*
* Returns <code>true</code> if <em>ios</em> will be closed on exec.
*
* f = open("/dev/null")
* f.close_on_exec? #=> false
* f.close_on_exec = true
* f.close_on_exec? #=> true
* f.close_on_exec = false
* f.close_on_exec? #=> false
*/
static VALUE
rb_io_close_on_exec_p(VALUE io)
{
rb_io_t *fptr;
VALUE write_io;
int fd, ret;
write_io = GetWriteIO(io);
if (io != write_io) {
GetOpenFile(write_io, fptr);
if (fptr && 0 <= (fd = fptr->fd)) {
if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
if (!(ret & FD_CLOEXEC)) return Qfalse;
}
}
GetOpenFile(io, fptr);
if (fptr && 0 <= (fd = fptr->fd)) {
if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
if (!(ret & FD_CLOEXEC)) return Qfalse;
}
return Qtrue;
}
#else
#define rb_io_close_on_exec_p rb_f_notimplement
#endif
#if defined(HAVE_FCNTL) && defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC)
/*
* call-seq:
* ios.close_on_exec = bool -> true or false
*
* Sets a close-on-exec flag.
*
* f = open("/dev/null")
* f.close_on_exec = true
* system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory
* f.closed? #=> false
*/
static VALUE
rb_io_set_close_on_exec(VALUE io, VALUE arg)
{
int flag = RTEST(arg) ? FD_CLOEXEC : 0;
rb_io_t *fptr;
VALUE write_io;
int fd, ret;
write_io = GetWriteIO(io);
if (io != write_io) {
GetOpenFile(write_io, fptr);
if (fptr && 0 <= (fd = fptr->fd)) {
if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
if ((ret & FD_CLOEXEC) != flag) {
ret = (ret & ~FD_CLOEXEC) | flag;
ret = fcntl(fd, F_SETFD, ret);
if (ret == -1) rb_sys_fail_path(fptr->pathv);
}
}
}
GetOpenFile(io, fptr);
if (fptr && 0 <= (fd = fptr->fd)) {
if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
if ((ret & FD_CLOEXEC) != flag) {
ret = (ret & ~FD_CLOEXEC) | flag;
ret = fcntl(fd, F_SETFD, ret);
if (ret == -1) rb_sys_fail_path(fptr->pathv);
}
}
return Qnil;
}
#else
#define rb_io_set_close_on_exec rb_f_notimplement
#endif
#define FMODE_PREP (1<<16)
#define IS_PREP_STDIO(f) ((f)->mode & FMODE_PREP)
#define PREP_STDIO_NAME(f) (RSTRING_PTR((f)->pathv))
static VALUE
finish_writeconv(rb_io_t *fptr, int noalloc)
{
unsigned char *ds, *dp, *de;
rb_econv_result_t res;
if (!fptr->wbuf.ptr) {
unsigned char buf[1024];
long r;
res = econv_destination_buffer_full;
while (res == econv_destination_buffer_full) {
ds = dp = buf;
de = buf + sizeof(buf);
res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0);
while (dp-ds) {
retry:
r = rb_write_internal(fptr->fd, ds, dp-ds);
if (r == dp-ds)
break;
if (0 <= r) {
ds += r;
}
if (rb_io_wait_writable(fptr->fd)) {
if (fptr->fd < 0)
return noalloc ? Qtrue : rb_exc_new3(rb_eIOError, rb_str_new_cstr("closed stream"));
goto retry;
}
return noalloc ? Qtrue : INT2NUM(errno);
}
if (res == econv_invalid_byte_sequence ||
res == econv_incomplete_input ||
res == econv_undefined_conversion) {
return noalloc ? Qtrue : rb_econv_make_exception(fptr->writeconv);
}
}
return Qnil;
}
res = econv_destination_buffer_full;
while (res == econv_destination_buffer_full) {
if (fptr->wbuf.len == fptr->wbuf.capa) {
if (io_fflush(fptr) < 0)
return noalloc ? Qtrue : INT2NUM(errno);
}
ds = dp = (unsigned char *)fptr->wbuf.ptr + fptr->wbuf.off + fptr->wbuf.len;
de = (unsigned char *)fptr->wbuf.ptr + fptr->wbuf.capa;
res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0);
fptr->wbuf.len += (int)(dp - ds);
if (res == econv_invalid_byte_sequence ||
res == econv_incomplete_input ||
res == econv_undefined_conversion) {
return noalloc ? Qtrue : rb_econv_make_exception(fptr->writeconv);
}
}
return Qnil;
}
struct finish_writeconv_arg {
rb_io_t *fptr;
int noalloc;
};
static VALUE
finish_writeconv_sync(VALUE arg)
{
struct finish_writeconv_arg *p = (struct finish_writeconv_arg *)arg;
return finish_writeconv(p->fptr, p->noalloc);
}
static void*
nogvl_close(void *ptr)
{
int *fd = ptr;
return (void*)(intptr_t)close(*fd);
}
static int
maygvl_close(int fd, int keepgvl)
{
if (keepgvl)
return close(fd);
/*
* close() may block for certain file types (NFS, SO_LINGER sockets,
* inotify), so let other threads run.
*/
return (int)(intptr_t)rb_thread_call_without_gvl(nogvl_close, &fd, RUBY_UBF_IO, 0);
}
static void*
nogvl_fclose(void *ptr)
{
FILE *file = ptr;
return (void*)(intptr_t)fclose(file);
}
static int
maygvl_fclose(FILE *file, int keepgvl)
{
if (keepgvl)
return fclose(file);
return (int)(intptr_t)rb_thread_call_without_gvl(nogvl_fclose, file, RUBY_UBF_IO, 0);
}
static void
fptr_finalize(rb_io_t *fptr, int noraise)
{
VALUE err = Qnil;
int fd = fptr->fd;
FILE *stdio_file = fptr->stdio_file;
if (fptr->writeconv) {
if (fptr->write_lock && !noraise) {
struct finish_writeconv_arg arg;
arg.fptr = fptr;
arg.noalloc = noraise;
err = rb_mutex_synchronize(fptr->write_lock, finish_writeconv_sync, (VALUE)&arg);
}
else {
err = finish_writeconv(fptr, noraise);
}
}
if (fptr->wbuf.len) {
if (noraise) {
if ((int)io_flush_buffer_sync(fptr) < 0 && NIL_P(err))
err = Qtrue;
}
else {
if (io_fflush(fptr) < 0 && NIL_P(err))
err = INT2NUM(errno);
}
}
fptr->fd = -1;
fptr->stdio_file = 0;
fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE);
if (IS_PREP_STDIO(fptr) || fd <= 2) {
/* need to keep FILE objects of stdin, stdout and stderr */
}
else if (stdio_file) {
/* stdio_file is deallocated anyway
* even if fclose failed. */
if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(err))
err = noraise ? Qtrue : INT2NUM(errno);
}
else if (0 <= fd) {
/* fptr->fd may be closed even if close fails.
* POSIX doesn't specify it.
* We assumes it is closed. */
if ((maygvl_close(fd, noraise) < 0) && NIL_P(err))
err = noraise ? Qtrue : INT2NUM(errno);
}
if (!NIL_P(err) && !noraise) {
switch(TYPE(err)) {
case T_FIXNUM:
case T_BIGNUM:
errno = NUM2INT(err);
rb_sys_fail_path(fptr->pathv);
default:
rb_exc_raise(err);
}
}
}
static void
rb_io_fptr_cleanup(rb_io_t *fptr, int noraise)
{
if (fptr->finalize) {
(*fptr->finalize)(fptr, noraise);
}
else {
fptr_finalize(fptr, noraise);
}
}
static void
clear_readconv(rb_io_t *fptr)
{
if (fptr->readconv) {
rb_econv_close(fptr->readconv);
fptr->readconv = NULL;
}
if (fptr->cbuf.ptr) {
free(fptr->cbuf.ptr);
fptr->cbuf.ptr = NULL;
}
}
static void
clear_writeconv(rb_io_t *fptr)
{
if (fptr->writeconv) {
rb_econv_close(fptr->writeconv);
fptr->writeconv = NULL;
}
fptr->writeconv_initialized = 0;
}
static void
clear_codeconv(rb_io_t *fptr)
{
clear_readconv(fptr);
clear_writeconv(fptr);
}
int
rb_io_fptr_finalize(rb_io_t *fptr)
{
if (!fptr) return 0;
fptr->pathv = Qnil;
if (0 <= fptr->fd)
rb_io_fptr_cleanup(fptr, TRUE);
fptr->write_lock = 0;
if (fptr->rbuf.ptr) {
free(fptr->rbuf.ptr);
fptr->rbuf.ptr = 0;
}
if (fptr->wbuf.ptr) {
free(fptr->wbuf.ptr);
fptr->wbuf.ptr = 0;
}
clear_codeconv(fptr);
free(fptr);
return 1;
}
size_t rb_econv_memsize(rb_econv_t *);
RUBY_FUNC_EXPORTED size_t
rb_io_memsize(const rb_io_t *fptr)
{
size_t size = sizeof(rb_io_t);
size += fptr->rbuf.capa;
size += fptr->wbuf.capa;
size += fptr->cbuf.capa;
if (fptr->readconv) size += rb_econv_memsize(fptr->readconv);
if (fptr->writeconv) size += rb_econv_memsize(fptr->writeconv);
return size;
}
VALUE
rb