Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: trunk
Fetching contributors…

Cannot retrieve contributors at this time

12359 lines (11174 sloc) 321.339 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 "id.h"
#include <ctype.h>
#include <errno.h>
#include "ruby_atomic.h"
#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
# 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
#if SIZEOF_OFF_T > SIZEOF_LONG && defined(HAVE_LONG_LONG)
# define PRI_OFF_T_PREFIX "ll"
#elif SIZEOF_OFF_T == SIZEOF_LONG
# define PRI_OFF_T_PREFIX "l"
#else
# define PRI_OFF_T_PREFIX ""
#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
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.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
#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
#endif
VALUE rb_cIO;
VALUE rb_eEOFError;
VALUE rb_eIOError;
VALUE rb_mWaitReadable;
VALUE rb_mWaitWritable;
extern VALUE rb_eEAGAIN;
extern VALUE rb_eEWOULDBLOCK;
extern VALUE rb_eEINPROGRESS;
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;
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, sym_exception;
static VALUE sym_SET, sym_CUR, sym_END;
#ifdef SEEK_DATA
static VALUE sym_DATA;
#endif
#ifdef SEEK_HOLE
static VALUE sym_HOLE;
#endif
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 rb_atomic_t max_file_descriptor = NOFILE;
void
rb_update_max_fd(int fd)
{
struct stat buf;
rb_atomic_t afd = (rb_atomic_t)fd;
if (afd <= max_file_descriptor)
return;
if (fstat(fd, &buf) != 0 && errno == EBADF) {
rb_bug("rb_update_max_fd: invalid fd (%d) given.", fd);
}
while (max_file_descriptor < afd) {
ATOMIC_CAS(max_file_descriptor, max_file_descriptor, 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 == -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);
rb_update_max_fd(fd);
}
/* this is only called once */
static int
rb_fix_detect_o_cloexec(int fd)
{
#ifdef O_CLOEXEC
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 */
#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;
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 == -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) && 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 != -1 && 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 == -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)
#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 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)
{
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
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_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_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
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;
};
#ifdef HAVE_WRITEV
struct io_internal_writev_struct {
int fd;
const struct iovec *iov;
int iovcnt;
};
#endif
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 void*
internal_write_func2(void *ptr)
{
struct io_internal_write_struct *iis = ptr;
return (void*)(intptr_t)write(iis->fd, iis->buf, iis->capa);
}
#ifdef HAVE_WRITEV
static VALUE
internal_writev_func(void *ptr)
{
struct io_internal_writev_struct *iis = ptr;
return writev(iis->fd, iis->iov, iis->iovcnt);
}
#endif
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 ssize_t
rb_write_internal2(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_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;
iis.fd = fd;
iis.iov = iov;
iis.iovcnt = iovcnt;
return (ssize_t)rb_thread_io_blocking_region(internal_writev_func, &iis, fd);
}
#endif
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 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;
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_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)
{
if (f < 0) {
rb_raise(rb_eIOError, "closed stream");
}
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;
}
}
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 (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 {
long l = io_writable_length(fptr, p->length);
r = rb_write_internal(fptr->fd, p->ptr, l);
}
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);
l = io_writable_length(p->fptr, p->length);
return rb_write_internal(p->fptr->fd, p->ptr, l);
}
#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;
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))
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;
#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);
else
str = rb_str_new_frozen(str);
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;
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 == -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_funcallv(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
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(0);
}
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
* <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);
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;
{
VALUE path = rb_sprintf("fd:%d ", fptr->fd);
if (!NIL_P(fptr->pathv)) {
rb_str_append(path, fptr->pathv);
}
rb_sys_fail_path(path);
}
}
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);
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;
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 = RFILE(io)->fptr;
int fd;
rb_io_check_closed(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(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);
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)
{
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;
}
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);
}
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) {
rb_str_modify(s);
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)) {
int first = !NIL_P(str);
SET_BINARY_MODE(fptr);
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);
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);
}
}
}
void
rb_readwrite_sys_fail(int writable, const char *mesg);
struct read_internal_arg {
int fd;
char *str_ptr;
long len;
};
static VALUE
read_internal_call(VALUE arg)
{
struct read_internal_arg *p = (struct read_internal_arg *)arg;
p->len = rb_read_internal(p->fd, p->str_ptr, p->len);
return Qundef;
}
static VALUE
io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock, int no_exception)
{
rb_io_t *fptr;
VALUE length, str;
long n, len;
struct read_internal_arg arg;
rb_scan_args(argc, argv, "11:", &length, &str, NULL);
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);
arg.fd = fptr->fd;
arg.str_ptr = RSTRING_PTR(str);
arg.len = len;
rb_str_locktmp_ensure(str, read_internal_call, (VALUE)&arg);
n = arg.len;
if (n < 0) {
if (!nonblock && rb_io_wait_readable(fptr->fd))
goto again;
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
if (no_exception)
return ID2SYM(rb_intern("wait_readable"));
else
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "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, 0);
if (NIL_P(ret))
rb_eof_error();
return ret;
}
static VALUE
get_kwargs_exception(VALUE opts)
{
static ID ids[1];
VALUE except;
if (!ids[0])
ids[0] = sym_exception;
rb_get_kwargs(opts, ids, 0, 1, &except);
return except;
}
/*
* 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;
VALUE opts = Qnil;
int no_exception = 0;
rb_scan_args(argc, argv, "11:", NULL, NULL, &opts);
if (!NIL_P(opts) && Qfalse == get_kwargs_exception(opts))
no_exception = 1;
ret = io_getpartial(argc, argv, io, 1, no_exception);
if (NIL_P(ret)) {
if (no_exception)
return Qnil;
else
rb_eof_error();
}
return ret;
}
static VALUE
io_write_nonblock(VALUE io, VALUE str, int no_exception)
{
rb_io_t *fptr;
long n;
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) {
if (no_exception) {
return ID2SYM(rb_intern("wait_writable"));
}
else {
rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "write would block");
}
}
rb_sys_fail_path(fptr->pathv);
}
return LONG2FIX(n);
}
/*
* call-seq:
* ios.write_nonblock(string) -> integer
* ios.write_nonblock(string [, options]) -> 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>.
*
* By specifying `exception: false`, the options hash allows you to indicate
* that write_nonblock should not raise an IO::WaitWritable exception, but
* return the symbol :wait_writable instead.
*
*/
static VALUE
rb_io_write_nonblock(int argc, VALUE *argv, VALUE io)
{
VALUE str;
VALUE opts = Qnil;
int no_exceptions = 0;
rb_scan_args(argc, argv, "10:", &str, &opts);
if (!NIL_P(opts) && Qfalse == get_kwargs_exception(opts))
no_exceptions = 1;
return io_write_nonblock(io, str, no_exceptions);
}
/*
* 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) {
io_set_read_length(str, 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 = 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, VALUE io)
{
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;
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);
} while (io_fillbuf(fptr) >= 0);
if (NIL_P(str)) return Qnil;
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 = RSTRING_END(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 = RSTRING_END(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 && c != EOF)
swallow(fptr, '\n');
if (!NIL_P(str))
str = io_enc_str(str, fptr);
}
if (!NIL_P(str) && !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
*
* 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;
}
/*
* This is a deprecated alias for <code>each_line</code>.
*/
static VALUE
rb_io_lines(int argc, VALUE *argv, VALUE io)
{
rb_warn("IO#lines is deprecated; use #each_line instead");
if (!rb_block_given_p())
return rb_enumeratorize(io, ID2SYM(rb_intern("each_line")), argc, argv);
return rb_io_each_line(argc, argv, io);
}
/*
* call-seq:
* 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);
do {
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);
} while (io_fillbuf(fptr) >= 0);
return io;
}
/*
* This is a deprecated alias for <code>each_byte</code>.
*/
static VALUE
rb_io_bytes(VALUE io)
{
rb_warn("IO#bytes is deprecated; use #each_byte instead");
if (!rb_block_given_p())
return rb_enumeratorize(io, ID2SYM(rb_intern("each_byte")), 0, 0);
return rb_io_each_byte(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 = ENC_CODERANGE_VALID;
if (MBCLEN_CHARFOUND_LEN(r) == 1 && rb_enc_asciicompat(read_enc) &&
ISASCII(RSTRING_PTR(str)[0])) {
cr = ENC_CODERANGE_7BIT;
}
}
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.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;
}
/*
* This is a deprecated alias for <code>each_char</code>.
*/
static VALUE
rb_io_chars(VALUE io)
{
rb_warn("IO#chars is deprecated; use #each_char instead");
if (!rb_block_given_p())
return rb_enumeratorize(io, ID2SYM(rb_intern("each_char")), 0, 0);
return rb_io_each_char(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);
while (io_fillbuf(fptr) >= 0) {
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;
}
/*
* This is a deprecated alias for <code>each_codepoint</code>.
*/
static VALUE
rb_io_codepoints(VALUE io)
{
rb_warn("IO#codepoints is deprecated; use #each_codepoint instead");
if (!rb_block_given_p())
return rb_enumeratorize(io, ID2SYM(rb_intern("each_codepoint")), 0, 0);
return rb_io_each_codepoint(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
*
* Ruby sets close-on-exec flags of all file descriptors by default
* since Ruby 2.0.0.
* So you don't need to set by yourself.
* Also, unsetting a close-on-exec flag can cause file descriptor leak
* if another thread use fork() and exec() (via system() method for example).
* If you really needs file descriptor inheritance to child process,
* use spawn()'s argument such as fd=>fd.
*/
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:
if (fptr->write_lock && rb_mutex_owned_p(fptr->write_lock))
r = rb_write_internal2(fptr->fd, ds, dp-ds);
else
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) {