/********************************************************************** 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 "dln.h" #include "internal.h" #include #include #define free(x) xfree(x) #if defined(DOSISH) || defined(__CYGWIN__) #include #endif #include #if defined HAVE_NET_SOCKET_H # include #elif defined HAVE_SYS_SOCKET_H # include #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 #if defined(HAVE_SYS_IOCTL_H) && !defined(_WIN32) #include #endif #if defined(HAVE_FCNTL_H) || defined(_WIN32) #include #elif defined(HAVE_SYS_FCNTL_H) #include #endif #if !HAVE_OFF_T && !defined(off_t) # define off_t long #endif #include /* EMX has sys/param.h, but.. */ #if defined(HAVE_SYS_PARAM_H) && !(defined(__EMX__) || defined(__HIUX_MPP__)) # include #endif #if !defined NOFILE # define NOFILE 64 #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYSCALL_H #include #elif defined HAVE_SYS_SYSCALL_H #include #endif #if defined(__BEOS__) || defined(__HAIKU__) # ifndef NOFILE # define NOFILE (OPEN_MAX) # endif #endif #include "ruby/util.h" #ifndef O_ACCMODE #define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) #endif #if SIZEOF_OFF_T > SIZEOF_LONG && !defined(HAVE_LONG_LONG) # error off_t is bigger than long, but you have no long long... #endif #ifndef PIPE_BUF # ifdef _POSIX_PIPE_BUF # define PIPE_BUF _POSIX_PIPE_BUF # else # define PIPE_BUF 512 /* is this ok? */ # endif #endif #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) #define IO_RBUF_CAPA_MIN 8192 #define IO_CBUF_CAPA_MIN (128*1024) #define IO_RBUF_CAPA_FOR(fptr) (NEED_READCONV(fptr) ? IO_CBUF_CAPA_MIN : IO_RBUF_CAPA_MIN) #define IO_WBUF_CAPA_MIN 8192 /* define system APIs */ #ifdef _WIN32 #undef open #define open rb_w32_uopen #endif VALUE rb_cIO; VALUE rb_eEOFError; VALUE rb_eIOError; VALUE rb_mWaitReadable; VALUE rb_mWaitWritable; VALUE rb_stdin, rb_stdout, rb_stderr; VALUE rb_deferr; /* rescue VIM plugin */ static VALUE orig_stdout, orig_stderr; VALUE rb_output_fs; VALUE rb_rs; VALUE rb_output_rs; VALUE rb_default_rs; static VALUE argf; static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; static VALUE sym_mode, sym_perm, sym_extenc, sym_intenc, sym_encoding, sym_open_args; static VALUE sym_textmode, sym_binmode, sym_autoclose; struct argf { VALUE filename, current_file; long last_lineno; /* $. */ long lineno; VALUE argv; char *inplace; struct rb_io_enc_t encs; int8_t init_p, next_p, binmode; }; static int max_file_descriptor = NOFILE; void rb_update_max_fd(int fd) { struct stat buf; if (fstat(fd, &buf) != 0 && errno == EBADF) { rb_bug("rb_update_max_fd: invalid fd (%d) given.", fd); } if (max_file_descriptor < fd) max_file_descriptor = fd; } void rb_maygvl_fd_fix_cloexec(int fd) { /* MinGW don't have F_GETFD and FD_CLOEXEC. [ruby-core:40281] */ #ifdef F_GETFD int flags, flags2, ret; flags = fcntl(fd, F_GETFD); /* should not fail except EBADF. */ if (flags == -1) { rb_bug("rb_maygvl_fd_fix_cloexec: fcntl(%d, F_GETFD) failed: %s", fd, strerror(errno)); } if (fd <= 2) flags2 = flags & ~FD_CLOEXEC; /* Clear CLOEXEC for standard file descriptors: 0, 1, 2. */ else flags2 = flags | FD_CLOEXEC; /* Set CLOEXEC for non-standard file descriptors: 3, 4, 5, ... */ if (flags != flags2) { ret = fcntl(fd, F_SETFD, flags2); if (ret == -1) { rb_bug("rb_maygvl_fd_fix_cloexec: fcntl(%d, F_SETFD, %d) failed: %s", fd, flags2, strerror(errno)); } } #endif } void rb_fd_fix_cloexec(int fd) { rb_maygvl_fd_fix_cloexec(fd); if (max_file_descriptor < fd) max_file_descriptor = fd; } int rb_cloexec_open(const char *pathname, int flags, mode_t mode) { int ret; #ifdef O_CLOEXEC /* O_CLOEXEC is available since Linux 2.6.23. Linux 2.6.18 silently ignore it. */ flags |= O_CLOEXEC; #elif defined O_NOINHERIT flags |= O_NOINHERIT; #endif ret = open(pathname, flags, mode); if (ret == -1) return -1; rb_maygvl_fd_fix_cloexec(ret); return ret; } int rb_cloexec_dup(int oldfd) { /* Don't allocate standard file descriptors: 0, 1, 2 */ return rb_cloexec_fcntl_dupfd(oldfd, 3); } int rb_cloexec_dup2(int oldfd, int newfd) { int ret; /* When oldfd == newfd, dup2 succeeds but dup3 fails with EINVAL. * rb_cloexec_dup2 succeeds as dup2. */ if (oldfd == newfd) { ret = newfd; } else { #if defined(HAVE_DUP3) && defined(O_CLOEXEC) static int try_dup3 = 1; if (2 < newfd && try_dup3) { ret = dup3(oldfd, newfd, O_CLOEXEC); if (ret != -1) return ret; /* dup3 is available since Linux 2.6.27, glibc 2.9. */ if (errno == ENOSYS) { try_dup3 = 0; ret = dup2(oldfd, newfd); } } else { ret = dup2(oldfd, newfd); } #else ret = dup2(oldfd, newfd); #endif if (ret == -1) return -1; } rb_maygvl_fd_fix_cloexec(ret); return ret; } int rb_cloexec_pipe(int fildes[2]) { int ret; #if defined(HAVE_PIPE2) static int try_pipe2 = 1; if (try_pipe2) { ret = pipe2(fildes, O_CLOEXEC); if (ret != -1) return ret; /* pipe2 is available since Linux 2.6.27, glibc 2.9. */ if (errno == ENOSYS) { try_pipe2 = 0; ret = pipe(fildes); } } else { ret = pipe(fildes); } #else ret = pipe(fildes); #endif if (ret == -1) return -1; #ifdef __CYGWIN__ if (ret == 0 && fildes[1] == -1) { close(fildes[0]); fildes[0] = -1; errno = ENFILE; return -1; } #endif rb_maygvl_fd_fix_cloexec(fildes[0]); rb_maygvl_fd_fix_cloexec(fildes[1]); return ret; } int rb_cloexec_fcntl_dupfd(int fd, int minfd) { int ret; #if defined(HAVE_FCNTL) && defined(F_DUPFD_CLOEXEC) static int try_dupfd_cloexec = 1; if (try_dupfd_cloexec) { ret = fcntl(fd, F_DUPFD_CLOEXEC, minfd); if (ret != -1) { if (ret <= 2) rb_maygvl_fd_fix_cloexec(ret); return ret; } /* F_DUPFD_CLOEXEC is available since Linux 2.6.24. Linux 2.6.18 fails with EINVAL */ if (errno == EINVAL) { ret = fcntl(fd, F_DUPFD, minfd); if (ret != -1) { try_dupfd_cloexec = 0; } } } else { ret = fcntl(fd, F_DUPFD, minfd); } #else ret = fcntl(fd, F_DUPFD, minfd); #endif if (ret == -1) return -1; rb_maygvl_fd_fix_cloexec(ret); return ret; } #define argf_of(obj) (*(struct argf *)DATA_PTR(obj)) #define ARGF argf_of(argf) #ifdef _STDIO_USES_IOSTREAM /* GNU libc */ # ifdef _IO_fpos_t # define STDIO_READ_DATA_PENDING(fp) ((fp)->_IO_read_ptr != (fp)->_IO_read_end) # else # define STDIO_READ_DATA_PENDING(fp) ((fp)->_gptr < (fp)->_egptr) # endif #elif defined(FILE_COUNT) # define STDIO_READ_DATA_PENDING(fp) ((fp)->FILE_COUNT > 0) #elif defined(FILE_READEND) # define STDIO_READ_DATA_PENDING(fp) ((fp)->FILE_READPTR < (fp)->FILE_READEND) #elif defined(__BEOS__) || defined(__HAIKU__) # define STDIO_READ_DATA_PENDING(fp) ((fp)->_state._eof == 0) #else # define STDIO_READ_DATA_PENDING(fp) (!feof(fp)) #endif #define GetWriteIO(io) rb_io_get_write_io(io) #define READ_DATA_PENDING(fptr) ((fptr)->rbuf.len) #define READ_DATA_PENDING_COUNT(fptr) ((fptr)->rbuf.len) #define READ_DATA_PENDING_PTR(fptr) ((fptr)->rbuf.ptr+(fptr)->rbuf.off) #define READ_DATA_BUFFERED(fptr) READ_DATA_PENDING(fptr) #define READ_CHAR_PENDING(fptr) ((fptr)->cbuf.len) #define READ_CHAR_PENDING_COUNT(fptr) ((fptr)->cbuf.len) #define READ_CHAR_PENDING_PTR(fptr) ((fptr)->cbuf.ptr+(fptr)->cbuf.off) #if defined(_WIN32) #define WAIT_FD_IN_WIN32(fptr) \ (rb_w32_io_cancelable_p((fptr)->fd) ? 0 : rb_thread_wait_fd((fptr)->fd)) #else #define WAIT_FD_IN_WIN32(fptr) #endif #define READ_CHECK(fptr) do {\ if (!READ_DATA_PENDING(fptr)) {\ WAIT_FD_IN_WIN32(fptr);\ rb_io_check_closed(fptr);\ }\ } while(0) #ifndef S_ISSOCK # ifdef _S_ISSOCK # define S_ISSOCK(m) _S_ISSOCK(m) # else # ifdef _S_IFSOCK # define S_ISSOCK(m) (((m) & S_IFMT) == _S_IFSOCK) # else # ifdef S_IFSOCK # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) # endif # endif # endif #endif #define rb_sys_fail_path(path) rb_sys_fail(NIL_P(path) ? 0 : RSTRING_PTR(path)) static int io_fflush(rb_io_t *); #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) /* * 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. */ inline static int set_binary_mode_with_seek_cur(rb_io_t *fptr) { off_t r, pos; ssize_t read_size; long i; long newlines = 0; long extra_max; char *p; 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); } if (io_fflush(fptr) < 0) { rb_sys_fail(0); } errno = 0; pos = lseek(fptr->fd, 0, SEEK_CUR); if (pos < 0 && errno) { if (errno == ESPIPE) fptr->mode |= FMODE_DUPLEX; return setmode(fptr->fd, O_BINARY); } /* add extra offset for removed '\r' in rbuf */ extra_max = pos - fptr->rbuf.len; p = fptr->rbuf.ptr + fptr->rbuf.off; for (i = 0; i < fptr->rbuf.len; i++) { if (*p == '\n') newlines++; if (extra_max == newlines) break; p++; } 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, fptr->rbuf.ptr, fptr->rbuf.len + newlines); if (read_size < 0) { rb_sys_fail_path(fptr->pathv); } if (read_size == fptr->rbuf.len) { lseek(fptr->fd, r, SEEK_SET); break; } else { newlines--; } } fptr->rbuf.off = 0; fptr->rbuf.len = 0; return setmode(fptr->fd, O_BINARY); } #define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) set_binary_mode_with_seek_cur(fptr) #else /* Unix */ # define DEFAULT_TEXTMODE 0 #define NEED_READCONV(fptr) ((fptr)->encs.enc2 != NULL || NEED_NEWLINE_DECORATOR_ON_READ(fptr)) #define NEED_WRITECONV(fptr) (((fptr)->encs.enc != NULL && (fptr)->encs.enc != rb_ascii8bit_encoding()) || NEED_NEWLINE_DECORATOR_ON_WRITE(fptr) || ((fptr)->encs.ecflags & (ECONV_DECORATOR_MASK|ECONV_STATEFUL_DECORATOR_MASK))) #define SET_BINARY_MODE(fptr) (void)(fptr) #define NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr) (void)(fptr) #define SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags) ((void)(enc2), (void)(ecflags)) #define SET_BINARY_MODE_WITH_SEEK_CUR(fptr) (void)(fptr) #endif #if !defined HAVE_SHUTDOWN && !defined shutdown #define shutdown(a,b) 0 #endif #if defined(_WIN32) #define is_socket(fd, path) rb_w32_is_socket(fd) #elif !defined(S_ISSOCK) #define is_socket(fd, path) 0 #else static int is_socket(int fd, VALUE path) { struct stat sbuf; if (fstat(fd, &sbuf) < 0) rb_sys_fail_path(path); return S_ISSOCK(sbuf.st_mode); } #endif void rb_eof_error(void) { rb_raise(rb_eEOFError, "end of file reached"); } VALUE rb_io_taint_check(VALUE io) { if (!OBJ_UNTRUSTED(io) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: operation on trusted IO"); rb_check_frozen(io); return io; } void rb_io_check_initialized(rb_io_t *fptr) { if (!fptr) { rb_raise(rb_eIOError, "uninitialized stream"); } } void rb_io_check_closed(rb_io_t *fptr) { rb_io_check_initialized(fptr); if (fptr->fd < 0) { rb_raise(rb_eIOError, "closed stream"); } } VALUE rb_io_get_io(VALUE io) { return rb_convert_type(io, T_FILE, "IO", "to_io"); } static 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 obj into an IO, using to_io method. * Returns converted IO or nil if obj cannot be converted * for any reason. * * IO.try_convert(STDOUT) #=> STDOUT * IO.try_convert("STDOUT") #=> nil * * require 'zlib' * f = open("/tmp/zz.gz") #=> # * z = Zlib::GzipReader.open(f) #=> # * IO.try_convert(z) #=> # * */ static VALUE rb_io_s_try_convert(VALUE dummy, VALUE io) { return rb_io_check_io(io); } 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; } static rb_encoding *io_input_encoding(rb_io_t *fptr); static void io_ungetbyte(VALUE str, rb_io_t *fptr) { long len = RSTRING_LEN(str); if (fptr->rbuf.ptr == NULL) { const int min_capa = IO_RBUF_CAPA_FOR(fptr); fptr->rbuf.off = 0; fptr->rbuf.len = 0; #if SIZEOF_LONG > SIZEOF_INT if (len > INT_MAX) rb_raise(rb_eIOError, "ungetbyte failed"); #endif if (len > min_capa) fptr->rbuf.capa = (int)len; else fptr->rbuf.capa = min_capa; fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); } if (fptr->rbuf.capa < len + fptr->rbuf.len) { rb_raise(rb_eIOError, "ungetbyte failed"); } if (fptr->rbuf.off < len) { MEMMOVE(fptr->rbuf.ptr+fptr->rbuf.capa-fptr->rbuf.len, fptr->rbuf.ptr+fptr->rbuf.off, char, fptr->rbuf.len); fptr->rbuf.off = fptr->rbuf.capa-fptr->rbuf.len; } fptr->rbuf.off-=(int)len; fptr->rbuf.len+=(int)len; MEMMOVE(fptr->rbuf.ptr+fptr->rbuf.off, RSTRING_PTR(str), char, len); } static rb_io_t * flush_before_seek(rb_io_t *fptr) { if (io_fflush(fptr) < 0) rb_sys_fail(0); io_unread(fptr); errno = 0; return fptr; } #define io_seek(fptr, ofs, whence) (errno = 0, lseek(flush_before_seek(fptr)->fd, (ofs), (whence))) #define io_tell(fptr) lseek(flush_before_seek(fptr)->fd, 0, SEEK_CUR) #ifndef SEEK_CUR # define SEEK_SET 0 # define SEEK_CUR 1 # define SEEK_END 2 #endif #define FMODE_SYNCWRITE (FMODE_SYNC|FMODE_WRITABLE) void rb_io_check_char_readable(rb_io_t *fptr) { rb_io_check_closed(fptr); if (!(fptr->mode & FMODE_READABLE)) { rb_raise(rb_eIOError, "not opened for reading"); } if (fptr->wbuf.len) { if (io_fflush(fptr) < 0) rb_sys_fail(0); } if (fptr->tied_io_for_writing) { rb_io_t *wfptr; GetOpenFile(fptr->tied_io_for_writing, wfptr); if (io_fflush(wfptr) < 0) rb_sys_fail(0); } } void rb_io_check_byte_readable(rb_io_t *fptr) { rb_io_check_char_readable(fptr); if (READ_CHAR_PENDING(fptr)) { rb_raise(rb_eIOError, "byte oriented read for character buffered IO"); } } void rb_io_check_readable(rb_io_t *fptr) { rb_io_check_byte_readable(fptr); } static rb_encoding* io_read_encoding(rb_io_t *fptr) { if (fptr->encs.enc) { return fptr->encs.enc; } return rb_default_external_encoding(); } static rb_encoding* io_input_encoding(rb_io_t *fptr) { if (fptr->encs.enc2) { return fptr->encs.enc2; } return io_read_encoding(fptr); } void rb_io_check_writable(rb_io_t *fptr) { rb_io_check_closed(fptr); if (!(fptr->mode & FMODE_WRITABLE)) { rb_raise(rb_eIOError, "not opened for writing"); } if (fptr->rbuf.len) { io_unread(fptr); } } int rb_io_read_pending(rb_io_t *fptr) { /* This function is used for bytes and chars. Confusing. */ if (READ_CHAR_PENDING(fptr)) return 1; /* should raise? */ return READ_DATA_PENDING(fptr); } void rb_read_check(FILE *fp) { if (!STDIO_READ_DATA_PENDING(fp)) { rb_thread_wait_fd(fileno(fp)); } } void rb_io_read_check(rb_io_t *fptr) { if (!READ_DATA_PENDING(fptr)) { rb_thread_wait_fd(fptr->fd); } return; } static int ruby_dup(int orig) { int fd; fd = rb_cloexec_dup(orig); if (fd < 0) { if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) { rb_gc(); fd = rb_cloexec_dup(orig); } if (fd < 0) { rb_sys_fail(0); } } rb_update_max_fd(fd); return fd; } static VALUE io_alloc(VALUE klass) { NEWOBJ(io, struct RFile); OBJSETUP(io, klass, T_FILE); io->fptr = 0; return (VALUE)io; } #ifndef S_ISREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif static int wsplit_p(rb_io_t *fptr) { #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK) int r; #endif if (!(fptr->mode & FMODE_WSPLIT_INITIALIZED)) { struct stat buf; if (fstat(fptr->fd, &buf) == 0 && !S_ISREG(buf.st_mode) #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK) && (r = fcntl(fptr->fd, F_GETFL)) != -1 && !(r & O_NONBLOCK) #endif ) { fptr->mode |= FMODE_WSPLIT; } fptr->mode |= FMODE_WSPLIT_INITIALIZED; } return fptr->mode & FMODE_WSPLIT; } struct io_internal_read_struct { int fd; void *buf; size_t capa; }; struct io_internal_write_struct { int fd; const void *buf; size_t capa; }; static VALUE internal_read_func(void *ptr) { struct io_internal_read_struct *iis = ptr; return read(iis->fd, iis->buf, iis->capa); } static VALUE internal_write_func(void *ptr) { struct io_internal_write_struct *iis = ptr; return write(iis->fd, iis->buf, iis->capa); } static ssize_t rb_read_internal(int fd, void *buf, size_t count) { struct io_internal_read_struct iis; iis.fd = fd; iis.buf = buf; iis.capa = count; return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fd); } static ssize_t rb_write_internal(int fd, const void *buf, size_t count) { struct io_internal_write_struct iis; iis.fd = fd; iis.buf = buf; iis.capa = count; return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fd); } static long io_writable_length(rb_io_t *fptr, long l) { if (PIPE_BUF < l && !rb_thread_alone() && wsplit_p(fptr)) { l = PIPE_BUF; } return l; } static VALUE io_flush_buffer_sync(void *arg) { rb_io_t *fptr = arg; long l = io_writable_length(fptr, fptr->wbuf.len); ssize_t r = write(fptr->fd, fptr->wbuf.ptr+fptr->wbuf.off, (size_t)l); if (fptr->wbuf.len <= r) { fptr->wbuf.off = 0; fptr->wbuf.len = 0; return 0; } if (0 <= r) { fptr->wbuf.off += (int)r; fptr->wbuf.len -= (int)r; errno = EAGAIN; } return (VALUE)-1; } static VALUE io_flush_buffer_async(VALUE arg) { rb_io_t *fptr = (rb_io_t *)arg; return rb_thread_io_blocking_region(io_flush_buffer_sync, fptr, fptr->fd); } static inline int io_flush_buffer(rb_io_t *fptr) { if (fptr->write_lock) { return (int)rb_mutex_synchronize(fptr->write_lock, io_flush_buffer_async, (VALUE)fptr); } else { return (int)io_flush_buffer_async((VALUE)fptr); } } static int io_fflush(rb_io_t *fptr) { rb_io_check_closed(fptr); if (fptr->wbuf.len == 0) return 0; if (!rb_thread_fd_writable(fptr->fd)) { rb_io_check_closed(fptr); } while (fptr->wbuf.len > 0 && io_flush_buffer(fptr) != 0) { if (!rb_io_wait_writable(fptr->fd)) return -1; rb_io_check_closed(fptr); } return 0; } int rb_io_wait_readable(int f) { if (f < 0) { rb_raise(rb_eIOError, "closed stream"); } switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif rb_thread_wait_fd(f); return TRUE; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif rb_wait_for_single_fd(f, RB_WAITFD_IN, NULL); return TRUE; default: return FALSE; } } int rb_io_wait_writable(int f) { if (f < 0) { rb_raise(rb_eIOError, "closed stream"); } switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif rb_thread_fd_writable(f); return TRUE; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif rb_wait_for_single_fd(f, RB_WAITFD_OUT, NULL); return TRUE; default: return FALSE; } } static void make_writeconv(rb_io_t *fptr) { if (!fptr->writeconv_initialized) { const char *senc, *denc; rb_encoding *enc; int ecflags; VALUE ecopts; fptr->writeconv_initialized = 1; ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_READ_MASK; ecopts = fptr->encs.ecopts; if (!fptr->encs.enc || (fptr->encs.enc == rb_ascii8bit_encoding() && !fptr->encs.enc2)) { /* no encoding conversion */ fptr->writeconv_pre_ecflags = 0; fptr->writeconv_pre_ecopts = Qnil; fptr->writeconv = rb_econv_open_opts("", "", ecflags, ecopts); if (!fptr->writeconv) rb_exc_raise(rb_econv_open_exc("", "", ecflags)); fptr->writeconv_asciicompat = Qnil; } else { enc = fptr->encs.enc2 ? fptr->encs.enc2 : fptr->encs.enc; senc = rb_econv_asciicompat_encoding(rb_enc_name(enc)); if (!senc && !(fptr->encs.ecflags & ECONV_STATEFUL_DECORATOR_MASK)) { /* single conversion */ fptr->writeconv_pre_ecflags = ecflags; fptr->writeconv_pre_ecopts = ecopts; fptr->writeconv = NULL; fptr->writeconv_asciicompat = Qnil; } else { /* double conversion */ fptr->writeconv_pre_ecflags = ecflags & ~ECONV_STATEFUL_DECORATOR_MASK; fptr->writeconv_pre_ecopts = ecopts; if (senc) { denc = rb_enc_name(enc); fptr->writeconv_asciicompat = rb_str_new2(senc); } else { senc = denc = ""; fptr->writeconv_asciicompat = rb_str_new2(rb_enc_name(enc)); } ecflags = fptr->encs.ecflags & (ECONV_ERROR_HANDLER_MASK|ECONV_STATEFUL_DECORATOR_MASK); ecopts = fptr->encs.ecopts; fptr->writeconv = rb_econv_open_opts(senc, denc, ecflags, ecopts); if (!fptr->writeconv) rb_exc_raise(rb_econv_open_exc(senc, denc, ecflags)); } } } } /* writing functions */ struct binwrite_arg { rb_io_t *fptr; VALUE str; const char *ptr; long length; }; struct write_arg { VALUE io; VALUE str; int nosync; }; static VALUE io_binwrite_string(VALUE arg) { struct binwrite_arg *p = (struct binwrite_arg *)arg; long l = io_writable_length(p->fptr, p->length); return rb_write_internal(p->fptr->fd, p->ptr, l); } static long io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync) { long n, r, offset = 0; if ((n = len) <= 0) return n; if (fptr->wbuf.ptr == NULL && !(!nosync && (fptr->mode & FMODE_SYNC))) { fptr->wbuf.off = 0; fptr->wbuf.len = 0; fptr->wbuf.capa = IO_WBUF_CAPA_MIN; fptr->wbuf.ptr = ALLOC_N(char, fptr->wbuf.capa); fptr->write_lock = rb_mutex_new(); } if ((!nosync && (fptr->mode & (FMODE_SYNC|FMODE_TTY))) || (fptr->wbuf.ptr && fptr->wbuf.capa <= fptr->wbuf.len + len)) { struct binwrite_arg arg; /* xxx: use writev to avoid double write if available */ if (fptr->wbuf.len && fptr->wbuf.len+len <= fptr->wbuf.capa) { if (fptr->wbuf.capa < fptr->wbuf.off+fptr->wbuf.len+len) { MEMMOVE(fptr->wbuf.ptr, fptr->wbuf.ptr+fptr->wbuf.off, char, fptr->wbuf.len); fptr->wbuf.off = 0; } MEMMOVE(fptr->wbuf.ptr+fptr->wbuf.off+fptr->wbuf.len, ptr+offset, char, len); fptr->wbuf.len += (int)len; n = 0; } if (io_fflush(fptr) < 0) return -1L; if (n == 0) return len; /* avoid context switch between "a" and "\n" in STDERR.puts "a". [ruby-dev:25080] */ if (fptr->stdio_file != stderr && !rb_thread_fd_writable(fptr->fd)) { rb_io_check_closed(fptr); } arg.fptr = fptr; arg.str = str; retry: arg.ptr = ptr + offset; arg.length = n; if (fptr->write_lock) { r = rb_mutex_synchronize(fptr->write_lock, io_binwrite_string, (VALUE)&arg); } else { long l = io_writable_length(fptr, n); r = rb_write_internal(fptr->fd, ptr+offset, l); } /* xxx: other threads may modify given string. */ if (r == n) return len; if (0 <= r) { offset += r; n -= r; errno = EAGAIN; } if (rb_io_wait_writable(fptr->fd)) { rb_io_check_closed(fptr); if (offset < len) goto retry; } return -1L; } if (fptr->wbuf.off) { if (fptr->wbuf.len) MEMMOVE(fptr->wbuf.ptr, fptr->wbuf.ptr+fptr->wbuf.off, char, fptr->wbuf.len); fptr->wbuf.off = 0; } MEMMOVE(fptr->wbuf.ptr+fptr->wbuf.off+fptr->wbuf.len, ptr+offset, char, len); fptr->wbuf.len += (int)len; return len; } # define MODE_BTMODE(a,b,c) ((fmode & FMODE_BINMODE) ? (b) : \ (fmode & FMODE_TEXTMODE) ? (c) : (a)) static VALUE do_writeconv(VALUE str, rb_io_t *fptr) { if (NEED_WRITECONV(fptr)) { VALUE common_encoding = Qnil; SET_BINARY_MODE(fptr); make_writeconv(fptr); if (fptr->writeconv) { #define fmode (fptr->mode) if (!NIL_P(fptr->writeconv_asciicompat)) common_encoding = fptr->writeconv_asciicompat; else if (MODE_BTMODE(DEFAULT_TEXTMODE,0,1) && !rb_enc_asciicompat(rb_enc_get(str))) { rb_raise(rb_eArgError, "ASCII incompatible string written for text mode IO without encoding conversion: %s", rb_enc_name(rb_enc_get(str))); } #undef fmode } else { if (fptr->encs.enc2) common_encoding = rb_enc_from_encoding(fptr->encs.enc2); else if (fptr->encs.enc != rb_ascii8bit_encoding()) common_encoding = rb_enc_from_encoding(fptr->encs.enc); } if (!NIL_P(common_encoding)) { str = rb_str_encode(str, common_encoding, fptr->writeconv_pre_ecflags, fptr->writeconv_pre_ecopts); } if (fptr->writeconv) { str = rb_econv_str_convert(fptr->writeconv, str, ECONV_PARTIAL_INPUT); } } #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) #define fmode (fptr->mode) else if (MODE_BTMODE(DEFAULT_TEXTMODE,0,1)) { if ((fptr->mode & FMODE_READABLE) && !(fptr->encs.ecflags & ECONV_NEWLINE_DECORATOR_MASK)) { setmode(fptr->fd, O_BINARY); } else { setmode(fptr->fd, O_TEXT); } if (!rb_enc_asciicompat(rb_enc_get(str))) { rb_raise(rb_eArgError, "ASCII incompatible string written for text mode IO without encoding conversion: %s", rb_enc_name(rb_enc_get(str))); } } #undef fmode #endif return str; } static long io_fwrite(VALUE str, rb_io_t *fptr, int nosync) { #ifdef _WIN32 if (fptr->mode & FMODE_TTY) { long len = rb_w32_write_console(str, fptr->fd); if (len > 0) return len; } #endif str = do_writeconv(str, fptr); return io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), fptr, nosync); } ssize_t rb_io_bufwrite(VALUE io, const void *buf, size_t size) { rb_io_t *fptr; GetOpenFile(io, fptr); rb_io_check_writable(fptr); return (ssize_t)io_binwrite(0, buf, (long)size, fptr, 0); } static VALUE io_write(VALUE io, VALUE str, int nosync) { rb_io_t *fptr; long n; VALUE tmp; rb_secure(4); io = GetWriteIO(io); str = rb_obj_as_string(str); tmp = rb_io_check_io(io); if (NIL_P(tmp)) { /* port is not IO, call write method for it. */ return rb_funcall(io, id_write, 1, str); } io = tmp; if (RSTRING_LEN(str) == 0) return INT2FIX(0); 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 ios. The stream must be opened * for writing. If the argument is not a string, it will be converted * to a string using to_s. Returns the number of bytes * written. * * count = $stdout.write("This is a test\n") * puts "That was #{count} bytes of data" * * produces: * * This is a test * That was 15 bytes of data */ static VALUE io_write_m(VALUE io, VALUE str) { return io_write(io, str, 0); } VALUE rb_io_write(VALUE io, VALUE str) { return rb_funcall(io, id_write, 1, str); } /* * call-seq: * ios << obj -> ios * * String Output---Writes obj to ios. * obj will be converted to a string using * to_s. * * $stdout << "Hello " << "world!\n" * * produces: * * Hello world! */ VALUE rb_io_addstr(VALUE io, VALUE str) { rb_io_write(io, str); return io; } #ifdef HAVE_FSYNC static VALUE nogvl_fsync(void *ptr) { rb_io_t *fptr = ptr; return (VALUE)fsync(fptr->fd); } #endif /* * call-seq: * ios.flush -> ios * * Flushes any buffered data within ios 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 * * produces: * * no newline */ VALUE rb_io_flush(VALUE io) { rb_io_t *fptr; if (!RB_TYPE_P(io, T_FILE)) { return rb_funcall(io, id_flush, 0); } io = GetWriteIO(io); GetOpenFile(io, fptr); if (fptr->mode & FMODE_WRITABLE) { if (io_fflush(fptr) < 0) rb_sys_fail(0); #ifdef _WIN32 if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) == FILE_TYPE_DISK) { rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd); } #endif } if (fptr->mode & FMODE_READABLE) { io_unread(fptr); } return io; } /* * call-seq: * ios.pos -> integer * ios.tell -> integer * * Returns the current offset (in bytes) of ios. * * f = File.new("testfile") * f.pos #=> 0 * f.gets #=> "This is line one\n" * f.pos #=> 17 */ static VALUE rb_io_tell(VALUE io) { rb_io_t *fptr; off_t pos; GetOpenFile(io, fptr); pos = io_tell(fptr); if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv); pos -= fptr->rbuf.len; return OFFT2NUM(pos); } static VALUE rb_io_seek(VALUE io, VALUE offset, int whence) { rb_io_t *fptr; off_t pos; pos = NUM2OFFT(offset); GetOpenFile(io, fptr); pos = io_seek(fptr, pos, whence); if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv); return INT2FIX(0); } /* * call-seq: * ios.seek(amount, whence=IO::SEEK_SET) -> 0 * * Seeks to a given offset anInteger in the stream according to * the value of whence: * * IO::SEEK_CUR | Seeks to _amount_ plus current position * --------------+---------------------------------------------------- * IO::SEEK_END | Seeks to _amount_ plus end of stream (you probably * | want a negative value for _amount_) * --------------+---------------------------------------------------- * IO::SEEK_SET | Seeks to the absolute location given by _amount_ * * Example: * * f = File.new("testfile") * f.seek(-13, IO::SEEK_END) #=> 0 * f.readline #=> "And so on...\n" */ static VALUE rb_io_seek_m(int argc, VALUE *argv, VALUE io) { VALUE offset, ptrname; int whence = SEEK_SET; if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) { whence = NUM2INT(ptrname); } return rb_io_seek(io, offset, whence); } /* * call-seq: * ios.pos = integer -> integer * * Seeks to the given position (in bytes) in ios. * * 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); #ifdef HAVE_FSYNC /* * call-seq: * ios.rewind -> 0 * * Positions ios to the beginning of input, resetting * lineno to zero. * * f = File.new("testfile") * f.readline #=> "This is line one\n" * f.rewind #=> 0 * f.lineno #=> 0 * f.readline #=> "This is line one\n" * * Note that it cannot be used with streams such as pipes, ttys, and sockets. */ static VALUE rb_io_rewind(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); if (io_seek(fptr, 0L, 0) < 0 && errno) rb_sys_fail_path(fptr->pathv); #ifdef _WIN32 if (GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) == FILE_TYPE_DISK) { fsync(fptr->fd); } #endif if (io == ARGF.current_file) { ARGF.lineno -= fptr->lineno; } fptr->lineno = 0; if (fptr->readconv) { clear_readconv(fptr); } return INT2FIX(0); } static int io_fillbuf(rb_io_t *fptr) { ssize_t r; if (fptr->rbuf.ptr == NULL) { fptr->rbuf.off = 0; fptr->rbuf.len = 0; fptr->rbuf.capa = IO_RBUF_CAPA_FOR(fptr); fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); #ifdef _WIN32 fptr->rbuf.capa--; #endif } if (fptr->rbuf.len == 0) { retry: { r = rb_read_internal(fptr->fd, fptr->rbuf.ptr, fptr->rbuf.capa); } if (r < 0) { if (rb_io_wait_readable(fptr->fd)) goto retry; rb_sys_fail_path(fptr->pathv); } fptr->rbuf.off = 0; fptr->rbuf.len = (int)r; /* r should be <= rbuf_capa */ if (r == 0) return -1; /* EOF */ } return 0; } /* * call-seq: * ios.eof -> true or false * ios.eof? -> true or false * * Returns true if ios is at end of file that means * there are no more data to read. * The stream must be opened for reading or an IOError will be * raised. * * f = File.new("testfile") * dummy = f.readlines * f.eof #=> true * * If ios is a stream such as pipe or socket, IO#eof? * blocks until the other end sends some data or closes it. * * r, w = IO.pipe * Thread.new { sleep 1; w.close } * r.eof? #=> true after 1 second blocking * * r, w = IO.pipe * Thread.new { sleep 1; w.puts "a" } * r.eof? #=> false after 1 second blocking * * r, w = IO.pipe * r.eof? # blocks forever * * Note that IO#eof? reads data to the input byte buffer. * So IO#sysread may not behave as you intend with * IO#eof?, unless you call IO#rewind * first (which is not available for some streams). */ VALUE rb_io_eof(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); if (READ_CHAR_PENDING(fptr)) return Qfalse; if (READ_DATA_PENDING(fptr)) return Qfalse; READ_CHECK(fptr); if (io_fillbuf(fptr) < 0) { return Qtrue; } return Qfalse; } /* * call-seq: * ios.sync -> true or false * * Returns the current ``sync mode'' of ios. When sync mode is * true, all output is immediately flushed to the underlying operating * system and is not buffered by Ruby internally. See also * IO#fsync. * * f = File.new("testfile") * f.sync #=> false */ static VALUE rb_io_sync(VALUE io) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); return (fptr->mode & FMODE_SYNC) ? Qtrue : Qfalse; } /* * call-seq: * ios.sync = boolean -> boolean * * Sets the ``sync mode'' to true or false. * When sync mode is true, all output is immediately flushed to the * underlying operating system and is not buffered internally. Returns * the new state. See also IO#fsync. * * f = File.new("testfile") * f.sync = true * * (produces no output) */ 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 ios to disk. * Note that fsync differs from * using IO#sync=. The latter ensures that data is flushed * from Ruby's buffers, but doesn't not guarantee that the underlying * operating system actually writes it to disk. * * NotImplementedError is raised * if the underlying operating system does not support fsync(2). */ static VALUE rb_io_fsync(VALUE io) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); if (io_fflush(fptr) < 0) rb_sys_fail(0); #ifndef _WIN32 /* already called in io_fflush() */ if ((int)rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd) < 0) rb_sys_fail_path(fptr->pathv); #endif return INT2FIX(0); } #else #define rb_io_fsync rb_f_notimplement #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 ios to disk. * * If the underlying operating system does not support fdatasync(2), * IO#fsync is called instead (which might raise a * NotImplementedError). */ static VALUE rb_io_fdatasync(VALUE io) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); if (io_fflush(fptr) < 0) rb_sys_fail(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 * ios. * * $stdin.fileno #=> 0 * $stdout.fileno #=> 1 */ static VALUE rb_io_fileno(VALUE io) { rb_io_t *fptr; int fd; GetOpenFile(io, fptr); fd = fptr->fd; return INT2FIX(fd); } /* * call-seq: * ios.pid -> fixnum * * Returns the process ID of a child process associated with * ios. This will be set by IO.popen. * * pipe = IO.popen("-") * if pipe * $stderr.puts "In parent, child pid is #{pipe.pid}" * else * $stderr.puts "In child, pid is #{$$}" * end * * produces: * * 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; const char *cname; char fd_desc[4+sizeof(int)*3]; const char *path; const char *st = ""; fptr = RFILE(rb_io_taint_check(obj))->fptr; if (!fptr) return rb_any_to_s(obj); cname = rb_obj_classname(obj); if (NIL_P(fptr->pathv)) { if (fptr->fd < 0) { path = ""; st = "(closed)"; } else { snprintf(fd_desc, sizeof(fd_desc), "fd %d", fptr->fd); path = fd_desc; } } else { path = RSTRING_PTR(fptr->pathv); if (fptr->fd < 0) { st = " (closed)"; } } return rb_sprintf("#<%s:%s%s>", cname, path, st); } /* * call-seq: * ios.to_io -> ios * * Returns ios. */ 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; rb_thread_wait_fd(fptr->fd); } 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_thread_wait_fd(fptr->fd); rb_io_check_closed(fptr); if (io_fillbuf(fptr) < 0) { break; } } return len - n; } static long io_fread(VALUE str, long offset, rb_io_t *fptr) { long len; rb_str_locktmp(str); len = io_bufread(RSTRING_PTR(str) + offset, RSTRING_LEN(str) - offset, fptr); rb_str_unlocktmp(str); if (len < 0) rb_sys_fail_path(fptr->pathv); return len; } ssize_t rb_io_bufread(VALUE io, void *buf, size_t size) { rb_io_t *fptr; GetOpenFile(io, fptr); rb_io_check_readable(fptr); return (ssize_t)io_bufread(buf, (long)size, fptr); } #define SMALLBUF 100 static long remain_size(rb_io_t *fptr) { struct stat st; off_t siz = READ_DATA_PENDING_COUNT(fptr); off_t pos; if (fstat(fptr->fd, &st) == 0 && S_ISREG(st.st_mode) #if defined(__BEOS__) || defined(__HAIKU__) && (st.st_dev > 3) #endif ) { if (io_fflush(fptr) < 0) rb_sys_fail(0); pos = lseek(fptr->fd, 0, SEEK_CUR); if (st.st_size >= pos && pos >= 0) { siz += st.st_size - pos; if (siz > LONG_MAX) { rb_raise(rb_eIOError, "file too big for single read"); } } } else { siz += BUFSIZ; } return (long)siz; } static VALUE io_enc_str(VALUE str, rb_io_t *fptr) { OBJ_TAINT(str); rb_enc_associate(str, io_read_encoding(fptr)); return str; } static void make_readconv(rb_io_t *fptr, int size) { if (!fptr->readconv) { int ecflags; VALUE ecopts; const char *sname, *dname; ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_WRITE_MASK; ecopts = fptr->encs.ecopts; if (fptr->encs.enc2) { sname = rb_enc_name(fptr->encs.enc2); dname = rb_enc_name(fptr->encs.enc); } else { sname = dname = ""; } fptr->readconv = rb_econv_open_opts(sname, dname, ecflags, ecopts); if (!fptr->readconv) rb_exc_raise(rb_econv_open_exc(sname, dname, ecflags)); fptr->cbuf.off = 0; fptr->cbuf.len = 0; if (size < IO_CBUF_CAPA_MIN) size = IO_CBUF_CAPA_MIN; fptr->cbuf.capa = size; fptr->cbuf.ptr = ALLOC_N(char, fptr->cbuf.capa); } } #define MORE_CHAR_SUSPENDED Qtrue #define MORE_CHAR_FINISHED Qnil static VALUE fill_cbuf(rb_io_t *fptr, int ec_flags) { const unsigned char *ss, *sp, *se; unsigned char *ds, *dp, *de; rb_econv_result_t res; int putbackable; int cbuf_len0; VALUE exc; ec_flags |= ECONV_PARTIAL_INPUT; if (fptr->cbuf.len == fptr->cbuf.capa) return MORE_CHAR_SUSPENDED; /* cbuf full */ if (fptr->cbuf.len == 0) fptr->cbuf.off = 0; else if (fptr->cbuf.off + fptr->cbuf.len == fptr->cbuf.capa) { memmove(fptr->cbuf.ptr, fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.len); fptr->cbuf.off = 0; } cbuf_len0 = fptr->cbuf.len; while (1) { ss = sp = (const unsigned char *)fptr->rbuf.ptr + fptr->rbuf.off; se = sp + fptr->rbuf.len; ds = dp = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.off + fptr->cbuf.len; de = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.capa; res = rb_econv_convert(fptr->readconv, &sp, se, &dp, de, ec_flags); fptr->rbuf.off += (int)(sp - ss); fptr->rbuf.len -= (int)(sp - ss); fptr->cbuf.len += (int)(dp - ds); putbackable = rb_econv_putbackable(fptr->readconv); if (putbackable) { rb_econv_putback(fptr->readconv, (unsigned char *)fptr->rbuf.ptr + fptr->rbuf.off - putbackable, putbackable); fptr->rbuf.off -= putbackable; fptr->rbuf.len += putbackable; } exc = rb_econv_make_exception(fptr->readconv); if (!NIL_P(exc)) return exc; if (cbuf_len0 != fptr->cbuf.len) return MORE_CHAR_SUSPENDED; if (res == econv_finished) { return MORE_CHAR_FINISHED; } if (res == econv_source_buffer_empty) { if (fptr->rbuf.len == 0) { READ_CHECK(fptr); if (io_fillbuf(fptr) == -1) { if (!fptr->readconv) { return MORE_CHAR_FINISHED; } ds = dp = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.off + fptr->cbuf.len; de = (unsigned char *)fptr->cbuf.ptr + fptr->cbuf.capa; res = rb_econv_convert(fptr->readconv, NULL, NULL, &dp, de, 0); fptr->cbuf.len += (int)(dp - ds); rb_econv_check_error(fptr->readconv); break; } } } } if (cbuf_len0 != fptr->cbuf.len) return MORE_CHAR_SUSPENDED; return MORE_CHAR_FINISHED; } static VALUE more_char(rb_io_t *fptr) { VALUE v; v = fill_cbuf(fptr, ECONV_AFTER_OUTPUT); if (v != MORE_CHAR_SUSPENDED && v != MORE_CHAR_FINISHED) rb_exc_raise(v); return v; } static VALUE io_shift_cbuf(rb_io_t *fptr, int len, VALUE *strp) { VALUE str = Qnil; if (strp) { str = *strp; if (NIL_P(str)) { *strp = str = rb_str_new(fptr->cbuf.ptr+fptr->cbuf.off, len); } else { rb_str_cat(str, fptr->cbuf.ptr+fptr->cbuf.off, len); } OBJ_TAINT(str); rb_enc_associate(str, fptr->encs.enc); } fptr->cbuf.off += len; fptr->cbuf.len -= len; /* xxx: set coderange */ if (fptr->cbuf.len == 0) fptr->cbuf.off = 0; else if (fptr->cbuf.capa/2 < fptr->cbuf.off) { memmove(fptr->cbuf.ptr, fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.len); fptr->cbuf.off = 0; } return str; } static void io_setstrbuf(VALUE *str,long len) { #ifdef _WIN32 if (NIL_P(*str)) { *str = rb_str_new(0, len+1); rb_str_set_len(*str,len); } else { StringValue(*str); rb_str_modify(*str); rb_str_resize(*str, len+1); rb_str_set_len(*str,len); } #else if (NIL_P(*str)) { *str = rb_str_new(0, len); } else { StringValue(*str); rb_str_modify(*str); rb_str_resize(*str, len); } #endif } static VALUE read_all(rb_io_t *fptr, long siz, VALUE str) { long bytes; long n; long pos; rb_encoding *enc; int cr; if (NEED_READCONV(fptr)) { SET_BINARY_MODE(fptr); io_setstrbuf(&str,0); make_readconv(fptr, 0); while (1) { VALUE v; if (fptr->cbuf.len) { io_shift_cbuf(fptr, fptr->cbuf.len, &str); } v = fill_cbuf(fptr, 0); if (v != MORE_CHAR_SUSPENDED && v != MORE_CHAR_FINISHED) { if (fptr->cbuf.len) { io_shift_cbuf(fptr, fptr->cbuf.len, &str); } rb_exc_raise(v); } if (v == MORE_CHAR_FINISHED) { clear_readconv(fptr); return io_enc_str(str, fptr); } } } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); bytes = 0; pos = 0; enc = io_read_encoding(fptr); cr = 0; if (siz == 0) siz = BUFSIZ; io_setstrbuf(&str,siz); for (;;) { READ_CHECK(fptr); n = io_fread(str, bytes, fptr); if (n == 0 && bytes == 0) { break; } bytes += n; 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_resize(str, siz); } if (bytes != siz) rb_str_resize(str, bytes); str = io_enc_str(str, fptr); ENC_CODERANGE_SET(str, cr); return str; } void rb_io_set_nonblock(rb_io_t *fptr) { int oflags; #ifdef F_GETFL oflags = fcntl(fptr->fd, F_GETFL); if (oflags == -1) { rb_sys_fail_path(fptr->pathv); } #else oflags = 0; #endif if ((oflags & O_NONBLOCK) == 0) { oflags |= O_NONBLOCK; if (fcntl(fptr->fd, F_SETFL, oflags) == -1) { rb_sys_fail_path(fptr->pathv); } } } static VALUE io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock) { rb_io_t *fptr; VALUE length, str; long n, len; rb_scan_args(argc, argv, "11", &length, &str); if ((len = NUM2LONG(length)) < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } io_setstrbuf(&str,len); OBJ_TAINT(str); GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); if (len == 0) return str; if (!nonblock) READ_CHECK(fptr); n = read_buffered_data(RSTRING_PTR(str), len, fptr); if (n <= 0) { again: if (nonblock) { rb_io_set_nonblock(fptr); } rb_str_locktmp(str); n = rb_read_internal(fptr->fd, RSTRING_PTR(str), len); rb_str_unlocktmp(str); if (n < 0) { if (!nonblock && rb_io_wait_readable(fptr->fd)) goto again; if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) rb_mod_sys_fail(rb_mWaitReadable, "read would block"); rb_sys_fail_path(fptr->pathv); } } rb_str_resize(str, n); if (n == 0) return Qnil; else return str; } /* * call-seq: * ios.readpartial(maxlen) -> string * ios.readpartial(maxlen, outbuf) -> outbuf * * Reads at most maxlen bytes from the I/O stream. * It blocks only if ios has no data immediately available. * It doesn't block if some data available. * If the optional outbuf argument is present, * it must reference a String, which will receive the data. * It raises EOFError on end of file. * * readpartial is designed for streams such as pipe, socket, tty, etc. * It blocks only when no data immediately available. * This means that it blocks only when following all conditions hold. * * the byte buffer in the IO object is empty. * * the content of the stream is empty. * * the stream is not reached to EOF. * * When readpartial blocks, it waits data or EOF on the stream. * If some data is reached, readpartial returns with the data. * If EOF is reached, readpartial raises EOFError. * * When readpartial doesn't blocks, it returns or raises immediately. * If the byte buffer is not empty, it returns the data in the buffer. * Otherwise if the stream has some content, * it returns the data in the stream. * Otherwise if the stream is reached to EOF, it raises EOFError. * * r, w = IO.pipe # buffer pipe content * w << "abc" # "" "abc". * r.readpartial(4096) #=> "abc" "" "" * r.readpartial(4096) # blocks because buffer and pipe is empty. * * r, w = IO.pipe # buffer pipe content * w << "abc" # "" "abc" * w.close # "" "abc" EOF * r.readpartial(4096) #=> "abc" "" EOF * r.readpartial(4096) # raises EOFError * * r, w = IO.pipe # buffer pipe content * w << "abc\ndef\n" # "" "abc\ndef\n" * r.gets #=> "abc\n" "def\n" "" * w << "ghi\n" # "def\n" "ghi\n" * r.readpartial(4096) #=> "def\n" "" "ghi\n" * r.readpartial(4096) #=> "ghi\n" "" "" * * Note that readpartial behaves similar to sysread. * The differences are: * * If the byte buffer is not empty, read from the byte buffer instead of "sysread for buffered IO (IOError)". * * It doesn't cause Errno::EWOULDBLOCK and Errno::EINTR. When readpartial meets EWOULDBLOCK and EINTR by read system call, readpartial retry the system call. * * The later means that readpartial is nonblocking-flag insensitive. * It blocks on the situation IO#sysread causes Errno::EWOULDBLOCK as if the fd is blocking mode. * */ static VALUE io_readpartial(int argc, VALUE *argv, VALUE io) { VALUE ret; ret = io_getpartial(argc, argv, io, 0); if (NIL_P(ret)) rb_eof_error(); else return ret; } /* * call-seq: * ios.read_nonblock(maxlen) -> string * ios.read_nonblock(maxlen, outbuf) -> outbuf * * Reads at most maxlen bytes from ios using * the read(2) system call after O_NONBLOCK is set for * the underlying file descriptor. * * If the optional outbuf argument is present, * it must reference a String, which will receive the data. * * read_nonblock just calls the read(2) system call. * It causes all errors the read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. * The caller should care such errors. * * If the exception is Errno::EWOULDBLOCK or Errno::AGAIN, * it is extended by IO::WaitReadable. * So IO::WaitReadable can be used to rescue the exceptions for retrying read_nonblock. * * read_nonblock causes EOFError on EOF. * * If the read byte buffer is not empty, * read_nonblock reads from the buffer like readpartial. * In this case, the read(2) system call is not called. * * When read_nonblock raises an exception kind of IO::WaitReadable, * read_nonblock should not be called * until io is readable for avoiding busy loop. * This can be done as follows. * * # emulates blocking read (readpartial). * begin * result = io.read_nonblock(maxlen) * rescue IO::WaitReadable * IO.select([io]) * retry * end * * Although IO#read_nonblock doesn't raise IO::WaitWritable. * OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. * If IO and SSL should be used polymorphically, * IO::WaitWritable should be rescued too. * See the document of OpenSSL::Buffering#read_nonblock for sample code. * * Note that this method is identical to readpartial * except the non-blocking flag is set. */ static VALUE io_read_nonblock(int argc, VALUE *argv, VALUE io) { VALUE ret; ret = io_getpartial(argc, argv, io, 1); if (NIL_P(ret)) rb_eof_error(); else return ret; } /* * call-seq: * ios.write_nonblock(string) -> integer * * Writes the given string to ios 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 Errno::EBADF. * */ static VALUE rb_io_write_nonblock(VALUE io, VALUE str) { rb_io_t *fptr; long n; rb_secure(4); if (!RB_TYPE_P(str, T_STRING)) str = rb_obj_as_string(str); io = GetWriteIO(io); GetOpenFile(io, fptr); rb_io_check_writable(fptr); if (io_fflush(fptr) < 0) rb_sys_fail(0); rb_io_set_nonblock(fptr); n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); if (n == -1) { if (errno == EWOULDBLOCK || errno == EAGAIN) rb_mod_sys_fail(rb_mWaitWritable, "write would block"); rb_sys_fail_path(fptr->pathv); } return LONG2FIX(n); } /* * call-seq: * ios.read([length [, buffer]]) -> string, buffer, or nil * * Reads length bytes from the I/O stream. * * length must be a non-negative integer or nil. * * If length is a positive integer, * it try to read length bytes without any conversion (binary mode). * It returns nil or a string whose length is 1 to length bytes. * nil means it met EOF at beginning. * The 1 to length-1 bytes string means it met EOF after reading the result. * The length bytes string means it doesn't meet EOF. * The resulted string is always ASCII-8BIT encoding. * * If length is omitted or is nil, * it reads until EOF and the encoding conversion is applied. * It returns a string even if EOF is met at beginning. * * If length is zero, it returns "". * * If the optional buffer argument is present, it must reference * a String, which will receive the data. * * At end of file, it returns nil or "" * depend on length. * ios.read() and * ios.read(nil) returns "". * ios.read(positive-integer) returns nil. * * 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 ios is non-blocking mode. * (This method is non-blocking flag insensitive as other methods.) * If you need the behavior like single read(2) system call, * consider readpartial, read_nonblock and sysread. */ static VALUE io_read(int argc, VALUE *argv, VALUE io) { rb_io_t *fptr; long n, len; VALUE length, str; #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) int previous_mode; #endif rb_scan_args(argc, argv, "02", &length, &str); if (NIL_P(length)) { GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); return read_all(fptr, remain_size(fptr), str); } len = NUM2LONG(length); if (len < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } io_setstrbuf(&str,len); GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); if (len == 0) return str; READ_CHECK(fptr); #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) previous_mode = set_binary_mode_with_seek_cur(fptr); #endif n = io_fread(str, 0, fptr); #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) if (previous_mode == O_TEXT) { setmode(fptr->fd, O_TEXT); } #endif if (n == 0) { if (fptr->fd < 0) return Qnil; rb_str_resize(str, 0); return Qnil; } rb_str_resize(str, n); OBJ_TAINT(str); return str; } static void rscheck(const char *rsptr, long rslen, VALUE rs) { if (!rs) return; if (RSTRING_PTR(rs) != rsptr && RSTRING_LEN(rs) != rslen) rb_raise(rb_eRuntimeError, "rs modified"); } static int appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp) { VALUE str = *strp; long limit = *lp; if (NEED_READCONV(fptr)) { SET_BINARY_MODE(fptr); make_readconv(fptr, 0); do { const char *p, *e; int searchlen; if (fptr->cbuf.len) { p = fptr->cbuf.ptr+fptr->cbuf.off; searchlen = fptr->cbuf.len; if (0 < limit && limit < searchlen) searchlen = (int)limit; e = memchr(p, delim, searchlen); if (e) { int len = (int)(e-p+1); if (NIL_P(str)) *strp = str = rb_str_new(p, len); else rb_str_buf_cat(str, p, len); fptr->cbuf.off += len; fptr->cbuf.len -= len; limit -= len; *lp = limit; return delim; } if (NIL_P(str)) *strp = str = rb_str_new(p, searchlen); else rb_str_buf_cat(str, p, searchlen); fptr->cbuf.off += searchlen; fptr->cbuf.len -= searchlen; limit -= searchlen; if (limit == 0) { *lp = limit; return (unsigned char)RSTRING_PTR(str)[RSTRING_LEN(str)-1]; } } } while (more_char(fptr) != MORE_CHAR_FINISHED); clear_readconv(fptr); *lp = limit; return EOF; } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); do { long pending = READ_DATA_PENDING_COUNT(fptr); if (pending > 0) { const char *p = READ_DATA_PENDING_PTR(fptr); const char *e; long last; if (limit > 0 && pending > limit) pending = limit; e = memchr(p, delim, pending); if (e) pending = e - p + 1; if (!NIL_P(str)) { last = RSTRING_LEN(str); rb_str_resize(str, last + pending); } else { last = 0; *strp = str = rb_str_buf_new(pending); rb_str_set_len(str, pending); } read_buffered_data(RSTRING_PTR(str) + last, pending, fptr); /* must not fail */ limit -= pending; *lp = limit; if (e) return delim; if (limit == 0) return (unsigned char)RSTRING_PTR(str)[RSTRING_LEN(str)-1]; } READ_CHECK(fptr); } while (io_fillbuf(fptr) >= 0); *lp = limit; return EOF; } static inline int swallow(rb_io_t *fptr, int term) { if (NEED_READCONV(fptr)) { rb_encoding *enc = io_read_encoding(fptr); int needconv = rb_enc_mbminlen(enc) != 1; SET_BINARY_MODE(fptr); make_readconv(fptr, 0); do { size_t cnt; while ((cnt = READ_CHAR_PENDING_COUNT(fptr)) > 0) { const char *p = READ_CHAR_PENDING_PTR(fptr); int i; if (!needconv) { if (*p != term) return TRUE; i = (int)cnt; while (--i && *++p == term); } else { const char *e = p + cnt; if (rb_enc_ascget(p, e, &i, enc) != term) return TRUE; while ((p += i) < e && rb_enc_ascget(p, e, &i, enc) == term); i = (int)(e - p); } io_shift_cbuf(fptr, (int)cnt - i, NULL); } } while (more_char(fptr) != MORE_CHAR_FINISHED); return FALSE; } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); do { size_t cnt; while ((cnt = READ_DATA_PENDING_COUNT(fptr)) > 0) { char buf[1024]; const char *p = READ_DATA_PENDING_PTR(fptr); int i; if (cnt > sizeof buf) cnt = sizeof buf; if (*p != term) return TRUE; i = (int)cnt; while (--i && *++p == term); if (!read_buffered_data(buf, cnt - i, fptr)) /* must not fail */ rb_sys_fail_path(fptr->pathv); } READ_CHECK(fptr); } while (io_fillbuf(fptr) == 0); return FALSE; } static VALUE rb_io_getline_fast(rb_io_t *fptr, rb_encoding *enc, VALUE io) { VALUE str = Qnil; int len = 0; long pos = 0; int cr = 0; for (;;) { int pending = READ_DATA_PENDING_COUNT(fptr); if (pending > 0) { const char *p = READ_DATA_PENDING_PTR(fptr); const char *e; e = memchr(p, '\n', pending); if (e) { pending = (int)(e - p + 1); } if (NIL_P(str)) { str = rb_str_new(p, pending); fptr->rbuf.off += pending; fptr->rbuf.len -= pending; } else { rb_str_resize(str, len + pending); read_buffered_data(RSTRING_PTR(str)+len, pending, fptr); } len += pending; if (cr != ENC_CODERANGE_BROKEN) pos += rb_str_coderange_scan_restartable(RSTRING_PTR(str) + pos, RSTRING_PTR(str) + len, enc, &cr); if (e) break; } READ_CHECK(fptr); if (io_fillbuf(fptr) < 0) { if (NIL_P(str)) return Qnil; break; } } str = io_enc_str(str, fptr); ENC_CODERANGE_SET(str, cr); fptr->lineno++; if (io == ARGF.current_file) { ARGF.lineno++; ARGF.last_lineno = ARGF.lineno; } else { ARGF.last_lineno = fptr->lineno; } return str; } static void prepare_getline_args(int argc, VALUE *argv, VALUE *rsp, long *limit, VALUE io) { VALUE rs = rb_rs, lim = Qnil; rb_io_t *fptr; if (argc == 1) { VALUE tmp = Qnil; if (NIL_P(argv[0]) || !NIL_P(tmp = rb_check_string_type(argv[0]))) { rs = tmp; } else { lim = argv[0]; } } else if (2 <= argc) { rb_scan_args(argc, argv, "2", &rs, &lim); if (!NIL_P(rs)) StringValue(rs); } if (!NIL_P(rs)) { rb_encoding *enc_rs, *enc_io; GetOpenFile(io, fptr); enc_rs = rb_enc_get(rs); enc_io = io_read_encoding(fptr); if (enc_io != enc_rs && (rb_enc_str_coderange(rs) != ENC_CODERANGE_7BIT || (RSTRING_LEN(rs) > 0 && !rb_enc_asciicompat(enc_io)))) { if (rs == rb_default_rs) { rs = rb_enc_str_new(0, 0, enc_io); rb_str_buf_cat_ascii(rs, "\n"); } else { rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS", rb_enc_name(enc_io), rb_enc_name(enc_rs)); } } } *rsp = rs; *limit = NIL_P(lim) ? -1L : NUM2LONG(lim); } static VALUE rb_io_getline_1(VALUE rs, long limit, VALUE io) { VALUE str = Qnil; rb_io_t *fptr; int nolimit = 0; rb_encoding *enc; GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); if (NIL_P(rs) && limit < 0) { str = read_all(fptr, 0, Qnil); if (RSTRING_LEN(str) == 0) return Qnil; } else if (limit == 0) { return rb_enc_str_new(0, 0, io_read_encoding(fptr)); } else if (rs == rb_default_rs && limit < 0 && !NEED_READCONV(fptr) && rb_enc_asciicompat(enc = io_read_encoding(fptr))) { NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); return rb_io_getline_fast(fptr, enc, io); } else { int c, newline = -1; const char *rsptr = 0; long rslen = 0; int rspara = 0; int extra_limit = 16; SET_BINARY_MODE(fptr); enc = io_read_encoding(fptr); if (!NIL_P(rs)) { rslen = RSTRING_LEN(rs); if (rslen == 0) { rsptr = "\n\n"; rslen = 2; rspara = 1; swallow(fptr, '\n'); rs = 0; if (!rb_enc_asciicompat(enc)) { rs = rb_usascii_str_new(rsptr, rslen); rs = rb_str_encode(rs, rb_enc_from_encoding(enc), 0, Qnil); OBJ_FREEZE(rs); rsptr = RSTRING_PTR(rs); rslen = RSTRING_LEN(rs); } } else { rsptr = RSTRING_PTR(rs); } newline = (unsigned char)rsptr[rslen - 1]; } /* MS - Optimisation */ while ((c = appendline(fptr, newline, &str, &limit)) != EOF) { const char *s, *p, *pp, *e; if (c == newline) { if (RSTRING_LEN(str) < rslen) continue; s = RSTRING_PTR(str); e = s + RSTRING_LEN(str); p = e - rslen; pp = rb_enc_left_char_head(s, p, e, enc); if (pp != p) continue; if (!rspara) rscheck(rsptr, rslen, rs); if (memcmp(p, rsptr, rslen) == 0) break; } if (limit == 0) { s = RSTRING_PTR(str); p = s + RSTRING_LEN(str); pp = rb_enc_left_char_head(s, p-1, p, enc); if (extra_limit && MBCLEN_NEEDMORE_P(rb_enc_precise_mbclen(pp, p, enc))) { /* relax the limit while incomplete character. * extra_limit limits the relax length */ limit = 1; extra_limit--; } else { nolimit = 1; break; } } } if (rspara) { if (c != EOF) { swallow(fptr, '\n'); } } if (!NIL_P(str)) str = io_enc_str(str, fptr); } if (!NIL_P(str)) { if (!nolimit) { fptr->lineno++; if (io == ARGF.current_file) { ARGF.lineno++; ARGF.last_lineno = ARGF.lineno; } else { ARGF.last_lineno = fptr->lineno; } } } return str; } static VALUE rb_io_getline(int argc, VALUE *argv, VALUE io) { VALUE rs; long limit; prepare_getline_args(argc, argv, &rs, &limit, io); return rb_io_getline_1(rs, limit, io); } VALUE rb_io_gets(VALUE io) { return rb_io_getline_1(rb_default_rs, -1, io); } /* * call-seq: * ios.gets(sep=$/) -> string or nil * ios.gets(limit) -> string or nil * ios.gets(sep, limit) -> string or nil * * Reads the next ``line'' from the I/O stream; lines are separated by * sep. A separator of nil 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 IOError * will be raised. The line read in will be returned and also assigned * to $_. Returns nil 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 ios. The stream must be * opened for reading. lineno 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 $/ like #each, #lines and #readline will * also increment lineno. * * See also the $. 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. * $. 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 IO#gets, but raises an * EOFError 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 ios, and returns them in * anArray. Lines are separated by the optional sep. If * sep is nil, 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 IOError will be raised. * * f = File.new("testfile") * f.readlines[0] #=> "This is line one\n" */ static VALUE rb_io_readlines(int argc, VALUE *argv, VALUE io) { VALUE line, ary, rs; long limit; prepare_getline_args(argc, argv, &rs, &limit, io); if (limit == 0) rb_raise(rb_eArgError, "invalid limit: 0 for readlines"); ary = rb_ary_new(); while (!NIL_P(line = rb_io_getline_1(rs, limit, io))) { rb_ary_push(ary, line); } return ary; } /* * call-seq: * ios.each(sep=$/) {|line| block } -> ios * ios.each(limit) {|line| block } -> ios * ios.each(sep,limit) {|line| block } -> ios * ios.each(...) -> an_enumerator * * ios.each_line(sep=$/) {|line| block } -> ios * ios.each_line(limit) {|line| block } -> ios * ios.each_line(sep,limit) {|line| block } -> ios * ios.each_line(...) -> an_enumerator * * ios.lines(sep=$/) {|line| block } -> ios * ios.lines(limit) {|line| block } -> ios * ios.lines(sep,limit) {|line| block } -> ios * ios.lines(...) -> an_enumerator * * Executes the block for every line in ios, where lines are * separated by sep. ios must be opened for * reading or an IOError will be raised. * * If no block is given, an enumerator is returned instead. * * f = File.new("testfile") * f.each {|line| puts "#{f.lineno}: #{line}" } * * produces: * * 1: This is line one * 2: This is line two * 3: This is line three * 4: And so on... */ static VALUE rb_io_each_line(int argc, VALUE *argv, VALUE io) { VALUE str, rs; long limit; RETURN_ENUMERATOR(io, argc, argv); prepare_getline_args(argc, argv, &rs, &limit, io); if (limit == 0) rb_raise(rb_eArgError, "invalid limit: 0 for each_line"); while (!NIL_P(str = rb_io_getline_1(rs, limit, io))) { rb_yield(str); } return io; } /* * call-seq: * ios.bytes {|byte| block } -> ios * ios.bytes -> an_enumerator * * ios.each_byte {|byte| block } -> ios * ios.each_byte -> an_enumerator * * Calls the given block once for each byte (0..255) in ios, * passing the byte as an argument. The stream must be opened for * reading or an IOError 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 } #=> # * checksum #=> 12 */ static VALUE rb_io_each_byte(VALUE io) { rb_io_t *fptr; RETURN_ENUMERATOR(io, 0, 0); GetOpenFile(io, fptr); for (;;) { while (fptr->rbuf.len > 0) { char *p = fptr->rbuf.ptr + fptr->rbuf.off++; fptr->rbuf.len--; rb_yield(INT2FIX(*p & 0xff)); errno = 0; } rb_io_check_byte_readable(fptr); READ_CHECK(fptr); if (io_fillbuf(fptr) < 0) { break; } } return io; } static VALUE io_getc(rb_io_t *fptr, rb_encoding *enc) { int r, n, cr = 0; VALUE str; if (NEED_READCONV(fptr)) { VALUE str = Qnil; rb_encoding *read_enc = io_read_encoding(fptr); SET_BINARY_MODE(fptr); make_readconv(fptr, 0); while (1) { if (fptr->cbuf.len) { r = rb_enc_precise_mbclen(fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len, read_enc); if (!MBCLEN_NEEDMORE_P(r)) break; if (fptr->cbuf.len == fptr->cbuf.capa) { rb_raise(rb_eIOError, "too long character"); } } if (more_char(fptr) == MORE_CHAR_FINISHED) { if (fptr->cbuf.len == 0) { clear_readconv(fptr); return Qnil; } /* return an unit of an incomplete character just before EOF */ str = rb_enc_str_new(fptr->cbuf.ptr+fptr->cbuf.off, 1, read_enc); fptr->cbuf.off += 1; fptr->cbuf.len -= 1; if (fptr->cbuf.len == 0) clear_readconv(fptr); ENC_CODERANGE_SET(str, ENC_CODERANGE_BROKEN); return str; } } if (MBCLEN_INVALID_P(r)) { r = rb_enc_mbclen(fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len, read_enc); io_shift_cbuf(fptr, r, &str); cr = ENC_CODERANGE_BROKEN; } else { io_shift_cbuf(fptr, MBCLEN_CHARFOUND_LEN(r), &str); cr = ISASCII(r) ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID; } str = io_enc_str(str, fptr); ENC_CODERANGE_SET(str, cr); return str; } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); if (io_fillbuf(fptr) < 0) { return Qnil; } if (rb_enc_asciicompat(enc) && ISASCII(fptr->rbuf.ptr[fptr->rbuf.off])) { str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, 1); fptr->rbuf.off += 1; fptr->rbuf.len -= 1; cr = ENC_CODERANGE_7BIT; } else { r = rb_enc_precise_mbclen(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc); if (MBCLEN_CHARFOUND_P(r) && (n = MBCLEN_CHARFOUND_LEN(r)) <= fptr->rbuf.len) { str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, n); fptr->rbuf.off += n; fptr->rbuf.len -= n; cr = ENC_CODERANGE_VALID; } else if (MBCLEN_NEEDMORE_P(r)) { str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.len); fptr->rbuf.len = 0; getc_needmore: if (io_fillbuf(fptr) != -1) { rb_str_cat(str, fptr->rbuf.ptr+fptr->rbuf.off, 1); fptr->rbuf.off++; fptr->rbuf.len--; r = rb_enc_precise_mbclen(RSTRING_PTR(str), RSTRING_PTR(str)+RSTRING_LEN(str), enc); if (MBCLEN_NEEDMORE_P(r)) { goto getc_needmore; } else if (MBCLEN_CHARFOUND_P(r)) { cr = ENC_CODERANGE_VALID; } } } else { str = rb_str_new(fptr->rbuf.ptr+fptr->rbuf.off, 1); fptr->rbuf.off++; fptr->rbuf.len--; } } if (!cr) cr = ENC_CODERANGE_BROKEN; str = io_enc_str(str, fptr); ENC_CODERANGE_SET(str, cr); return str; } /* * call-seq: * ios.chars {|c| block } -> ios * ios.chars -> an_enumerator * * ios.each_char {|c| block } -> ios * ios.each_char -> an_enumerator * * Calls the given block once for each character in ios, * passing the character as an argument. The stream must be opened for * reading or an IOError will be raised. * * If no block is given, an enumerator is returned instead. * * f = File.new("testfile") * f.each_char {|c| print c, ' ' } #=> # */ static VALUE rb_io_each_char(VALUE io) { rb_io_t *fptr; rb_encoding *enc; VALUE c; RETURN_ENUMERATOR(io, 0, 0); GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); enc = io_input_encoding(fptr); READ_CHECK(fptr); while (!NIL_P(c = io_getc(fptr, enc))) { rb_yield(c); } return io; } /* * call-seq: * ios.each_codepoint {|c| block } -> ios * ios.codepoints {|c| block } -> ios * ios.each_codepoint -> an_enumerator * ios.codepoints -> an_enumerator * * Passes the Integer ordinal of each character in ios, * passing the codepoint as an argument. The stream must be opened for * reading or an IOError will be raised. * * If no block is given, an enumerator is returned instead. * */ static VALUE rb_io_each_codepoint(VALUE io) { rb_io_t *fptr; rb_encoding *enc; unsigned int c; int r, n; RETURN_ENUMERATOR(io, 0, 0); GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); READ_CHECK(fptr); if (NEED_READCONV(fptr)) { SET_BINARY_MODE(fptr); for (;;) { make_readconv(fptr, 0); for (;;) { if (fptr->cbuf.len) { if (fptr->encs.enc) r = rb_enc_precise_mbclen(fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len, fptr->encs.enc); else r = ONIGENC_CONSTRUCT_MBCLEN_CHARFOUND(1); if (!MBCLEN_NEEDMORE_P(r)) break; if (fptr->cbuf.len == fptr->cbuf.capa) { rb_raise(rb_eIOError, "too long character"); } } if (more_char(fptr) == MORE_CHAR_FINISHED) { clear_readconv(fptr); /* ignore an incomplete character before EOF */ return io; } } if (MBCLEN_INVALID_P(r)) { rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(fptr->encs.enc)); } n = MBCLEN_CHARFOUND_LEN(r); if (fptr->encs.enc) { c = rb_enc_codepoint(fptr->cbuf.ptr+fptr->cbuf.off, fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len, fptr->encs.enc); } else { c = (unsigned char)fptr->cbuf.ptr[fptr->cbuf.off]; } fptr->cbuf.off += n; fptr->cbuf.len -= n; rb_yield(UINT2NUM(c)); } } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); enc = io_input_encoding(fptr); for (;;) { if (io_fillbuf(fptr) < 0) { return io; } r = rb_enc_precise_mbclen(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc); if (MBCLEN_CHARFOUND_P(r) && (n = MBCLEN_CHARFOUND_LEN(r)) <= fptr->rbuf.len) { c = rb_enc_codepoint(fptr->rbuf.ptr+fptr->rbuf.off, fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc); fptr->rbuf.off += n; fptr->rbuf.len -= n; rb_yield(UINT2NUM(c)); } else if (MBCLEN_INVALID_P(r)) { rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(enc)); } else { continue; } } return io; } /* * call-seq: * ios.getc -> string or nil * * Reads a one-character string from ios. Returns * nil 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 ios. Raises an * EOFError 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 ios. Returns * nil 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 IO#getbyte, but raises an * EOFError 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 ios, * 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 IO#sysread). * * f = File.new("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 ios, * 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 IO#sysread). * * f = File.new("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 true if ios is associated with a * terminal device (tty), false 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 true if ios will be closed on exec. * * f = open("/dev/null") * f.close_on_exec? #=> false * f.close_on_exec = true * f.close_on_exec? #=> true * f.close_on_exec = false * f.close_on_exec? #=> false */ static VALUE rb_io_close_on_exec_p(VALUE io) { rb_io_t *fptr; VALUE write_io; int fd, ret; write_io = GetWriteIO(io); if (io != write_io) { GetOpenFile(write_io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if (!(ret & FD_CLOEXEC)) return Qfalse; } } GetOpenFile(io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if (!(ret & FD_CLOEXEC)) return Qfalse; } return Qtrue; } #else #define rb_io_close_on_exec_p rb_f_notimplement #endif #if defined(HAVE_FCNTL) && defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) /* * call-seq: * ios.close_on_exec = bool -> true or false * * Sets a close-on-exec flag. * * f = open("/dev/null") * f.close_on_exec = true * system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory * f.closed? #=> false */ static VALUE rb_io_set_close_on_exec(VALUE io, VALUE arg) { int flag = RTEST(arg) ? FD_CLOEXEC : 0; rb_io_t *fptr; VALUE write_io; int fd, ret; write_io = GetWriteIO(io); if (io != write_io) { GetOpenFile(write_io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if ((ret & FD_CLOEXEC) != flag) { ret = (ret & ~FD_CLOEXEC) | flag; ret = fcntl(fd, F_SETFD, ret); if (ret == -1) rb_sys_fail_path(fptr->pathv); } } } GetOpenFile(io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if ((ret & FD_CLOEXEC) != flag) { ret = (ret & ~FD_CLOEXEC) | flag; ret = fcntl(fd, F_SETFD, ret); if (ret == -1) rb_sys_fail_path(fptr->pathv); } } return Qnil; } #else #define rb_io_set_close_on_exec rb_f_notimplement #endif #define FMODE_PREP (1<<16) #define IS_PREP_STDIO(f) ((f)->mode & FMODE_PREP) #define PREP_STDIO_NAME(f) (RSTRING_PTR((f)->pathv)) static VALUE finish_writeconv(rb_io_t *fptr, int noalloc) { unsigned char *ds, *dp, *de; rb_econv_result_t res; if (!fptr->wbuf.ptr) { unsigned char buf[1024]; long r; res = econv_destination_buffer_full; while (res == econv_destination_buffer_full) { ds = dp = buf; de = buf + sizeof(buf); res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0); while (dp-ds) { retry: r = rb_write_internal(fptr->fd, ds, dp-ds); if (r == dp-ds) break; if (0 <= r) { ds += r; } if (rb_io_wait_writable(fptr->fd)) { if (fptr->fd < 0) return noalloc ? Qtrue : rb_exc_new3(rb_eIOError, rb_str_new_cstr("closed stream")); goto retry; } return noalloc ? Qtrue : INT2NUM(errno); } if (res == econv_invalid_byte_sequence || res == econv_incomplete_input || res == econv_undefined_conversion) { return noalloc ? Qtrue : rb_econv_make_exception(fptr->writeconv); } } return Qnil; } res = econv_destination_buffer_full; while (res == econv_destination_buffer_full) { if (fptr->wbuf.len == fptr->wbuf.capa) { if (io_fflush(fptr) < 0) return noalloc ? Qtrue : INT2NUM(errno); } ds = dp = (unsigned char *)fptr->wbuf.ptr + fptr->wbuf.off + fptr->wbuf.len; de = (unsigned char *)fptr->wbuf.ptr + fptr->wbuf.capa; res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0); fptr->wbuf.len += (int)(dp - ds); if (res == econv_invalid_byte_sequence || res == econv_incomplete_input || res == econv_undefined_conversion) { return noalloc ? Qtrue : rb_econv_make_exception(fptr->writeconv); } } return Qnil; } struct finish_writeconv_arg { rb_io_t *fptr; int noalloc; }; static VALUE finish_writeconv_sync(VALUE arg) { struct finish_writeconv_arg *p = (struct finish_writeconv_arg *)arg; return finish_writeconv(p->fptr, p->noalloc); } static void fptr_finalize(rb_io_t *fptr, int noraise) { VALUE err = Qnil; 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); } } if (IS_PREP_STDIO(fptr) || fptr->fd <= 2) { goto skip_fd_close; } if (fptr->stdio_file) { /* fptr->stdio_file is deallocated anyway * even if fclose failed. */ if (fclose(fptr->stdio_file) < 0 && NIL_P(err)) err = noraise ? Qtrue : INT2NUM(errno); } else if (0 <= fptr->fd) { /* fptr->fd may be closed even if close fails. * POSIX doesn't specify it. * We assumes it is closed. */ if (close(fptr->fd) < 0 && NIL_P(err)) err = noraise ? Qtrue : INT2NUM(errno); } skip_fd_close: fptr->fd = -1; fptr->stdio_file = 0; fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE); if (!NIL_P(err) && !noraise) { switch(TYPE(err)) { case T_FIXNUM: case T_BIGNUM: errno = NUM2INT(err); rb_sys_fail_path(fptr->pathv); default: rb_exc_raise(err); } } } static void rb_io_fptr_cleanup(rb_io_t *fptr, int noraise) { if (fptr->finalize) { (*fptr->finalize)(fptr, noraise); } else { fptr_finalize(fptr, noraise); } } static void clear_readconv(rb_io_t *fptr) { if (fptr->readconv) { rb_econv_close(fptr->readconv); fptr->readconv = NULL; } if (fptr->cbuf.ptr) { free(fptr->cbuf.ptr); fptr->cbuf.ptr = NULL; } } static void clear_writeconv(rb_io_t *fptr) { if (fptr->writeconv) { rb_econv_close(fptr->writeconv); fptr->writeconv = NULL; } fptr->writeconv_initialized = 0; } static void clear_codeconv(rb_io_t *fptr) { clear_readconv(fptr); clear_writeconv(fptr); } int rb_io_fptr_finalize(rb_io_t *fptr) { if (!fptr) return 0; fptr->pathv = Qnil; if (0 <= fptr->fd) rb_io_fptr_cleanup(fptr, TRUE); fptr->write_lock = 0; if (fptr->rbuf.ptr) { free(fptr->rbuf.ptr); fptr->rbuf.ptr = 0; } if (fptr->wbuf.ptr) { free(fptr->wbuf.ptr); fptr->wbuf.ptr = 0; } clear_codeconv(fptr); free(fptr); return 1; } size_t rb_econv_memsize(rb_econv_t *); RUBY_FUNC_EXPORTED size_t rb_io_memsize(const rb_io_t *fptr) { size_t size = sizeof(rb_io_t); size += fptr->rbuf.capa; size += fptr->wbuf.capa; size += fptr->cbuf.capa; if (fptr->readconv) size += rb_econv_memsize(fptr->readconv); if (fptr->writeconv) size += rb_econv_memsize(fptr->writeconv); return size; } VALUE rb_io_close(VALUE io) { rb_io_t *fptr; int fd; VALUE write_io; rb_io_t *write_fptr; write_io = GetWriteIO(io); if (io != write_io) { write_fptr = RFILE(write_io)->fptr; if (write_fptr && 0 <= write_fptr->fd) { rb_io_fptr_cleanup(write_fptr, TRUE); } } fptr = RFILE(io)->fptr; if (!fptr) return Qnil; if (fptr->fd < 0) return Qnil; fd = fptr->fd; #if defined __APPLE__ && defined(__MACH__) && \ (!defined(MAC_OS_X_VERSION_MIN_ALLOWED) || MAC_OS_X_VERSION_MIN_ALLOWED <= 1050) /* close(2) on a fd which is being read by another thread causes * deadlock on Mac OS X 10.5 */ rb_thread_fd_close(fd); #endif rb_io_fptr_cleanup(fptr, FALSE); rb_thread_fd_close(fd); if (fptr->pid) { rb_syswait(fptr->pid); fptr->pid = 0; } return Qnil; } /* * call-seq: * ios.close -> nil * * Closes ios and flushes any pending writes to the operating * system. The stream is unavailable for any further data operations; * an IOError is raised if such an attempt is made. I/O * streams are automatically closed when they are claimed by the * garbage collector. * * If ios is opened by IO.popen, * close sets $?. */ static VALUE rb_io_close_m(VALUE io) { if (rb_safe_level() >= 4 && !OBJ_UNTRUSTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } rb_io_check_closed(RFILE(io)->fptr); rb_io_close(io); return Qnil; } static VALUE io_call_close(VALUE io) { return rb_funcall(io, rb_intern("close"), 0, 0); } static VALUE io_close(VALUE io) { return rb_rescue(io_call_close, io, 0, 0); } /* * call-seq: * ios.closed? -> true or false * * Returns true if ios is completely closed (for * duplex streams, both reader and writer), false * otherwise. * * f = File.new("testfile") * f.close #=> nil * f.closed? #=> true * f = IO.popen("/bin/sh","r+") * f.close_write #=> nil * f.closed? #=> false * f.close_read #=> nil * f.closed? #=> true */ static VALUE rb_io_closed(VALUE io) { rb_io_t *fptr; VALUE write_io; rb_io_t *write_fptr; write_io = GetWriteIO(io); if (io != write_io) { write_fptr = RFILE(write_io)->fptr; if (write_fptr && 0 <= write_fptr->fd) { return Qfalse; } } fptr = RFILE(io)->fptr; rb_io_check_initialized(fptr); return 0 <= fptr->fd ? Qfalse : Qtrue; } /* * call-seq: * ios.close_read -> nil * * Closes the read end of a duplex I/O stream (i.e., one that contains * both a read and a write stream, such as a pipe). Will raise an * IOError if the stream is not duplexed. * * f = IO.popen("/bin/sh","r+") * f.close_read * f.readlines * * produces: * * prog.rb:3:in `readlines': not opened for reading (IOError) * from prog.rb:3 */ static VALUE rb_io_close_read(VALUE io) { rb_io_t *fptr; VALUE write_io; if (rb_safe_level() >= 4 && !OBJ_UNTRUSTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } GetOpenFile(io, fptr); if (is_socket(fptr->fd, fptr->pathv)) { #ifndef SHUT_RD # define SHUT_RD 0 #endif if (shutdown(fptr->fd, SHUT_RD) < 0) rb_sys_fail_path(fptr->pathv); fptr->mode &= ~FMODE_READABLE; if (!(fptr->mode & FMODE_WRITABLE)) return rb_io_close(io); return Qnil; } write_io = GetWriteIO(io); if (io != write_io) { rb_io_t *wfptr; rb_io_fptr_cleanup(fptr, FALSE); GetOpenFile(write_io, wfptr); RFILE(io)->fptr = wfptr; RFILE(write_io)->fptr = NULL; rb_io_fptr_finalize(fptr); return Qnil; } if (fptr->mode & FMODE_WRITABLE) { rb_raise(rb_eIOError, "closing non-duplex IO for reading"); } return rb_io_close(io); } /* * call-seq: * ios.close_write -> nil * * Closes the write end of a duplex I/O stream (i.e., one that contains * both a read and a write stream, such as a pipe). Will raise an * IOError if the stream is not duplexed. * * f = IO.popen("/bin/sh","r+") * f.close_write * f.print "nowhere" * * produces: * * prog.rb:3:in `write': not opened for writing (IOError) * from prog.rb:3:in `print' * from prog.rb:3 */ static VALUE rb_io_close_write(VALUE io) { rb_io_t *fptr; VALUE write_io; if (rb_safe_level() >= 4 && !OBJ_UNTRUSTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } write_io = GetWriteIO(io); GetOpenFile(write_io, fptr); if (is_socket(fptr->fd, fptr->pathv)) { #ifndef SHUT_WR # define SHUT_WR 1 #endif if (shutdown(fptr->fd, SHUT_WR) < 0) rb_sys_fail_path(fptr->pathv); fptr->mode &= ~FMODE_WRITABLE; if (!(fptr->mode & FMODE_READABLE)) return rb_io_close(write_io); return Qnil; } if (fptr->mode & FMODE_READABLE) { rb_raise(rb_eIOError, "closing non-duplex IO for writing"); } rb_io_close(write_io); if (io != write_io) { GetOpenFile(io, fptr); fptr->tied_io_for_writing = 0; fptr->mode &= ~FMODE_DUPLEX; } return Qnil; } /* * call-seq: * ios.sysseek(offset, whence=IO::SEEK_SET) -> integer * * Seeks to a given offset in the stream according to the value * of whence (see IO#seek for values of * whence). Returns the new offset into the file. * * f = File.new("testfile") * f.sysseek(-13, IO::SEEK_END) #=> 53 * f.sysread(10) #=> "And so on." */ static VALUE rb_io_sysseek(int argc, VALUE *argv, VALUE io) { VALUE offset, ptrname; int whence = SEEK_SET; rb_io_t *fptr; off_t pos; if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) { whence = NUM2INT(ptrname); } pos = NUM2OFFT(offset); GetOpenFile(io, fptr); if ((fptr->mode & FMODE_READABLE) && (READ_DATA_BUFFERED(fptr) || READ_CHAR_PENDING(fptr))) { rb_raise(rb_eIOError, "sysseek for buffered IO"); } if ((fptr->mode & FMODE_WRITABLE) && fptr->wbuf.len) { rb_warn("sysseek for buffered IO"); } errno = 0; pos = lseek(fptr->fd, pos, whence); if (pos == -1 && errno) rb_sys_fail_path(fptr->pathv); return OFFT2NUM(pos); } /* * call-seq: * ios.syswrite(string) -> integer * * Writes the given string to ios using a low-level write. * Returns the number of bytes written. Do not mix with other methods * that write to ios or you may get unpredictable results. * Raises SystemCallError on error. * * f = File.new("out", "w") * f.syswrite("ABCDEF") #=> 6 */ static VALUE rb_io_syswrite(VALUE io, VALUE str) { rb_io_t *fptr; long n; rb_secure(4); if (!RB_TYPE_P(str, T_STRING)) str = rb_obj_as_string(str); io = GetWriteIO(io); GetOpenFile(io, fptr); rb_io_check_writable(fptr); if (fptr->wbuf.len) { rb_warn("syswrite for buffered IO"); } if (!rb_thread_fd_writable(fptr->fd)) { rb_io_check_closed(fptr); } n = rb_write_internal(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); if (n == -1) rb_sys_fail_path(fptr->pathv); return LONG2FIX(n); } /* * call-seq: * ios.sysread(maxlen[, outbuf]) -> string * * Reads maxlen bytes from ios using a low-level * read and returns them as a string. Do not mix with other methods * that read from ios or you may get unpredictable results. * If the optional outbuf argument is present, it must reference * a String, which will receive the data. * Raises SystemCallError on error and * EOFError at end of file. * * f = File.new("testfile") * f.sysread(16) #=> "This is line one" */ static VALUE rb_io_sysread(int argc, VALUE *argv, VALUE io) { VALUE len, str; rb_io_t *fptr; long n, ilen; rb_scan_args(argc, argv, "11", &len, &str); ilen = NUM2LONG(len); io_setstrbuf(&str,ilen); if (ilen == 0) return str; GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); if (READ_DATA_BUFFERED(fptr)) { rb_raise(rb_eIOError, "sysread for buffered IO"); } n = fptr->fd; rb_thread_wait_fd(fptr->fd); rb_io_check_closed(fptr); rb_str_locktmp(str); n = rb_read_internal(fptr->fd, RSTRING_PTR(str), ilen); rb_str_unlocktmp(str); if (n == -1) { rb_sys_fail_path(fptr->pathv); } rb_str_set_len(str, n); if (n == 0 && ilen > 0) { rb_eof_error(); } rb_str_resize(str, n); OBJ_TAINT(str); return str; } VALUE rb_io_binmode(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); if (fptr->readconv) rb_econv_binmode(fptr->readconv); if (fptr->writeconv) rb_econv_binmode(fptr->writeconv); fptr->mode |= FMODE_BINMODE; fptr->mode &= ~FMODE_TEXTMODE; fptr->writeconv_pre_ecflags &= ~ECONV_NEWLINE_DECORATOR_MASK; #ifdef O_BINARY if (!fptr->readconv) { SET_BINARY_MODE_WITH_SEEK_CUR(fptr); } else { setmode(fptr->fd, O_BINARY); } #endif return io; } VALUE rb_io_ascii8bit_binmode(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); if (fptr->readconv) { rb_econv_close(fptr->readconv); fptr->readconv = NULL; } if (fptr->writeconv) { rb_econv_close(fptr->writeconv); fptr->writeconv = NULL; } fptr->mode |= FMODE_BINMODE; fptr->mode &= ~FMODE_TEXTMODE; SET_BINARY_MODE_WITH_SEEK_CUR(fptr); fptr->encs.enc = rb_ascii8bit_encoding(); fptr->encs.enc2 = NULL; fptr->encs.ecflags = 0; fptr->encs.ecopts = Qnil; clear_codeconv(fptr); return io; } /* * call-seq: * ios.binmode -> ios * * Puts ios into binary mode. * Once a stream is in binary mode, it cannot be reset to nonbinary mode. * * - newline conversion disabled * - encoding conversion disabled * - content is treated as ASCII-8BIT * */ static VALUE rb_io_binmode_m(VALUE io) { VALUE write_io; rb_io_ascii8bit_binmode(io); write_io = GetWriteIO(io); if (write_io != io) rb_io_ascii8bit_binmode(write_io); return io; } /* * call-seq: * ios.binmode? -> true or false * * Returns true if ios is binmode. */ static VALUE rb_io_binmode_p(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); return fptr->mode & FMODE_BINMODE ? Qtrue : Qfalse; } static const char* rb_io_fmode_modestr(int fmode) { if (fmode & FMODE_APPEND) { if ((fmode & FMODE_READWRITE) == FMODE_READWRITE) { return MODE_BTMODE("a+", "ab+", "at+"); } return MODE_BTMODE("a", "ab", "at"); } switch (fmode & FMODE_READWRITE) { case FMODE_READABLE: return MODE_BTMODE("r", "rb", "rt"); case FMODE_WRITABLE: return MODE_BTMODE("w", "wb", "wt"); case FMODE_READWRITE: if (fmode & FMODE_CREATE) { return MODE_BTMODE("w+", "wb+", "wt+"); } return MODE_BTMODE("r+", "rb+", "rt+"); } rb_raise(rb_eArgError, "invalid access fmode 0x%x", fmode); return NULL; /* not reached */ } static int io_encname_bom_p(const char *name, long len) { static const char bom_prefix[] = "bom|utf-"; enum {bom_prefix_len = (int)sizeof(bom_prefix) - 1}; if (!len) { const char *p = strchr(name, ':'); len = p ? (long)(p - name) : (long)strlen(name); } return len > bom_prefix_len && STRNCASECMP(name, bom_prefix, bom_prefix_len) == 0; } int rb_io_modestr_fmode(const char *modestr) { int fmode = 0; const char *m = modestr, *p = NULL; switch (*m++) { case 'r': fmode |= FMODE_READABLE; break; case 'w': fmode |= FMODE_WRITABLE | FMODE_TRUNC | FMODE_CREATE; break; case 'a': fmode |= FMODE_WRITABLE | FMODE_APPEND | FMODE_CREATE; break; default: error: rb_raise(rb_eArgError, "invalid access mode %s", modestr); } while (*m) { switch (*m++) { case 'b': fmode |= FMODE_BINMODE; break; case 't': fmode |= FMODE_TEXTMODE; break; case '+': fmode |= FMODE_READWRITE; break; default: goto error; case ':': p = m; goto finished; } } finished: if ((fmode & FMODE_BINMODE) && (fmode & FMODE_TEXTMODE)) goto error; if (p && io_encname_bom_p(p, 0)) fmode |= FMODE_SETENC_BY_BOM; return fmode; } int rb_io_oflags_fmode(int oflags) { int fmode = 0; switch (oflags & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: fmode = FMODE_READABLE; break; case O_WRONLY: fmode = FMODE_WRITABLE; break; case O_RDWR: fmode = FMODE_READWRITE; break; } if (oflags & O_APPEND) { fmode |= FMODE_APPEND; } if (oflags & O_TRUNC) { fmode |= FMODE_TRUNC; } if (oflags & O_CREAT) { fmode |= FMODE_CREATE; } #ifdef O_BINARY if (oflags & O_BINARY) { fmode |= FMODE_BINMODE; } #endif return fmode; } static int rb_io_fmode_oflags(int fmode) { int oflags = 0; switch (fmode & FMODE_READWRITE) { case FMODE_READABLE: oflags |= O_RDONLY; break; case FMODE_WRITABLE: oflags |= O_WRONLY; break; case FMODE_READWRITE: oflags |= O_RDWR; break; } if (fmode & FMODE_APPEND) { oflags |= O_APPEND; } if (fmode & FMODE_TRUNC) { oflags |= O_TRUNC; } if (fmode & FMODE_CREATE) { oflags |= O_CREAT; } #ifdef O_BINARY if (fmode & FMODE_BINMODE) { oflags |= O_BINARY; } #endif return oflags; } int rb_io_modestr_oflags(const char *modestr) { return rb_io_fmode_oflags(rb_io_modestr_fmode(modestr)); } static const char* rb_io_oflags_modestr(int oflags) { #ifdef O_BINARY # define MODE_BINARY(a,b) ((oflags & O_BINARY) ? (b) : (a)) #else # define MODE_BINARY(a,b) (a) #endif int accmode = oflags & (O_RDONLY|O_WRONLY|O_RDWR); if (oflags & O_APPEND) { if (accmode == O_WRONLY) { return MODE_BINARY("a", "ab"); } if (accmode == O_RDWR) { return MODE_BINARY("a+", "ab+"); } } switch (oflags & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: return MODE_BINARY("r", "rb"); case O_WRONLY: return MODE_BINARY("w", "wb"); case O_RDWR: return MODE_BINARY("r+", "rb+"); } rb_raise(rb_eArgError, "invalid access oflags 0x%x", oflags); return NULL; /* not reached */ } /* * Convert external/internal encodings to enc/enc2 * NULL => use default encoding * Qnil => no encoding specified (internal only) */ static void rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc, rb_encoding **enc2) { int default_ext = 0; if (ext == NULL) { ext = rb_default_external_encoding(); default_ext = 1; } if (intern == NULL && ext != rb_ascii8bit_encoding()) /* If external is ASCII-8BIT, no default transcoding */ intern = rb_default_internal_encoding(); if (intern == NULL || intern == (rb_encoding *)Qnil || intern == ext) { /* No internal encoding => use external + no transcoding */ *enc = (default_ext && intern != ext) ? NULL : ext; *enc2 = NULL; } else { *enc = intern; *enc2 = ext; } } static void parse_mode_enc(const char *estr, rb_encoding **enc_p, rb_encoding **enc2_p, int *fmode_p) { const char *p; char encname[ENCODING_MAXNAMELEN+1]; int idx, idx2; rb_encoding *ext_enc, *int_enc; /* parse estr as "enc" or "enc2:enc" or "enc:-" */ p = strrchr(estr, ':'); if (p) { long len = (p++) - estr; if (len == 0 || len > ENCODING_MAXNAMELEN) idx = -1; else { if (io_encname_bom_p(estr, len)) { if (fmode_p) *fmode_p |= FMODE_SETENC_BY_BOM; estr += 4; len -= 4; } memcpy(encname, estr, len); encname[len] = '\0'; estr = encname; idx = rb_enc_find_index(encname); } } else { long len = strlen(estr); if (io_encname_bom_p(estr, len)) { if (fmode_p) *fmode_p |= FMODE_SETENC_BY_BOM; estr += 4; len -= 4; memcpy(encname, estr, len); encname[len] = '\0'; estr = encname; } idx = rb_enc_find_index(estr); } if (idx >= 0) ext_enc = rb_enc_from_index(idx); else { if (idx != -2) rb_warn("Unsupported encoding %s ignored", estr); ext_enc = NULL; } int_enc = NULL; if (p) { if (*p == '-' && *(p+1) == '\0') { /* Special case - "-" => no transcoding */ int_enc = (rb_encoding *)Qnil; } else { idx2 = rb_enc_find_index(p); if (idx2 < 0) rb_warn("Unsupported encoding %s ignored", p); else if (idx2 == idx) { rb_warn("Ignoring internal encoding %s: it is identical to external encoding %s", p, estr); int_enc = (rb_encoding *)Qnil; } else int_enc = rb_enc_from_index(idx2); } } rb_io_ext_int_to_encs(ext_enc, int_enc, enc_p, enc2_p); } static void mode_enc(rb_io_t *fptr, const char *estr) { clear_codeconv(fptr); parse_mode_enc(estr, &fptr->encs.enc, &fptr->encs.enc2, NULL); } static void rb_io_mode_enc(rb_io_t *fptr, const char *modestr) { const char *p = strchr(modestr, ':'); if (p) { mode_enc(fptr, p+1); } } int rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2_p, int *fmode_p) { VALUE encoding=Qnil, extenc=Qundef, intenc=Qundef, tmp; int extracted = 0; rb_encoding *extencoding = NULL; rb_encoding *intencoding = NULL; if (!NIL_P(opt)) { VALUE v; v = rb_hash_lookup2(opt, sym_encoding, Qnil); if (v != Qnil) encoding = v; v = rb_hash_lookup2(opt, sym_extenc, Qundef); if (v != Qnil) extenc = v; v = rb_hash_lookup2(opt, sym_intenc, Qundef); if (v != Qundef) intenc = v; } if ((extenc != Qundef || intenc != Qundef) && !NIL_P(encoding)) { if (!NIL_P(ruby_verbose)) { int idx = rb_to_encoding_index(encoding); rb_warn("Ignoring encoding parameter '%s': %s_encoding is used", idx < 0 ? StringValueCStr(encoding) : rb_enc_name(rb_enc_from_index(idx)), extenc == Qundef ? "internal" : "external"); } encoding = Qnil; } if (extenc != Qundef && !NIL_P(extenc)) { extencoding = rb_to_encoding(extenc); } if (intenc != Qundef) { if (NIL_P(intenc)) { /* internal_encoding: nil => no transcoding */ intencoding = (rb_encoding *)Qnil; } else if (!NIL_P(tmp = rb_check_string_type(intenc))) { char *p = StringValueCStr(tmp); if (*p == '-' && *(p+1) == '\0') { /* Special case - "-" => no transcoding */ intencoding = (rb_encoding *)Qnil; } else { intencoding = rb_to_encoding(intenc); } } else { intencoding = rb_to_encoding(intenc); } if (extencoding == intencoding) { intencoding = (rb_encoding *)Qnil; } } if (!NIL_P(encoding)) { extracted = 1; if (!NIL_P(tmp = rb_check_string_type(encoding))) { parse_mode_enc(StringValueCStr(tmp), enc_p, enc2_p, fmode_p); } else { rb_io_ext_int_to_encs(rb_to_encoding(encoding), NULL, enc_p, enc2_p); } } else if (extenc != Qundef || intenc != Qundef) { extracted = 1; rb_io_ext_int_to_encs(extencoding, intencoding, enc_p, enc2_p); } return extracted; } typedef struct rb_io_enc_t convconfig_t; static void validate_enc_binmode(int *fmode_p, int ecflags, rb_encoding *enc, rb_encoding *enc2) { int fmode = *fmode_p; if ((fmode & FMODE_READABLE) && !enc2 && !(fmode & FMODE_BINMODE) && !rb_enc_asciicompat(enc ? enc : rb_default_external_encoding())) rb_raise(rb_eArgError, "ASCII incompatible encoding needs binmode"); if (!(fmode & FMODE_BINMODE) && (DEFAULT_TEXTMODE || (ecflags & ECONV_NEWLINE_DECORATOR_MASK))) { fmode |= DEFAULT_TEXTMODE; *fmode_p = fmode; } #if !DEFAULT_TEXTMODE else if (!(ecflags & ECONV_NEWLINE_DECORATOR_MASK)) { fmode &= ~FMODE_TEXTMODE; *fmode_p = fmode; } #endif } static void extract_binmode(VALUE opthash, int *fmode) { if (!NIL_P(opthash)) { VALUE v; v = rb_hash_aref(opthash, sym_textmode); if (!NIL_P(v) && RTEST(v)) *fmode |= FMODE_TEXTMODE; v = rb_hash_aref(opthash, sym_binmode); if (!NIL_P(v) && RTEST(v)) *fmode |= FMODE_BINMODE; if ((*fmode & FMODE_BINMODE) && (*fmode & FMODE_TEXTMODE)) rb_raise(rb_eArgError, "both textmode and binmode specified"); } } static void rb_io_extract_modeenc(VALUE *vmode_p, VALUE *vperm_p, VALUE opthash, int *oflags_p, int *fmode_p, convconfig_t *convconfig_p) { VALUE vmode; int oflags, fmode; rb_encoding *enc, *enc2; int ecflags; VALUE ecopts; int has_enc = 0, has_vmode = 0; VALUE intmode; vmode = *vmode_p; /* Set to defaults */ rb_io_ext_int_to_encs(NULL, NULL, &enc, &enc2); vmode_handle: if (NIL_P(vmode)) { fmode = FMODE_READABLE | DEFAULT_TEXTMODE; oflags = O_RDONLY; } else if (!NIL_P(intmode = rb_check_to_integer(vmode, "to_int"))) { vmode = intmode; oflags = NUM2INT(intmode); fmode = rb_io_oflags_fmode(oflags); } else { const char *p; SafeStringValue(vmode); p = StringValueCStr(vmode); fmode = rb_io_modestr_fmode(p); oflags = rb_io_fmode_oflags(fmode); p = strchr(p, ':'); if (p) { has_enc = 1; parse_mode_enc(p+1, &enc, &enc2, &fmode); } else { rb_encoding *e; e = (fmode & FMODE_BINMODE) ? rb_ascii8bit_encoding() : NULL; rb_io_ext_int_to_encs(e, NULL, &enc, &enc2); } } if (NIL_P(opthash)) { ecflags = (fmode & FMODE_READABLE) ? MODE_BTMODE(ECONV_DEFAULT_NEWLINE_DECORATOR, 0, ECONV_UNIVERSAL_NEWLINE_DECORATOR) : 0; #ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE ecflags |= (fmode & FMODE_WRITABLE) ? MODE_BTMODE(TEXTMODE_NEWLINE_DECORATOR_ON_WRITE, 0, TEXTMODE_NEWLINE_DECORATOR_ON_WRITE) : 0; #endif SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecopts = Qnil; } else { VALUE v; extract_binmode(opthash, &fmode); #ifdef O_BINARY if (fmode & FMODE_BINMODE) oflags |= O_BINARY; #endif if (!has_vmode) { v = rb_hash_aref(opthash, sym_mode); if (!NIL_P(v)) { if (!NIL_P(vmode)) { rb_raise(rb_eArgError, "mode specified twice"); } has_vmode = 1; vmode = v; goto vmode_handle; } } v = rb_hash_aref(opthash, sym_perm); if (!NIL_P(v)) { if (vperm_p) { if (!NIL_P(*vperm_p)) { rb_raise(rb_eArgError, "perm specified twice"); } *vperm_p = v; } else { /* perm no use, just ignore */ } } ecflags = (fmode & FMODE_READABLE) ? MODE_BTMODE(ECONV_DEFAULT_NEWLINE_DECORATOR, 0, ECONV_UNIVERSAL_NEWLINE_DECORATOR) : 0; #ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE ecflags |= (fmode & FMODE_WRITABLE) ? MODE_BTMODE(TEXTMODE_NEWLINE_DECORATOR_ON_WRITE, 0, TEXTMODE_NEWLINE_DECORATOR_ON_WRITE) : 0; #endif if (rb_io_extract_encoding_option(opthash, &enc, &enc2, &fmode)) { if (has_enc) { rb_raise(rb_eArgError, "encoding specified twice"); } } SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecflags = rb_econv_prepare_options(opthash, &ecopts, ecflags); } validate_enc_binmode(&fmode, ecflags, enc, enc2); *vmode_p = vmode; *oflags_p = oflags; *fmode_p = fmode; convconfig_p->enc = enc; convconfig_p->enc2 = enc2; convconfig_p->ecflags = ecflags; convconfig_p->ecopts = ecopts; } struct sysopen_struct { VALUE fname; int oflags; mode_t perm; }; static VALUE sysopen_func(void *ptr) { const struct sysopen_struct *data = ptr; const char *fname = RSTRING_PTR(data->fname); return (VALUE)rb_cloexec_open(fname, data->oflags, data->perm); } static inline int rb_sysopen_internal(struct sysopen_struct *data) { int fd; fd = (int)rb_thread_blocking_region(sysopen_func, data, RUBY_UBF_IO, 0); if (0 <= fd) rb_update_max_fd(fd); return fd; } static int rb_sysopen(VALUE fname, int oflags, mode_t perm) { int fd; struct sysopen_struct data; data.fname = rb_str_encode_ospath(fname); data.oflags = oflags; data.perm = perm; fd = rb_sysopen_internal(&data); if (fd < 0) { if (errno == EMFILE || errno == ENFILE) { rb_gc(); fd = rb_sysopen_internal(&data); } if (fd < 0) { rb_sys_fail(RSTRING_PTR(fname)); } } return fd; } FILE * rb_fdopen(int fd, const char *modestr) { FILE *file; #if defined(__sun) errno = 0; #endif file = fdopen(fd, modestr); if (!file) { if ( #if defined(__sun) errno == 0 || #endif errno == EMFILE || errno == ENFILE) { rb_gc(); #if defined(__sun) errno = 0; #endif file = fdopen(fd, modestr); } if (!file) { #ifdef _WIN32 if (errno == 0) errno = EINVAL; #elif defined(__sun) if (errno == 0) errno = EMFILE; #endif rb_sys_fail(0); } } /* xxx: should be _IONBF? A buffer in FILE may have trouble. */ #ifdef USE_SETVBUF if (setvbuf(file, NULL, _IOFBF, 0) != 0) rb_warn("setvbuf() can't be honoured (fd=%d)", fd); #endif return file; } static void io_check_tty(rb_io_t *fptr) { if (isatty(fptr->fd)) fptr->mode |= FMODE_TTY|FMODE_DUPLEX; } static VALUE rb_io_internal_encoding(VALUE); static void io_encoding_set(rb_io_t *, VALUE, VALUE, VALUE); static int io_strip_bom(VALUE io) { int b1, b2, b3, b4; switch (b1 = FIX2INT(rb_io_getbyte(io))) { case 0xEF: b2 = FIX2INT(rb_io_getbyte(io)); if (b2 == 0xBB) { b3 = FIX2INT(rb_io_getbyte(io)); if (b3 == 0xBF) { return rb_utf8_encindex(); } rb_io_ungetbyte(io, INT2FIX(b3)); } rb_io_ungetbyte(io, INT2FIX(b2)); break; case 0xFE: b2 = FIX2INT(rb_io_getbyte(io)); if (b2 == 0xFF) { return rb_enc_find_index("UTF-16BE"); } rb_io_ungetbyte(io, INT2FIX(b2)); break; case 0xFF: b2 = FIX2INT(rb_io_getbyte(io)); if (b2 == 0xFE) { b3 = FIX2INT(rb_io_getbyte(io)); if (b3 == 0) { b4 = FIX2INT(rb_io_getbyte(io)); if (b4 == 0) { return rb_enc_find_index("UTF-32LE"); } rb_io_ungetbyte(io, INT2FIX(b4)); } else { rb_io_ungetbyte(io, INT2FIX(b3)); return rb_enc_find_index("UTF-16LE"); } rb_io_ungetbyte(io, INT2FIX(b3)); } rb_io_ungetbyte(io, INT2FIX(b2)); break; case 0: b2 = FIX2INT(rb_io_getbyte(io)); if (b2 == 0) { b3 = FIX2INT(rb_io_getbyte(io)); if (b3 == 0xFE) { b4 = FIX2INT(rb_io_getbyte(io)); if (b4 == 0xFF) { return rb_enc_find_index("UTF-32BE"); } rb_io_ungetbyte(io, INT2FIX(b4)); } rb_io_ungetbyte(io, INT2FIX(b3)); } rb_io_ungetbyte(io, INT2FIX(b2)); break; } rb_io_ungetbyte(io, INT2FIX(b1)); return 0; } static void io_set_encoding_by_bom(VALUE io) { int idx = io_strip_bom(io); if (idx) { rb_io_t *fptr; GetOpenFile(io, fptr); io_encoding_set(fptr, rb_enc_from_encoding(rb_enc_from_index(idx)), rb_io_internal_encoding(io), Qnil); } } static VALUE rb_file_open_generic(VALUE io, VALUE filename, int oflags, int fmode, convconfig_t *convconfig, mode_t perm) { rb_io_t *fptr; convconfig_t cc; if (!convconfig) { /* Set to default encodings */ rb_io_ext_int_to_encs(NULL, NULL, &cc.enc, &cc.enc2); cc.ecflags = 0; cc.ecopts = Qnil; convconfig = &cc; } validate_enc_binmode(&fmode, convconfig->ecflags, convconfig->enc, convconfig->enc2); MakeOpenFile(io, fptr); fptr->mode = fmode; fptr->encs = *convconfig; fptr->pathv = rb_str_new_frozen(filename); fptr->fd = rb_sysopen(fptr->pathv, oflags, perm); io_check_tty(fptr); if (fmode & FMODE_SETENC_BY_BOM) io_set_encoding_by_bom(io); return io; } static VALUE rb_file_open_internal(VALUE io, VALUE filename, const char *modestr) { int fmode = rb_io_modestr_fmode(modestr); const char *p = strchr(modestr, ':'); convconfig_t convconfig; if (p) { parse_mode_enc(p+1, &convconfig.enc, &convconfig.enc2, &fmode); } else { rb_encoding *e; /* Set to default encodings */ e = (fmode & FMODE_BINMODE) ? rb_ascii8bit_encoding() : NULL; rb_io_ext_int_to_encs(e, NULL, &convconfig.enc, &convconfig.enc2); convconfig.ecflags = 0; convconfig.ecopts = Qnil; } return rb_file_open_generic(io, filename, rb_io_fmode_oflags(fmode), fmode, &convconfig, 0666); } VALUE rb_file_open_str(VALUE fname, const char *modestr) { FilePathValue(fname); return rb_file_open_internal(io_alloc(rb_cFile), fname, modestr); } VALUE rb_file_open(const char *fname, const char *modestr) { return rb_file_open_internal(io_alloc(rb_cFile), rb_str_new_cstr(fname), modestr); } #if defined(__CYGWIN__) || !defined(HAVE_FORK) static struct pipe_list { rb_io_t *fptr; struct pipe_list *next; } *pipe_list; static void pipe_add_fptr(rb_io_t *fptr) { struct pipe_list *list; list = ALLOC(struct pipe_list); list->fptr = fptr; list->next = pipe_list; pipe_list = list; } static void pipe_del_fptr(rb_io_t *fptr) { struct pipe_list *list = pipe_list; struct pipe_list *tmp; if (list->fptr == fptr) { pipe_list = list->next; free(list); return; } while (list->next) { if (list->next->fptr == fptr) { tmp = list->next; list->next = list->next->next; free(tmp); return; } list = list->next; } } static void pipe_atexit(void) { struct pipe_list *list = pipe_list; struct pipe_list *tmp; while (list) { tmp = list->next; rb_io_fptr_finalize(list->fptr); list = tmp; } } static void pipe_finalize(rb_io_t *fptr, int noraise) { #if !defined(HAVE_FORK) && !defined(_WIN32) int status = 0; if (fptr->stdio_file) { status = pclose(fptr->stdio_file); } fptr->fd = -1; fptr->stdio_file = 0; rb_last_status_set(status, fptr->pid); #else fptr_finalize(fptr, noraise); #endif pipe_del_fptr(fptr); } #endif void rb_io_synchronized(rb_io_t *fptr) { rb_io_check_initialized(fptr); fptr->mode |= FMODE_SYNC; } void rb_io_unbuffered(rb_io_t *fptr) { rb_io_synchronized(fptr); } int rb_pipe(int *pipes) { int ret; ret = rb_cloexec_pipe(pipes); if (ret == -1) { if (errno == EMFILE || errno == ENFILE) { rb_gc(); ret = rb_cloexec_pipe(pipes); } } if (ret == 0) { rb_update_max_fd(pipes[0]); rb_update_max_fd(pipes[1]); } return ret; } #ifdef HAVE_FORK struct popen_arg { struct rb_exec_arg *execp; int modef; int pair[2]; int write_pair[2]; }; static void popen_redirect(struct popen_arg *p) { if ((p->modef & FMODE_READABLE) && (p->modef & FMODE_WRITABLE)) { close(p->write_pair[1]); if (p->write_pair[0] != 0) { dup2(p->write_pair[0], 0); close(p->write_pair[0]); } close(p->pair[0]); if (p->pair[1] != 1) { dup2(p->pair[1], 1); close(p->pair[1]); } } else if (p->modef & FMODE_READABLE) { close(p->pair[0]); if (p->pair[1] != 1) { dup2(p->pair[1], 1); close(p->pair[1]); } } else { close(p->pair[1]); if (p->pair[0] != 0) { dup2(p->pair[0], 0); close(p->pair[0]); } } } #if defined(__linux__) /* Linux /proc/self/status contains a line: "FDSize:\t\n" * Since /proc may not be available, linux_get_maxfd is just a hint. * This function, linux_get_maxfd, must be async-signal-safe. * I.e. opendir() is not usable. * * Note that memchr() and memcmp is *not* async-signal-safe in POSIX. * However they are easy to re-implement in async-signal-safe manner. * (Also note that there is missing/memcmp.c.) */ static int linux_get_maxfd(void) { int fd; char buf[4096], *p, *np, *e; ssize_t ss; fd = rb_cloexec_open("/proc/self/status", O_RDONLY|O_NOCTTY, 0); if (fd == -1) return -1; ss = read(fd, buf, sizeof(buf)); if (ss == -1) goto err; p = buf; e = buf + ss; while ((int)sizeof("FDSize:\t0\n")-1 <= e-p && (np = memchr(p, '\n', e-p)) != NULL) { if (memcmp(p, "FDSize:", sizeof("FDSize:")-1) == 0) { int fdsize; p += sizeof("FDSize:")-1; *np = '\0'; fdsize = (int)ruby_strtoul(p, (char **)NULL, 10); close(fd); return fdsize; } p = np+1; } /* fall through */ err: close(fd); return -1; } #endif void rb_close_before_exec(int lowfd, int maxhint, VALUE noclose_fds) { int fd, ret; int max = max_file_descriptor; #ifdef F_MAXFD /* F_MAXFD is available since NetBSD 2.0. */ ret = fcntl(0, F_MAXFD); if (ret != -1) maxhint = max = ret; #elif defined(__linux__) ret = linux_get_maxfd(); if (maxhint < ret) maxhint = ret; /* maxhint = max = ret; if (ret == -1) abort(); // test */ #endif if (max < maxhint) max = maxhint; for (fd = lowfd; fd <= max; fd++) { if (!NIL_P(noclose_fds) && RTEST(rb_hash_lookup(noclose_fds, INT2FIX(fd)))) continue; #ifdef FD_CLOEXEC ret = fcntl(fd, F_GETFD); if (ret != -1 && !(ret & FD_CLOEXEC)) { fcntl(fd, F_SETFD, ret|FD_CLOEXEC); } #else ret = close(fd); #endif #define CONTIGUOUS_CLOSED_FDS 20 if (ret != -1) { if (max < fd + CONTIGUOUS_CLOSED_FDS) max = fd + CONTIGUOUS_CLOSED_FDS; } } } static int popen_exec(void *pp, char *errmsg, size_t errmsg_len) { struct popen_arg *p = (struct popen_arg*)pp; rb_thread_atfork_before_exec(); return rb_exec_err(p->execp, errmsg, errmsg_len); } #endif static VALUE pipe_open(struct rb_exec_arg *eargp, VALUE prog, const char *modestr, int fmode, convconfig_t *convconfig) { rb_pid_t pid = 0; rb_io_t *fptr; VALUE port; rb_io_t *write_fptr; VALUE write_port; #if defined(HAVE_FORK) int status; struct popen_arg arg; char errmsg[80] = { '\0' }; #elif defined(_WIN32) volatile VALUE argbuf; char **args = NULL; int pair[2], write_pair[2]; #endif #if !defined(HAVE_FORK) struct rb_exec_arg sarg; #endif FILE *fp = 0; int fd = -1; int write_fd = -1; const char *cmd = 0; #if !defined(HAVE_FORK) int argc; VALUE *argv; #endif if (prog) cmd = StringValueCStr(prog); #if !defined(HAVE_FORK) if (!eargp) { /* fork : IO.popen("-") */ argc = 0; argv = 0; } else if (eargp->argc) { /* no shell : IO.popen([prog, arg0], arg1, ...) */ argc = eargp->argc; argv = eargp->argv; } else { /* with shell : IO.popen(prog) */ argc = 0; argv = 0; } #endif #if defined(HAVE_FORK) arg.execp = eargp; arg.modef = fmode; arg.pair[0] = arg.pair[1] = -1; arg.write_pair[0] = arg.write_pair[1] = -1; switch (fmode & (FMODE_READABLE|FMODE_WRITABLE)) { case FMODE_READABLE|FMODE_WRITABLE: if (rb_pipe(arg.write_pair) < 0) rb_sys_fail(cmd); if (rb_pipe(arg.pair) < 0) { int e = errno; close(arg.write_pair[0]); close(arg.write_pair[1]); errno = e; rb_sys_fail(cmd); } if (eargp) { rb_exec_arg_addopt(eargp, INT2FIX(0), INT2FIX(arg.write_pair[0])); rb_exec_arg_addopt(eargp, INT2FIX(1), INT2FIX(arg.pair[1])); } break; case FMODE_READABLE: if (rb_pipe(arg.pair) < 0) rb_sys_fail(cmd); if (eargp) rb_exec_arg_addopt(eargp, INT2FIX(1), INT2FIX(arg.pair[1])); break; case FMODE_WRITABLE: if (rb_pipe(arg.pair) < 0) rb_sys_fail(cmd); if (eargp) rb_exec_arg_addopt(eargp, INT2FIX(0), INT2FIX(arg.pair[0])); break; default: rb_sys_fail(cmd); } if (eargp) { rb_exec_arg_fixup(arg.execp); pid = rb_fork_err(&status, popen_exec, &arg, arg.execp->redirect_fds, errmsg, sizeof(errmsg)); } else { pid = rb_fork(&status, 0, 0, Qnil); if (pid == 0) { /* child */ rb_thread_atfork(); popen_redirect(&arg); rb_io_synchronized(RFILE(orig_stdout)->fptr); rb_io_synchronized(RFILE(orig_stderr)->fptr); return Qnil; } } /* parent */ if (pid == -1) { int e = errno; close(arg.pair[0]); close(arg.pair[1]); if ((fmode & (FMODE_READABLE|FMODE_WRITABLE)) == (FMODE_READABLE|FMODE_WRITABLE)) { close(arg.write_pair[0]); close(arg.write_pair[1]); } errno = e; if (errmsg[0]) rb_sys_fail(errmsg); rb_sys_fail(cmd); } if ((fmode & FMODE_READABLE) && (fmode & FMODE_WRITABLE)) { close(arg.pair[1]); fd = arg.pair[0]; close(arg.write_pair[0]); write_fd = arg.write_pair[1]; } else if (fmode & FMODE_READABLE) { close(arg.pair[1]); fd = arg.pair[0]; } else { close(arg.pair[0]); fd = arg.pair[1]; } #elif defined(_WIN32) if (argc) { int i; if (argc >= (int)(FIXNUM_MAX / sizeof(char *))) { rb_raise(rb_eArgError, "too many arguments"); } argbuf = rb_str_tmp_new((argc+1) * sizeof(char *)); args = (void *)RSTRING_PTR(argbuf); for (i = 0; i < argc; ++i) { args[i] = StringValueCStr(argv[i]); } args[i] = NULL; } switch (fmode & (FMODE_READABLE|FMODE_WRITABLE)) { case FMODE_READABLE|FMODE_WRITABLE: if (rb_pipe(write_pair) < 0) rb_sys_fail(cmd); if (rb_pipe(pair) < 0) { int e = errno; close(write_pair[0]); close(write_pair[1]); errno = e; rb_sys_fail(cmd); } if (eargp) { rb_exec_arg_addopt(eargp, INT2FIX(0), INT2FIX(write_pair[0])); rb_exec_arg_addopt(eargp, INT2FIX(1), INT2FIX(pair[1])); } break; case FMODE_READABLE: if (rb_pipe(pair) < 0) rb_sys_fail(cmd); if (eargp) rb_exec_arg_addopt(eargp, INT2FIX(1), INT2FIX(pair[1])); break; case FMODE_WRITABLE: if (rb_pipe(pair) < 0) rb_sys_fail(cmd); if (eargp) rb_exec_arg_addopt(eargp, INT2FIX(0), INT2FIX(pair[0])); break; default: rb_sys_fail(cmd); } if (eargp) { rb_exec_arg_fixup(eargp); rb_run_exec_options(eargp, &sarg); } while ((pid = (args ? rb_w32_aspawn(P_NOWAIT, cmd, args) : rb_w32_spawn(P_NOWAIT, cmd, 0))) == -1) { /* exec failed */ switch (errno) { case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif rb_thread_sleep(1); break; default: { int e = errno; if (eargp) rb_run_exec_options(&sarg, NULL); close(pair[0]); close(pair[1]); if ((fmode & (FMODE_READABLE|FMODE_WRITABLE)) == (FMODE_READABLE|FMODE_WRITABLE)) { close(write_pair[0]); close(write_pair[1]); } errno = e; rb_sys_fail(cmd); } break; } } RB_GC_GUARD(argbuf); if (eargp) rb_run_exec_options(&sarg, NULL); if ((fmode & FMODE_READABLE) && (fmode & FMODE_WRITABLE)) { close(pair[1]); fd = pair[0]; close(write_pair[0]); write_fd = write_pair[1]; } else if (fmode & FMODE_READABLE) { close(pair[1]); fd = pair[0]; } else { close(pair[0]); fd = pair[1]; } #else if (argc) { prog = rb_ary_join(rb_ary_new4(argc, argv), rb_str_new2(" ")); cmd = StringValueCStr(prog); } if (eargp) { rb_exec_arg_fixup(eargp); rb_run_exec_options(eargp, &sarg); } fp = popen(cmd, modestr); if (eargp) rb_run_exec_options(&sarg, NULL); if (!fp) rb_sys_fail(RSTRING_PTR(prog)); fd = fileno(fp); #endif port = io_alloc(rb_cIO); MakeOpenFile(port, fptr); fptr->fd = fd; fptr->stdio_file = fp; fptr->mode = fmode | FMODE_SYNC|FMODE_DUPLEX; if (convconfig) { fptr->encs = *convconfig; #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) if (fptr->encs.ecflags & ECONV_DEFAULT_NEWLINE_DECORATOR) { fptr->encs.ecflags |= ECONV_UNIVERSAL_NEWLINE_DECORATOR; } #endif } else { if (NEED_NEWLINE_DECORATOR_ON_READ(fptr)) { fptr->encs.ecflags |= ECONV_UNIVERSAL_NEWLINE_DECORATOR; } #ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE if (NEED_NEWLINE_DECORATOR_ON_WRITE(fptr)) { fptr->encs.ecflags |= TEXTMODE_NEWLINE_DECORATOR_ON_WRITE; } #endif } fptr->pid = pid; if (0 <= write_fd) { write_port = io_alloc(rb_cIO); MakeOpenFile(write_port, write_fptr); write_fptr->fd = write_fd; write_fptr->mode = (fmode & ~FMODE_READABLE)| FMODE_SYNC|FMODE_DUPLEX; fptr->mode &= ~FMODE_WRITABLE; fptr->tied_io_for_writing = write_port; rb_ivar_set(port, rb_intern("@tied_io_for_writing"), write_port); } #if defined (__CYGWIN__) || !defined(HAVE_FORK) fptr->finalize = pipe_finalize; pipe_add_fptr(fptr); #endif return port; } static VALUE pipe_open_v(int argc, VALUE *argv, const char *modestr, int fmode, convconfig_t *convconfig) { VALUE prog; struct rb_exec_arg earg; prog = rb_exec_arg_init(argc, argv, FALSE, &earg); return pipe_open(&earg, prog, modestr, fmode, convconfig); } static VALUE pipe_open_s(VALUE prog, const char *modestr, int fmode, convconfig_t *convconfig) { const char *cmd = RSTRING_PTR(prog); int argc = 1; VALUE *argv = &prog; struct rb_exec_arg earg; if (RSTRING_LEN(prog) == 1 && cmd[0] == '-') { #if !defined(HAVE_FORK) rb_raise(rb_eNotImpError, "fork() function is unimplemented on this machine"); #endif return pipe_open(0, 0, modestr, fmode, convconfig); } rb_exec_arg_init(argc, argv, TRUE, &earg); return pipe_open(&earg, prog, modestr, fmode, convconfig); } /* * call-seq: * IO.popen(cmd, mode="r" [, opt]) -> io * IO.popen(cmd, mode="r" [, opt]) {|io| block } -> obj * * Runs the specified command as a subprocess; the subprocess's * standard input and output will be connected to the returned * IO object. * * The PID of the started process can be obtained by IO#pid method. * * _cmd_ is a string or an array as follows. * * cmd: * "-" : fork * commandline : command line string which is passed to a shell * [env, cmdname, arg1, ..., opts] : command name and zero or more arguments (no shell) * [env, [cmdname, argv0], arg1, ..., opts] : command name, argv[0] and zero or more arguments (no shell) * (env and opts are optional.) * * If _cmd_ is a +String+ ``-'', * then a new instance of Ruby is started as the subprocess. * * If cmd is an +Array+ of +String+, * then it will be used as the subprocess's +argv+ bypassing a shell. * The array can contains a hash at first for environments and * a hash at last for options similar to spawn. * * The default mode for the new file object is ``r'', * but mode may be set to any of the modes listed in the description for class IO. * The last argument opt qualifies mode. * * # set IO encoding * IO.popen("nkf -e filename", :external_encoding=>"EUC-JP") {|nkf_io| * euc_jp_string = nkf_io.read * } * * # merge standard output and standard error using * # spawn option. See the document of Kernel.spawn. * IO.popen(["ls", "/", :err=>[:child, :out]]) {|ls_io| * ls_result_with_error = ls_io.read * } * * Raises exceptions which IO.pipe and * Kernel.spawn raise. * * If a block is given, Ruby will run the command as a child connected * to Ruby with a pipe. Ruby's end of the pipe will be passed as a * parameter to the block. * At the end of block, Ruby close the pipe and sets $?. * In this case IO.popen returns * the value of the block. * * If a block is given with a _cmd_ of ``-'', * the block will be run in two separate processes: once in the parent, * and once in a child. The parent process will be passed the pipe * object as a parameter to the block, the child version of the block * will be passed nil, and the child's standard in and * standard out will be connected to the parent through the pipe. Not * available on all platforms. * * f = IO.popen("uname") * p f.readlines * f.close * puts "Parent is #{Process.pid}" * IO.popen("date") { |f| puts f.gets } * IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f.inspect}"} * p $? * IO.popen(%w"sed -e s|^|| -e s&$&;zot;&", "r+") {|f| * f.puts "bar"; f.close_write; puts f.gets * } * * produces: * * ["Linux\n"] * Parent is 21346 * Thu Jan 15 22:41:19 JST 2009 * 21346 is here, f is # * 21352 is here, f is nil * # * bar;zot; */ static VALUE rb_io_s_popen(int argc, VALUE *argv, VALUE klass) { const char *modestr; VALUE pname, pmode, port, tmp, opt; int oflags, fmode; convconfig_t convconfig; argc = rb_scan_args(argc, argv, "11:", &pname, &pmode, &opt); rb_io_extract_modeenc(&pmode, 0, opt, &oflags, &fmode, &convconfig); modestr = rb_io_oflags_modestr(oflags); tmp = rb_check_array_type(pname); if (!NIL_P(tmp)) { long len = RARRAY_LEN(tmp); #if SIZEOF_LONG > SIZEOF_INT if (len > INT_MAX) { rb_raise(rb_eArgError, "too many arguments"); } #endif tmp = rb_ary_dup(tmp); RBASIC(tmp)->klass = 0; port = pipe_open_v((int)len, RARRAY_PTR(tmp), modestr, fmode, &convconfig); rb_ary_clear(tmp); } else { SafeStringValue(pname); port = pipe_open_s(pname, modestr, fmode, &convconfig); } if (NIL_P(port)) { /* child */ if (rb_block_given_p()) { rb_yield(Qnil); rb_io_flush(rb_stdout); rb_io_flush(rb_stderr); _exit(0); } return Qnil; } RBASIC(port)->klass = klass; if (rb_block_given_p()) { return rb_ensure(rb_yield, port, io_close, port); } return port; } static void rb_scan_open_args(int argc, VALUE *argv, VALUE *fname_p, int *oflags_p, int *fmode_p, convconfig_t *convconfig_p, mode_t *perm_p) { VALUE opt, fname, vmode, vperm; int oflags, fmode; mode_t perm; argc = rb_scan_args(argc, argv, "12:", &fname, &vmode, &vperm, &opt); FilePathValue(fname); rb_io_extract_modeenc(&vmode, &vperm, opt, &oflags, &fmode, convconfig_p); perm = NIL_P(vperm) ? 0666 : NUM2MODET(vperm); *fname_p = fname; *oflags_p = oflags; *fmode_p = fmode; *perm_p = perm; } static VALUE rb_open_file(int argc, VALUE *argv, VALUE io) { VALUE fname; int oflags, fmode; convconfig_t convconfig; mode_t perm; rb_scan_open_args(argc, argv, &fname, &oflags, &fmode, &convconfig, &perm); rb_file_open_generic(io, fname, oflags, fmode, &convconfig, perm); return io; } /* * Document-method: File::open * * call-seq: * File.open(filename, mode="r" [, opt]) -> file * File.open(filename [, mode [, perm]] [, opt]) -> file * File.open(filename, mode="r" [, opt]) {|file| block } -> obj * File.open(filename [, mode [, perm]] [, opt]) {|file| block } -> obj * * With no associated block, File.open is a synonym for * File.new. If the optional code block is given, it will * be passed the opened +file+ as an argument, and the File object will * automatically be closed when the block terminates. In this instance, * File.open returns the value of the block. * * See IO.new for a list of values for the +opt+ parameter. */ /* * Document-method: IO::open * * call-seq: * IO.open(fd, mode_string="r" [, opt]) -> io * IO.open(fd, mode_string="r" [, opt]) {|io| block } -> obj * * With no associated block, IO.open is a synonym for IO.new. If * the optional code block is given, it will be passed +io+ as an * argument, and the IO object will automatically be closed when the block * terminates. In this instance, IO.open returns the value of the block. * * See IO.new for a description of values for the +opt+ parameter. * */ static VALUE rb_io_s_open(int argc, VALUE *argv, VALUE klass) { VALUE io = rb_class_new_instance(argc, argv, klass); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); } return io; } /* * call-seq: * IO.sysopen(path, [mode, [perm]]) -> fixnum * * Opens the given path, returning the underlying file descriptor as a * Fixnum. * * IO.sysopen("testfile") #=> 3 * */ static VALUE rb_io_s_sysopen(int argc, VALUE *argv) { VALUE fname, vmode, vperm; VALUE intmode; int oflags, fd; mode_t perm; rb_scan_args(argc, argv, "12", &fname, &vmode, &vperm); FilePathValue(fname); if (NIL_P(vmode)) oflags = O_RDONLY; else if (!NIL_P(intmode = rb_check_to_integer(vmode, "to_int"))) oflags = NUM2INT(intmode); else { SafeStringValue(vmode); oflags = rb_io_modestr_oflags(StringValueCStr(vmode)); } if (NIL_P(vperm)) perm = 0666; else perm = NUM2MODET(vperm); RB_GC_GUARD(fname) = rb_str_new4(fname); fd = rb_sysopen(fname, oflags, perm); return INT2NUM(fd); } static VALUE check_pipe_command(VALUE filename_or_command) { char *s = RSTRING_PTR(filename_or_command); long l = RSTRING_LEN(filename_or_command); char *e = s + l; int chlen; if (rb_enc_ascget(s, e, &chlen, rb_enc_get(filename_or_command)) == '|') { VALUE cmd = rb_str_new(s+chlen, l-chlen); OBJ_INFECT(cmd, filename_or_command); return cmd; } return Qnil; } /* * call-seq: * open(path [, mode_enc [, perm]] [, opt]) -> io or nil * open(path [, mode_enc [, perm]] [, opt]) {|io| block } -> obj * * Creates an IO object connected to the given stream, * file, or subprocess. * * If path does not start with a pipe character * (``|''), treat it as the name of a file to open using * the specified mode (defaulting to ``r''). * * The mode_enc is * either a string or an integer. If it is an integer, it must be * bitwise-or of open(2) flags, such as File::RDWR or File::EXCL. * If it is a string, it is either "mode", "mode:ext_enc", or * "mode:ext_enc:int_enc". * The mode is one of the following: * * r: read (default) * w: write * a: append * * The mode can be followed by "b" (means binary-mode), or "+" * (means both reading and writing allowed) or both. * If ext_enc (external encoding) is specified, * read string will be tagged by the encoding in reading, * and output string will be converted * to the specified encoding in writing. * If ext_enc starts with 'BOM|', check whether the input has a BOM. If * there is a BOM, strip it and set external encoding as * what the BOM tells. If there is no BOM, use ext_enc without 'BOM|'. * If two encoding names, * ext_enc and int_enc (external encoding and internal encoding), * are specified, the read string is converted from ext_enc * to int_enc then tagged with the int_enc in read mode, * and in write mode, the output string will be * converted from int_enc to ext_enc before writing. * * If a file is being created, its initial permissions may be * set using the integer third parameter. * * If a block is specified, it will be invoked with the * File object as a parameter, and the file will be * automatically closed when the block terminates. The call * returns the value of the block. * * If path starts with a pipe character, a subprocess is * created, connected to the caller by a pair of pipes. The returned * IO object may be used to write to the standard input * and read from the standard output of this subprocess. If the command * following the ``|'' is a single minus sign, Ruby forks, * and this subprocess is connected to the parent. In the subprocess, * the open call returns nil. If the command * is not ``-'', the subprocess runs the command. If a * block is associated with an open("|-") call, that block * will be run twice---once in the parent and once in the child. The * block parameter will be an IO object in the parent and * nil in the child. The parent's IO object * will be connected to the child's $stdin and * $stdout. The subprocess will be terminated at the end * of the block. * * open("testfile") do |f| * print f.gets * end * * produces: * * This is line one * * Open a subprocess and read its output: * * cmd = open("|date") * print cmd.gets * cmd.close * * produces: * * Wed Apr 9 08:56:31 CDT 2003 * * Open a subprocess running the same Ruby program: * * f = open("|-", "w+") * if f == nil * puts "in Child" * exit * else * puts "Got: #{f.gets}" * end * * produces: * * Got: in Child * * Open a subprocess using a block to receive the I/O object: * * open("|-") do |f| * if f == nil * puts "in Child" * else * puts "Got: #{f.gets}" * end * end * * produces: * * Got: in Child */ static VALUE rb_f_open(int argc, VALUE *argv) { ID to_open = 0; int redirect = FALSE; if (argc >= 1) { CONST_ID(to_open, "to_open"); if (rb_respond_to(argv[0], to_open)) { redirect = TRUE; } else { VALUE tmp = argv[0]; FilePathValue(tmp); if (NIL_P(tmp)) { redirect = TRUE; } else { VALUE cmd = check_pipe_command(tmp); if (!NIL_P(cmd)) { argv[0] = cmd; return rb_io_s_popen(argc, argv, rb_cIO); } } } } if (redirect) { VALUE io = rb_funcall2(argv[0], to_open, argc-1, argv+1); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); } return io; } return rb_io_s_open(argc, argv, rb_cFile); } static VALUE rb_io_open(VALUE filename, VALUE vmode, VALUE vperm, VALUE opt) { VALUE cmd; int oflags, fmode; convconfig_t convconfig; mode_t perm; rb_io_extract_modeenc(&vmode, &vperm, opt, &oflags, &fmode, &convconfig); perm = NIL_P(vperm) ? 0666 : NUM2MODET(vperm); if (!NIL_P(cmd = check_pipe_command(filename))) { return pipe_open_s(cmd, rb_io_oflags_modestr(oflags), fmode, &convconfig); } else { return rb_file_open_generic(io_alloc(rb_cFile), filename, oflags, fmode, &convconfig, perm); } } static VALUE rb_io_open_with_args(int argc, VALUE *argv) { VALUE io; io = io_alloc(rb_cFile); rb_open_file(argc, argv, io); return io; } static VALUE io_reopen(VALUE io, VALUE nfile) { rb_io_t *fptr, *orig; int fd, fd2; off_t pos = 0; nfile = rb_io_get_io(nfile); if (rb_safe_level() >= 4 && (!OBJ_UNTRUSTED(io) || !OBJ_UNTRUSTED(nfile))) { rb_raise(rb_eSecurityError, "Insecure: can't reopen"); } GetOpenFile(io, fptr); GetOpenFile(nfile, orig); if (fptr == orig) return io; if (IS_PREP_STDIO(fptr)) { if ((fptr->stdio_file == stdin && !(orig->mode & FMODE_READABLE)) || (fptr->stdio_file == stdout && !(orig->mode & FMODE_WRITABLE)) || (fptr->stdio_file == stderr && !(orig->mode & FMODE_WRITABLE))) { rb_raise(rb_eArgError, "%s can't change access mode from \"%s\" to \"%s\"", PREP_STDIO_NAME(fptr), rb_io_fmode_modestr(fptr->mode), rb_io_fmode_modestr(orig->mode)); } } if (fptr->mode & FMODE_WRITABLE) { if (io_fflush(fptr) < 0) rb_sys_fail(0); } else { io_tell(fptr); } if (orig->mode & FMODE_READABLE) { pos = io_tell(orig); } if (orig->mode & FMODE_WRITABLE) { if (io_fflush(orig) < 0) rb_sys_fail(0); } /* copy rb_io_t structure */ fptr->mode = orig->mode | (fptr->mode & FMODE_PREP); fptr->pid = orig->pid; fptr->lineno = orig->lineno; if (RTEST(orig->pathv)) fptr->pathv = orig->pathv; else if (!IS_PREP_STDIO(fptr)) fptr->pathv = Qnil; fptr->finalize = orig->finalize; #if defined (__CYGWIN__) || !defined(HAVE_FORK) if (fptr->finalize == pipe_finalize) pipe_add_fptr(fptr); #endif fd = fptr->fd; fd2 = orig->fd; if (fd != fd2) { if (IS_PREP_STDIO(fptr) || fd <= 2 || !fptr->stdio_file) { /* need to keep FILE objects of stdin, stdout and stderr */ if (rb_cloexec_dup2(fd2, fd) < 0) rb_sys_fail_path(orig->pathv); rb_update_max_fd(fd); } else { fclose(fptr->stdio_file); fptr->stdio_file = 0; fptr->fd = -1; if (rb_cloexec_dup2(fd2, fd) < 0) rb_sys_fail_path(orig->pathv); rb_update_max_fd(fd); fptr->fd = fd; } rb_thread_fd_close(fd); if ((orig->mode & FMODE_READABLE) && pos >= 0) { if (io_seek(fptr, pos, SEEK_SET) < 0 && errno) { rb_sys_fail_path(fptr->pathv); } if (io_seek(orig, pos, SEEK_SET) < 0 && errno) { rb_sys_fail_path(orig->pathv); } } } if (fptr->mode & FMODE_BINMODE) { rb_io_binmode(io); } RBASIC(io)->klass = rb_obj_class(nfile); return io; } /* * call-seq: * ios.reopen(other_IO) -> ios * ios.reopen(path, mode_str) -> ios * * Reassociates ios with the I/O stream given in * other_IO or to a new stream opened on path. This may * dynamically change the actual class of this stream. * * f1 = File.new("testfile") * f2 = File.new("testfile") * f2.readlines[0] #=> "This is line one\n" * f2.reopen(f1) #=> # * f2.readlines[0] #=> "This is line one\n" */ static VALUE rb_io_reopen(int argc, VALUE *argv, VALUE file) { VALUE fname, nmode; int oflags; rb_io_t *fptr; rb_secure(4); if (rb_scan_args(argc, argv, "11", &fname, &nmode) == 1) { VALUE tmp = rb_io_check_io(fname); if (!NIL_P(tmp)) { return io_reopen(file, tmp); } } FilePathValue(fname); rb_io_taint_check(file); fptr = RFILE(file)->fptr; if (!fptr) { fptr = RFILE(file)->fptr = ALLOC(rb_io_t); MEMZERO(fptr, rb_io_t, 1); } if (!NIL_P(nmode)) { int fmode = rb_io_modestr_fmode(StringValueCStr(nmode)); if (IS_PREP_STDIO(fptr) && ((fptr->mode & FMODE_READWRITE) & (fmode & FMODE_READWRITE)) != (fptr->mode & FMODE_READWRITE)) { rb_raise(rb_eArgError, "%s can't change access mode from \"%s\" to \"%s\"", PREP_STDIO_NAME(fptr), rb_io_fmode_modestr(fptr->mode), rb_io_fmode_modestr(fmode)); } fptr->mode = fmode; rb_io_mode_enc(fptr, StringValueCStr(nmode)); fptr->encs.ecflags = 0; fptr->encs.ecopts = Qnil; } fptr->pathv = rb_str_new_frozen(fname); oflags = rb_io_fmode_oflags(fptr->mode); if (fptr->fd < 0) { fptr->fd = rb_sysopen(fptr->pathv, oflags, 0666); fptr->stdio_file = 0; return file; } if (fptr->mode & FMODE_WRITABLE) { if (io_fflush(fptr) < 0) rb_sys_fail(0); } fptr->rbuf.off = fptr->rbuf.len = 0; if (fptr->stdio_file) { if (freopen(RSTRING_PTR(fptr->pathv), rb_io_oflags_modestr(oflags), fptr->stdio_file) == 0) { rb_sys_fail_path(fptr->pathv); } fptr->fd = fileno(fptr->stdio_file); rb_fd_fix_cloexec(fptr->fd); #ifdef USE_SETVBUF if (setvbuf(fptr->stdio_file, NULL, _IOFBF, 0) != 0) rb_warn("setvbuf() can't be honoured for %s", RSTRING_PTR(fptr->pathv)); #endif if (fptr->stdio_file == stderr) { if (setvbuf(fptr->stdio_file, NULL, _IONBF, BUFSIZ) != 0) rb_warn("setvbuf() can't be honoured for %s", RSTRING_PTR(fptr->pathv)); } else if (fptr->stdio_file == stdout && isatty(fptr->fd)) { if (setvbuf(fptr->stdio_file, NULL, _IOLBF, BUFSIZ) != 0) rb_warn("setvbuf() can't be honoured for %s", RSTRING_PTR(fptr->pathv)); } } else { if (close(fptr->fd) < 0) rb_sys_fail_path(fptr->pathv); fptr->fd = -1; fptr->fd = rb_sysopen(fptr->pathv, oflags, 0666); } return file; } /* :nodoc: */ static VALUE rb_io_init_copy(VALUE dest, VALUE io) { rb_io_t *fptr, *orig; int fd; VALUE write_io; off_t pos; io = rb_io_get_io(io); if (dest == io) return dest; GetOpenFile(io, orig); MakeOpenFile(dest, fptr); rb_io_flush(io); /* copy rb_io_t structure */ fptr->mode = orig->mode & ~FMODE_PREP; fptr->encs = orig->encs; fptr->pid = orig->pid; fptr->lineno = orig->lineno; if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv; fptr->finalize = orig->finalize; #if defined (__CYGWIN__) || !defined(HAVE_FORK) if (fptr->finalize == pipe_finalize) pipe_add_fptr(fptr); #endif fd = ruby_dup(orig->fd); fptr->fd = fd; pos = io_tell(orig); if (0 <= pos) io_seek(fptr, pos, SEEK_SET); if (fptr->mode & FMODE_BINMODE) { rb_io_binmode(dest); } write_io = GetWriteIO(io); if (io != write_io) { write_io = rb_obj_dup(write_io); fptr->tied_io_for_writing = write_io; rb_ivar_set(dest, rb_intern("@tied_io_for_writing"), write_io); } return dest; } /* * call-seq: * ios.printf(format_string [, obj, ...]) -> nil * * Formats and writes to ios, converting parameters under * control of the format string. See Kernel#sprintf * for details. */ VALUE rb_io_printf(int argc, VALUE *argv, VALUE out) { rb_io_write(out, rb_f_sprintf(argc, argv)); return Qnil; } /* * call-seq: * printf(io, string [, obj ... ]) -> nil * printf(string [, obj ... ]) -> nil * * Equivalent to: * io.write(sprintf(string, obj, ...) * or * $stdout.write(sprintf(string, obj, ...) */ static VALUE rb_f_printf(int argc, VALUE *argv) { VALUE out; if (argc == 0) return Qnil; if (TYPE(argv[0]) == T_STRING) { out = rb_stdout; } else { out = argv[0]; argv++; argc--; } rb_io_write(out, rb_f_sprintf(argc, argv)); return Qnil; } /* * call-seq: * ios.print() -> nil * ios.print(obj, ...) -> nil * * Writes the given object(s) to ios. The stream must be * opened for writing. If the output field separator ($,) * is not nil, it will be inserted between each object. * If the output record separator ($\\) * is not nil, it will be appended to the output. If no * arguments are given, prints $_. Objects that aren't * strings will be converted by calling their to_s method. * With no argument, prints the contents of the variable $_. * Returns nil. * * $stdout.print("This is ", 100, " percent.\n") * * produces: * * This is 100 percent. */ VALUE rb_io_print(int argc, VALUE *argv, VALUE out) { int i; VALUE line; /* if no argument given, print `$_' */ if (argc == 0) { argc = 1; line = rb_lastline_get(); argv = &line; } for (i=0; i0) { rb_io_write(out, rb_output_fs); } rb_io_write(out, argv[i]); } if (argc > 0 && !NIL_P(rb_output_rs)) { rb_io_write(out, rb_output_rs); } return Qnil; } /* * call-seq: * print(obj, ...) -> nil * * Prints each object in turn to $stdout. If the output * field separator ($,) is not +nil+, its * contents will appear between each field. If the output record * separator ($\\) is not +nil+, it will be * appended to the output. If no arguments are given, prints * $_. Objects that aren't strings will be converted by * calling their to_s method. * * print "cat", [1,2,3], 99, "\n" * $, = ", " * $\ = "\n" * print "cat", [1,2,3], 99 * * produces: * * cat12399 * cat, 1, 2, 3, 99 */ static VALUE rb_f_print(int argc, VALUE *argv) { rb_io_print(argc, argv, rb_stdout); return Qnil; } /* * call-seq: * ios.putc(obj) -> obj * * If obj is Numeric, write the character whose code is * the least-significant byte of obj, otherwise write the first byte * of the string representation of obj to ios. Note: This * method is not safe for use with multi-byte characters as it will truncate * them. * * $stdout.putc "A" * $stdout.putc 65 * * produces: * * AA */ static VALUE rb_io_putc(VALUE io, VALUE ch) { VALUE str; if (RB_TYPE_P(ch, T_STRING)) { str = rb_str_substr(ch, 0, 1); } else { char c = NUM2CHR(ch); str = rb_str_new(&c, 1); } rb_io_write(io, str); return ch; } /* * call-seq: * putc(int) -> int * * Equivalent to: * * $stdout.putc(int) * * Refer to the documentation for IO#putc for important information regarding * multi-byte characters. */ static VALUE rb_f_putc(VALUE recv, VALUE ch) { if (recv == rb_stdout) { return rb_io_putc(recv, ch); } return rb_funcall2(rb_stdout, rb_intern("putc"), 1, &ch); } static int str_end_with_asciichar(VALUE str, int c) { long len = RSTRING_LEN(str); const char *ptr = RSTRING_PTR(str); rb_encoding *enc = rb_enc_from_index(ENCODING_GET(str)); int n; if (len == 0) return 0; if ((n = rb_enc_mbminlen(enc)) == 1) { return ptr[len - 1] == c; } return rb_enc_ascget(ptr + ((len - 1) / n) * n, ptr + len, &n, enc) == c; } static VALUE io_puts_ary(VALUE ary, VALUE out, int recur) { VALUE tmp; long i; if (recur) { tmp = rb_str_new2("[...]"); rb_io_puts(1, &tmp, out); return Qnil; } for (i=0; i nil * * Writes the given objects to ios as with * IO#print. Writes a record separator (typically a * newline) after any that do not already end with a newline sequence. * If called with an array argument, writes each element on a new line. * If called without arguments, outputs a single record separator. * * $stdout.puts("this", "is", "a", "test") * * produces: * * this * is * a * test */ VALUE rb_io_puts(int argc, VALUE *argv, VALUE out) { int i; VALUE line; /* if no argument given, print newline. */ if (argc == 0) { rb_io_write(out, rb_default_rs); return Qnil; } for (i=0; i nil * * Equivalent to * * $stdout.puts(obj, ...) */ static VALUE rb_f_puts(int argc, VALUE *argv, VALUE recv) { if (recv == rb_stdout) { return rb_io_puts(argc, argv, recv); } return rb_funcall2(rb_stdout, rb_intern("puts"), argc, argv); } void rb_p(VALUE obj) /* for debug print within C code */ { VALUE str = rb_obj_as_string(rb_inspect(obj)); if (RB_TYPE_P(rb_stdout, T_FILE) && rb_method_basic_definition_p(CLASS_OF(rb_stdout), id_write)) { io_write(rb_stdout, str, 1); io_write(rb_stdout, rb_default_rs, 0); } else { rb_io_write(rb_stdout, str); rb_io_write(rb_stdout, rb_default_rs); } } /* * call-seq: * p(obj) -> obj * p(obj1, obj2, ...) -> [obj, ...] * p() -> nil * * For each object, directly writes _obj_.+inspect+ followed by a * newline to the program's standard output. * * S = Struct.new(:name, :state) * s = S['dave', 'TX'] * p s * * produces: * * # */ static VALUE rb_f_p(int argc, VALUE *argv, VALUE self) { int i; VALUE ret = Qnil; for (i=0; i 1) { ret = rb_ary_new4(argc, argv); } if (RB_TYPE_P(rb_stdout, T_FILE)) { rb_io_flush(rb_stdout); } return ret; } /* * call-seq: * obj.display(port=$>) -> nil * * Prints obj on the given port (default $>). * Equivalent to: * * def display(port=$>) * port.write self * end * * For example: * * 1.display * "cat".display * [ 4, 5, 6 ].display * puts * * produces: * * 1cat456 */ static VALUE rb_obj_display(int argc, VALUE *argv, VALUE self) { VALUE out; if (argc == 0) { out = rb_stdout; } else { rb_scan_args(argc, argv, "01", &out); } rb_io_write(out, self); return Qnil; } void rb_write_error2(const char *mesg, long len) { if (rb_stderr == orig_stderr || RFILE(orig_stderr)->fptr->fd < 0) { if (fwrite(mesg, sizeof(char), (size_t)len, stderr) < (size_t)len) { /* failed to write to stderr, what can we do? */ return; } } else { rb_io_write(rb_stderr, rb_str_new(mesg, len)); } } void rb_write_error(const char *mesg) { rb_write_error2(mesg, strlen(mesg)); } static void must_respond_to(ID mid, VALUE val, ID id) { if (!rb_respond_to(val, mid)) { rb_raise(rb_eTypeError, "%s must have %s method, %s given", rb_id2name(id), rb_id2name(mid), rb_obj_classname(val)); } } static void stdout_setter(VALUE val, ID id, VALUE *variable) { must_respond_to(id_write, val, id); *variable = val; } static VALUE prep_io(int fd, int fmode, VALUE klass, const char *path) { rb_io_t *fp; VALUE io = io_alloc(klass); MakeOpenFile(io, fp); fp->fd = fd; #ifdef __CYGWIN__ if (!isatty(fd)) { fmode |= FMODE_BINMODE; setmode(fd, O_BINARY); } #endif fp->mode = fmode; io_check_tty(fp); if (path) fp->pathv = rb_obj_freeze(rb_str_new_cstr(path)); rb_update_max_fd(fd); return io; } VALUE rb_io_fdopen(int fd, int oflags, const char *path) { VALUE klass = rb_cIO; if (path && strcmp(path, "-")) klass = rb_cFile; return prep_io(fd, rb_io_oflags_fmode(oflags), klass, path); } static VALUE prep_stdio(FILE *f, int fmode, VALUE klass, const char *path) { rb_io_t *fptr; VALUE io = prep_io(fileno(f), fmode|FMODE_PREP|DEFAULT_TEXTMODE, klass, path); GetOpenFile(io, fptr); fptr->encs.ecflags |= ECONV_DEFAULT_NEWLINE_DECORATOR; #ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE fptr->encs.ecflags |= TEXTMODE_NEWLINE_DECORATOR_ON_WRITE; if (fmode & FMODE_READABLE) { fptr->encs.ecflags |= ECONV_UNIVERSAL_NEWLINE_DECORATOR; } #endif fptr->stdio_file = f; return io; } FILE * rb_io_stdio_file(rb_io_t *fptr) { if (!fptr->stdio_file) { int oflags = rb_io_fmode_oflags(fptr->mode); fptr->stdio_file = rb_fdopen(fptr->fd, rb_io_oflags_modestr(oflags)); } return fptr->stdio_file; } /* * call-seq: * IO.new(fd [, mode] [, opt]) -> io * * Returns a new IO object (a stream) for the given IO object or integer file * descriptor and mode string. See also IO.sysopen and IO.for_fd. * * === Parameters * * fd:: numeric file descriptor or IO object * mode:: file mode. a string or an integer * opt:: hash for specifying +mode+ by name. * * ==== Mode * * When mode is an integer it must be combination of the modes defined in * File::Constants. * * When mode is a string it must be in one of the following forms: * - "fmode", * - "fmode:extern", * - "fmode:extern:intern". * extern is the external encoding name for the IO. * intern is the internal encoding. * fmode must be a file open mode string. See the description of * class IO for mode string directives. * * When the mode of original IO is read only, the mode cannot be changed to * be writable. Similarly, the mode cannot be changed from write only to * readable. * * When such a change is attempted the error is raised in different locations * according to the platform. * * ==== Options * +opt+ can have the following keys * :mode :: * Same as +mode+ parameter * :external_encoding :: * External encoding for the IO. "-" is a synonym for the default external * encoding. * :internal_encoding :: * Internal encoding for the IO. "-" is a synonym for the default internal * encoding. * * If the value is nil no conversion occurs. * :encoding :: * Specifies external and internal encodings as "extern:intern". * :textmode :: * If the value is truth value, same as "t" in argument +mode+. * :binmode :: * If the value is truth value, same as "b" in argument +mode+. * :autoclose :: * If the value is +false+, the +fd+ will be kept open after this IO * instance gets finalized. * * Also +opt+ can have same keys in String#encode for controlling conversion * between the external encoding and the internal encoding. * * === Example 1 * * fd = IO.sysopen("/dev/tty", "w") * a = IO.new(fd,"w") * $stderr.puts "Hello" * a.puts "World" * * produces: * * Hello * World * * === Example 2 * * require 'fcntl' * * fd = STDERR.fcntl(Fcntl::F_DUPFD) * io = IO.new(fd, mode: 'w:UTF-16LE', cr_newline: true) * io.puts "Hello, World!" * * fd = STDERR.fcntl(Fcntl::F_DUPFD) * io = IO.new(fd, mode: 'w', cr_newline: true, * external_encoding: Encoding::UTF_16LE) * io.puts "Hello, World!" * * Both of above print "Hello, World!" in UTF-16LE to standard error output * with converting EOL generated by puts to CR. */ static VALUE rb_io_initialize(int argc, VALUE *argv, VALUE io) { VALUE fnum, vmode; rb_io_t *fp; int fd, fmode, oflags = O_RDONLY; convconfig_t convconfig; VALUE opt; #if defined(HAVE_FCNTL) && defined(F_GETFL) int ofmode; #else struct stat st; #endif rb_secure(4); argc = rb_scan_args(argc, argv, "11:", &fnum, &vmode, &opt); rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &fmode, &convconfig); fd = NUM2INT(fnum); if (rb_reserved_fd_p(fd)) { rb_raise(rb_eArgError, "The given fd is not accessible because RubyVM reserves it"); } #if defined(HAVE_FCNTL) && defined(F_GETFL) oflags = fcntl(fd, F_GETFL); if (oflags == -1) rb_sys_fail(0); #else if (fstat(fd, &st) == -1) rb_sys_fail(0); #endif rb_update_max_fd(fd); #if defined(HAVE_FCNTL) && defined(F_GETFL) ofmode = rb_io_oflags_fmode(oflags); if (NIL_P(vmode)) { fmode = ofmode; } else if ((~ofmode & fmode) & FMODE_READWRITE) { VALUE error = INT2FIX(EINVAL); rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError)); } #endif if (!NIL_P(opt) && rb_hash_aref(opt, sym_autoclose) == Qfalse) { fmode |= FMODE_PREP; } MakeOpenFile(io, fp); fp->fd = fd; fp->mode = fmode; fp->encs = convconfig; clear_codeconv(fp); io_check_tty(fp); if (fileno(stdin) == fd) fp->stdio_file = stdin; else if (fileno(stdout) == fd) fp->stdio_file = stdout; else if (fileno(stderr) == fd) fp->stdio_file = stderr; if (fmode & FMODE_SETENC_BY_BOM) io_set_encoding_by_bom(io); return io; } /* * call-seq: * File.new(filename, mode="r" [, opt]) -> file * File.new(filename [, mode [, perm]] [, opt]) -> file * * Opens the file named by +filename+ according to +mode+ (default is "r") * and returns a new File object. * * === Parameters * * See the description of class IO for a description of +mode+. The file * mode may optionally be specified as a Fixnum by +or+-ing together the * flags (O_RDONLY etc, again described under +IO+). * * Optional permission bits may be given in +perm+. These mode and * permission bits are platform dependent; on Unix systems, see * open(2) for details. * * Optional +opt+ parameter is same as in IO.open. * * === Examples * * f = File.new("testfile", "r") * f = File.new("newfile", "w+") * f = File.new("newfile", File::CREAT|File::TRUNC|File::RDWR, 0644) */ static VALUE rb_file_initialize(int argc, VALUE *argv, VALUE io) { if (RFILE(io)->fptr) { rb_raise(rb_eRuntimeError, "reinitializing File"); } if (0 < argc && argc < 3) { VALUE fd = rb_check_convert_type(argv[0], T_FIXNUM, "Fixnum", "to_int"); if (!NIL_P(fd)) { argv[0] = fd; return rb_io_initialize(argc, argv, io); } } rb_open_file(argc, argv, io); return io; } /* :nodoc: */ static VALUE rb_io_s_new(int argc, VALUE *argv, VALUE klass) { if (rb_block_given_p()) { const char *cname = rb_class2name(klass); rb_warn("%s::new() does not take block; use %s::open() instead", cname, cname); } return rb_class_new_instance(argc, argv, klass); } /* * call-seq: * IO.for_fd(fd, mode [, opt]) -> io * * Synonym for IO.new. * */ static VALUE rb_io_s_for_fd(int argc, VALUE *argv, VALUE klass) { VALUE io = rb_obj_alloc(klass); rb_io_initialize(argc, argv, io); return io; } /* * call-seq: * ios.autoclose? -> true or false * * Returns +true+ if the underlying file descriptor of _ios_ will be * closed automatically at its finalization, otherwise +false+. */ static VALUE rb_io_autoclose_p(VALUE io) { rb_io_t *fptr; rb_secure(4); GetOpenFile(io, fptr); return (fptr->mode & FMODE_PREP) ? Qfalse : Qtrue; } /* * call-seq: * io.autoclose = bool -> true or false * * Sets auto-close flag. * * f = open("/dev/null") * IO.for_fd(f.fileno) * # ... * f.gets # may cause IOError * * f = open("/dev/null") * IO.for_fd(f.fileno).autoclose = true * # ... * f.gets # won't cause IOError */ static VALUE rb_io_set_autoclose(VALUE io, VALUE autoclose) { rb_io_t *fptr; rb_secure(4); GetOpenFile(io, fptr); if (!RTEST(autoclose)) fptr->mode |= FMODE_PREP; else fptr->mode &= ~FMODE_PREP; return io; } static void argf_mark(void *ptr) { struct argf *p = ptr; rb_gc_mark(p->filename); rb_gc_mark(p->current_file); rb_gc_mark(p->argv); rb_gc_mark(p->encs.ecopts); } static void argf_free(void *ptr) { struct argf *p = ptr; xfree(p->inplace); xfree(p); } static size_t argf_memsize(const void *ptr) { const struct argf *p = ptr; size_t size = sizeof(*p); if (!ptr) return 0; if (p->inplace) size += strlen(p->inplace) + 1; return size; } static const rb_data_type_t argf_type = { "ARGF", {argf_mark, argf_free, argf_memsize}, }; static inline void argf_init(struct argf *p, VALUE v) { p->filename = Qnil; p->current_file = Qnil; p->lineno = 0; p->argv = v; } static VALUE argf_alloc(VALUE klass) { struct argf *p; VALUE argf = TypedData_Make_Struct(klass, struct argf, &argf_type, p); argf_init(p, Qnil); return argf; } #undef rb_argv /* :nodoc: */ static VALUE argf_initialize(VALUE argf, VALUE argv) { memset(&ARGF, 0, sizeof(ARGF)); argf_init(&ARGF, argv); return argf; } /* :nodoc: */ static VALUE argf_initialize_copy(VALUE argf, VALUE orig) { ARGF = argf_of(orig); ARGF.argv = rb_obj_dup(ARGF.argv); if (ARGF.inplace) { const char *inplace = ARGF.inplace; ARGF.inplace = 0; ARGF.inplace = ruby_strdup(inplace); } return argf; } /* * call-seq: * ARGF.lineno = integer -> integer * * Sets the line number of +ARGF+ as a whole to the given +Integer+. * * +ARGF+ sets the line number automatically as you read data, so normally * you will not need to set it explicitly. To access the current line number * use +ARGF.lineno+. * * For example: * * ARGF.lineno #=> 0 * ARGF.readline #=> "This is line 1\n" * ARGF.lineno #=> 1 * ARGF.lineno = 0 #=> 0 * ARGF.lineno #=> 0 */ static VALUE argf_set_lineno(VALUE argf, VALUE val) { ARGF.lineno = NUM2INT(val); ARGF.last_lineno = ARGF.lineno; return Qnil; } /* * call-seq: * ARGF.lineno -> integer * * Returns the current line number of ARGF as a whole. This value * can be set manually with +ARGF.lineno=+. * * For example: * * ARGF.lineno #=> 0 * ARGF.readline #=> "This is line 1\n" * ARGF.lineno #=> 1 */ static VALUE argf_lineno(VALUE argf) { return INT2FIX(ARGF.lineno); } static VALUE argf_forward(int argc, VALUE *argv, VALUE argf) { return rb_funcall3(ARGF.current_file, rb_frame_this_func(), argc, argv); } #define next_argv() argf_next_argv(argf) #define ARGF_GENERIC_INPUT_P() \ (ARGF.current_file == rb_stdin && TYPE(ARGF.current_file) != T_FILE) #define ARGF_FORWARD(argc, argv) do {\ if (ARGF_GENERIC_INPUT_P())\ return argf_forward((argc), (argv), argf);\ } while (0) #define NEXT_ARGF_FORWARD(argc, argv) do {\ if (!next_argv()) return Qnil;\ ARGF_FORWARD((argc), (argv));\ } while (0) static void argf_close(VALUE file) { if (RB_TYPE_P(file, T_FILE)) { rb_io_set_write_io(file, Qnil); } rb_funcall3(file, rb_intern("close"), 0, 0); } static int argf_next_argv(VALUE argf) { char *fn; rb_io_t *fptr; int stdout_binmode = 0; int fmode; if (RB_TYPE_P(rb_stdout, T_FILE)) { GetOpenFile(rb_stdout, fptr); if (fptr->mode & FMODE_BINMODE) stdout_binmode = 1; } if (ARGF.init_p == 0) { if (!NIL_P(ARGF.argv) && RARRAY_LEN(ARGF.argv) > 0) { ARGF.next_p = 1; } else { ARGF.next_p = -1; } ARGF.init_p = 1; } if (ARGF.next_p == 1) { retry: if (RARRAY_LEN(ARGF.argv) > 0) { ARGF.filename = rb_ary_shift(ARGF.argv); fn = StringValueCStr(ARGF.filename); if (strlen(fn) == 1 && fn[0] == '-') { ARGF.current_file = rb_stdin; if (ARGF.inplace) { rb_warn("Can't do inplace edit for stdio; skipping"); goto retry; } } else { VALUE write_io = Qnil; int fr = rb_sysopen(ARGF.filename, O_RDONLY, 0); if (ARGF.inplace) { struct stat st; #ifndef NO_SAFE_RENAME struct stat st2; #endif VALUE str; int fw; if (RB_TYPE_P(rb_stdout, T_FILE) && rb_stdout != orig_stdout) { rb_io_close(rb_stdout); } fstat(fr, &st); if (*ARGF.inplace) { str = rb_str_new2(fn); rb_str_cat2(str, ARGF.inplace); #ifdef NO_SAFE_RENAME (void)close(fr); (void)unlink(RSTRING_PTR(str)); if (rename(fn, RSTRING_PTR(str)) < 0) { rb_warn("Can't rename %s to %s: %s, skipping file", fn, RSTRING_PTR(str), strerror(errno)); goto retry; } fr = rb_sysopen(str, O_RDONLY, 0); #else if (rename(fn, RSTRING_PTR(str)) < 0) { rb_warn("Can't rename %s to %s: %s, skipping file", fn, RSTRING_PTR(str), strerror(errno)); close(fr); goto retry; } #endif } else { #ifdef NO_SAFE_RENAME rb_fatal("Can't do inplace edit without backup"); #else if (unlink(fn) < 0) { rb_warn("Can't remove %s: %s, skipping file", fn, strerror(errno)); close(fr); goto retry; } #endif } fw = rb_sysopen(ARGF.filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); #ifndef NO_SAFE_RENAME fstat(fw, &st2); #ifdef HAVE_FCHMOD fchmod(fw, st.st_mode); #else chmod(fn, st.st_mode); #endif if (st.st_uid!=st2.st_uid || st.st_gid!=st2.st_gid) { int err; #ifdef HAVE_FCHOWN err = fchown(fw, st.st_uid, st.st_gid); #else err = chown(fn, st.st_uid, st.st_gid); #endif if (err && getuid() == 0 && st2.st_uid == 0) { const char *wkfn = RSTRING_PTR(ARGF.filename); rb_warn("Can't set owner/group of %s to same as %s: %s, skipping file", wkfn, fn, strerror(errno)); (void)close(fr); (void)close(fw); (void)unlink(wkfn); goto retry; } } #endif write_io = prep_io(fw, FMODE_WRITABLE, rb_cFile, fn); rb_stdout = write_io; if (stdout_binmode) rb_io_binmode(rb_stdout); } fmode = FMODE_READABLE; if (!ARGF.binmode) { fmode |= DEFAULT_TEXTMODE; } ARGF.current_file = prep_io(fr, fmode, rb_cFile, fn); if (!NIL_P(write_io)) { rb_io_set_write_io(ARGF.current_file, write_io); } } if (ARGF.binmode) rb_io_ascii8bit_binmode(ARGF.current_file); GetOpenFile(ARGF.current_file, fptr); if (ARGF.encs.enc) { fptr->encs = ARGF.encs; clear_codeconv(fptr); } else { fptr->encs.ecflags &= ~ECONV_NEWLINE_DECORATOR_MASK; if (!ARGF.binmode) { fptr->encs.ecflags |= ECONV_DEFAULT_NEWLINE_DECORATOR; #ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE fptr->encs.ecflags |= TEXTMODE_NEWLINE_DECORATOR_ON_WRITE; #endif } } ARGF.next_p = 0; } else { ARGF.next_p = 1; return FALSE; } } else if (ARGF.next_p == -1) { ARGF.current_file = rb_stdin; ARGF.filename = rb_str_new2("-"); if (ARGF.inplace) { rb_warn("Can't do inplace edit for stdio"); rb_stdout = orig_stdout; } } return TRUE; } static VALUE argf_getline(int argc, VALUE *argv, VALUE argf) { VALUE line; long lineno = ARGF.lineno; retry: if (!next_argv()) return Qnil; if (ARGF_GENERIC_INPUT_P()) { line = rb_funcall3(ARGF.current_file, rb_intern("gets"), argc, argv); } else { if (argc == 0 && rb_rs == rb_default_rs) { line = rb_io_gets(ARGF.current_file); } else { line = rb_io_getline(argc, argv, ARGF.current_file); } if (NIL_P(line) && ARGF.next_p != -1) { argf_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } } if (!NIL_P(line)) { ARGF.lineno = ++lineno; ARGF.last_lineno = ARGF.lineno; } return line; } static VALUE argf_lineno_getter(ID id, VALUE *var) { VALUE argf = *var; return INT2FIX(ARGF.last_lineno); } static void argf_lineno_setter(VALUE val, ID id, VALUE *var) { VALUE argf = *var; int n = NUM2INT(val); ARGF.last_lineno = ARGF.lineno = n; } static VALUE argf_gets(int, VALUE *, VALUE); /* * call-seq: * gets(sep=$/) -> string or nil * gets(limit) -> string or nil * gets(sep,limit) -> string or nil * * Returns (and assigns to $_) the next line from the list * of files in +ARGV+ (or $*), or from standard input if * no files are present on the command line. Returns +nil+ at end of * file. The optional argument specifies the record separator. The * separator is included with the contents of each record. A separator * of +nil+ reads the entire contents, and a zero-length separator * reads the input one paragraph at a time, where paragraphs are * divided by two consecutive newlines. 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. If multiple * filenames are present in +ARGV+, +gets(nil)+ will read the contents * one file at a time. * * ARGV << "testfile" * print while gets * * produces: * * This is line one * This is line two * This is line three * And so on... * * The style of programming using $_ as an implicit * parameter is gradually losing favor in the Ruby community. */ static VALUE rb_f_gets(int argc, VALUE *argv, VALUE recv) { if (recv == argf) { return argf_gets(argc, argv, argf); } return rb_funcall2(argf, rb_intern("gets"), argc, argv); } /* * call-seq: * ARGF.gets(sep=$/) -> string * ARGF.gets(limit) -> string * ARGF.gets(sep, limit) -> string * * Returns the next line from the current file in +ARGF+. * * By default lines are assumed to be separated by +$/+; to use a different * character as a separator, supply it as a +String+ for the _sep_ argument. * * The optional _limit_ argument specifies how many characters of each line * to return. By default all characters are returned. * */ static VALUE argf_gets(int argc, VALUE *argv, VALUE argf) { VALUE line; line = argf_getline(argc, argv, argf); rb_lastline_set(line); return line; } VALUE rb_gets(void) { VALUE line; if (rb_rs != rb_default_rs) { return rb_f_gets(0, 0, argf); } retry: if (!next_argv()) return Qnil; line = rb_io_gets(ARGF.current_file); if (NIL_P(line) && ARGF.next_p != -1) { rb_io_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } rb_lastline_set(line); if (!NIL_P(line)) { ARGF.lineno++; ARGF.last_lineno = ARGF.lineno; } return line; } static VALUE argf_readline(int, VALUE *, VALUE); /* * call-seq: * readline(sep=$/) -> string * readline(limit) -> string * readline(sep, limit) -> string * * Equivalent to Kernel::gets, except * +readline+ raises +EOFError+ at end of file. */ static VALUE rb_f_readline(int argc, VALUE *argv, VALUE recv) { if (recv == argf) { return argf_readline(argc, argv, argf); } return rb_funcall2(argf, rb_intern("readline"), argc, argv); } /* * call-seq: * ARGF.readline(sep=$/) -> string * ARGF.readline(limit) -> string * ARGF.readline(sep, limit) -> string * * Returns the next line from the current file in +ARGF+. * * By default lines are assumed to be separated by +$/+; to use a different * character as a separator, supply it as a +String+ for the _sep_ argument. * * The optional _limit_ argument specifies how many characters of each line * to return. By default all characters are returned. * * An +EOFError+ is raised at the end of the file. */ static VALUE argf_readline(int argc, VALUE *argv, VALUE argf) { VALUE line; if (!next_argv()) rb_eof_error(); ARGF_FORWARD(argc, argv); line = argf_gets(argc, argv, argf); if (NIL_P(line)) { rb_eof_error(); } return line; } static VALUE argf_readlines(int, VALUE *, VALUE); /* * call-seq: * readlines(sep=$/) -> array * readlines(limit) -> array * readlines(sep,limit) -> array * * Returns an array containing the lines returned by calling * Kernel.gets(sep) until the end of file. */ static VALUE rb_f_readlines(int argc, VALUE *argv, VALUE recv) { if (recv == argf) { return argf_readlines(argc, argv, argf); } return rb_funcall2(argf, rb_intern("readlines"), argc, argv); } /* * call-seq: * ARGF.readlines(sep=$/) -> array * ARGF.readlines(limit) -> array * ARGF.readlines(sep, limit) -> array * * ARGF.to_a(sep=$/) -> array * ARGF.to_a(limit) -> array * ARGF.to_a(sep, limit) -> array * * Reads +ARGF+'s current file in its entirety, returning an +Array+ of its * lines, one line per element. Lines are assumed to be separated by _sep_. * * lines = ARGF.readlines * lines[0] #=> "This is line one\n" */ static VALUE argf_readlines(int argc, VALUE *argv, VALUE argf) { long lineno = ARGF.lineno; VALUE lines, ary; ary = rb_ary_new(); while (next_argv()) { if (ARGF_GENERIC_INPUT_P()) { lines = rb_funcall3(ARGF.current_file, rb_intern("readlines"), argc, argv); } else { lines = rb_io_readlines(argc, argv, ARGF.current_file); argf_close(ARGF.current_file); } ARGF.next_p = 1; rb_ary_concat(ary, lines); ARGF.lineno = lineno + RARRAY_LEN(ary); ARGF.last_lineno = ARGF.lineno; } return ary; } /* * call-seq: * `cmd` -> string * * Returns the standard output of running _cmd_ in a subshell. * The built-in syntax %x{...} uses * this method. Sets $? to the process status. * * `date` #=> "Wed Apr 9 08:56:30 CDT 2003\n" * `ls testdir`.split[1] #=> "main.rb" * `echo oops && exit 99` #=> "oops\n" * $?.exitstatus #=> 99 */ static VALUE rb_f_backquote(VALUE obj, VALUE str) { volatile VALUE port; VALUE result; rb_io_t *fptr; SafeStringValue(str); port = pipe_open_s(str, "r", FMODE_READABLE|DEFAULT_TEXTMODE, NULL); if (NIL_P(port)) return rb_str_new(0,0); GetOpenFile(port, fptr); result = read_all(fptr, remain_size(fptr), Qnil); rb_io_close(port); return result; } #ifdef HAVE_SYS_SELECT_H #include #endif static VALUE select_internal(VALUE read, VALUE write, VALUE except, struct timeval *tp, rb_fdset_t *fds) { VALUE res, list; rb_fdset_t *rp, *wp, *ep; rb_io_t *fptr; long i; int max = 0, n; int pending = 0; struct timeval timerec; if (!NIL_P(read)) { Check_Type(read, T_ARRAY); for (i=0; ifd, &fds[0]); if (READ_DATA_PENDING(fptr) || READ_CHAR_PENDING(fptr)) { /* check for buffered data */ pending++; rb_fd_set(fptr->fd, &fds[3]); } if (max < fptr->fd) max = fptr->fd; } if (pending) { /* no blocking if there's buffered data */ timerec.tv_sec = timerec.tv_usec = 0; tp = &timerec; } rp = &fds[0]; } else rp = 0; if (!NIL_P(write)) { Check_Type(write, T_ARRAY); for (i=0; ifd, &fds[1]); if (max < fptr->fd) max = fptr->fd; } wp = &fds[1]; } else wp = 0; if (!NIL_P(except)) { Check_Type(except, T_ARRAY); for (i=0; ifd, &fds[2]); if (max < fptr->fd) max = fptr->fd; if (io != write_io) { GetOpenFile(write_io, fptr); rb_fd_set(fptr->fd, &fds[2]); if (max < fptr->fd) max = fptr->fd; } } ep = &fds[2]; } else { ep = 0; } max++; n = rb_thread_fd_select(max, rp, wp, ep, tp); if (n < 0) { rb_sys_fail(0); } if (!pending && n == 0) return Qnil; /* returns nil on timeout */ res = rb_ary_new2(3); rb_ary_push(res, rp?rb_ary_new():rb_ary_new2(0)); rb_ary_push(res, wp?rb_ary_new():rb_ary_new2(0)); rb_ary_push(res, ep?rb_ary_new():rb_ary_new2(0)); if (rp) { list = RARRAY_PTR(res)[0]; for (i=0; i< RARRAY_LEN(read); i++) { VALUE obj = rb_ary_entry(read, i); VALUE io = rb_io_get_io(obj); GetOpenFile(io, fptr); if (rb_fd_isset(fptr->fd, &fds[0]) || rb_fd_isset(fptr->fd, &fds[3])) { rb_ary_push(list, obj); } } } if (wp) { list = RARRAY_PTR(res)[1]; for (i=0; i< RARRAY_LEN(write); i++) { VALUE obj = rb_ary_entry(write, i); VALUE io = rb_io_get_io(obj); VALUE write_io = GetWriteIO(io); GetOpenFile(write_io, fptr); if (rb_fd_isset(fptr->fd, &fds[1])) { rb_ary_push(list, obj); } } } if (ep) { list = RARRAY_PTR(res)[2]; for (i=0; i< RARRAY_LEN(except); i++) { VALUE obj = rb_ary_entry(except, i); VALUE io = rb_io_get_io(obj); VALUE write_io = GetWriteIO(io); GetOpenFile(io, fptr); if (rb_fd_isset(fptr->fd, &fds[2])) { rb_ary_push(list, obj); } else if (io != write_io) { GetOpenFile(write_io, fptr); if (rb_fd_isset(fptr->fd, &fds[2])) { rb_ary_push(list, obj); } } } } return res; /* returns an empty array on interrupt */ } struct select_args { VALUE read, write, except; struct timeval *timeout; rb_fdset_t fdsets[4]; }; static VALUE select_call(VALUE arg) { struct select_args *p = (struct select_args *)arg; return select_internal(p->read, p->write, p->except, p->timeout, p->fdsets); } static VALUE select_end(VALUE arg) { struct select_args *p = (struct select_args *)arg; int i; for (i = 0; i < numberof(p->fdsets); ++i) rb_fd_term(&p->fdsets[i]); return Qnil; } static VALUE sym_normal, sym_sequential, sym_random, sym_willneed, sym_dontneed, sym_noreuse; #ifdef HAVE_POSIX_FADVISE struct io_advise_struct { int fd; off_t offset; off_t len; int advice; }; static VALUE io_advise_internal(void *arg) { struct io_advise_struct *ptr = arg; return posix_fadvise(ptr->fd, ptr->offset, ptr->len, ptr->advice); } static VALUE io_advise_sym_to_const(VALUE sym) { #ifdef POSIX_FADV_NORMAL if (sym == sym_normal) return INT2NUM(POSIX_FADV_NORMAL); #endif #ifdef POSIX_FADV_RANDOM if (sym == sym_random) return INT2NUM(POSIX_FADV_RANDOM); #endif #ifdef POSIX_FADV_SEQUENTIAL if (sym == sym_sequential) return INT2NUM(POSIX_FADV_SEQUENTIAL); #endif #ifdef POSIX_FADV_WILLNEED if (sym == sym_willneed) return INT2NUM(POSIX_FADV_WILLNEED); #endif #ifdef POSIX_FADV_DONTNEED if (sym == sym_dontneed) return INT2NUM(POSIX_FADV_DONTNEED); #endif #ifdef POSIX_FADV_NOREUSE if (sym == sym_noreuse) return INT2NUM(POSIX_FADV_NOREUSE); #endif return Qnil; } static VALUE do_io_advise(rb_io_t *fptr, VALUE advice, off_t offset, off_t len) { int rv; struct io_advise_struct ias; VALUE num_adv; num_adv = io_advise_sym_to_const(advice); /* * The platform doesn't support this hint. We don't raise exception, instead * silently ignore it. Because IO::advise is only hint. */ if (NIL_P(num_adv)) return Qnil; ias.fd = fptr->fd; ias.advice = NUM2INT(num_adv); ias.offset = offset; ias.len = len; rv = (int)rb_thread_io_blocking_region(io_advise_internal, &ias, fptr->fd); if (rv) { /* posix_fadvise(2) doesn't set errno. On success it returns 0; otherwise it returns the error code. */ rb_syserr_fail(rv, RSTRING_PTR(fptr->pathv)); } return Qnil; } #endif /* HAVE_POSIX_FADVISE */ static void advice_arg_check(VALUE advice) { if (!SYMBOL_P(advice)) rb_raise(rb_eTypeError, "advice must be a Symbol"); if (advice != sym_normal && advice != sym_sequential && advice != sym_random && advice != sym_willneed && advice != sym_dontneed && advice != sym_noreuse) { VALUE symname = rb_inspect(advice); rb_raise(rb_eNotImpError, "Unsupported advice: %s", StringValuePtr(symname)); } } /* * call-seq: * ios.advise(advice, offset=0, len=0) -> nil * * Announce an intention to access data from the current file in a * specific pattern. On platforms that do not support the * posix_fadvise(2) system call, this method is a no-op. * * _advice_ is one of the following symbols: * * * :normal - No advice to give; the default assumption for an open file. * * :sequential - The data will be accessed sequentially: * with lower offsets read before higher ones. * * :random - The data will be accessed in random order. * * :willneed - The data will be accessed in the near future. * * :dontneed - The data will not be accessed in the near future. * * :noreuse - The data will only be accessed once. * * The semantics of a piece of advice are platform-dependent. See * man 2 posix_fadvise for details. * * "data" means the region of the current file that begins at * _offset_ and extends for _len_ bytes. If _len_ is 0, the region * ends at the last byte of the file. By default, both _offset_ and * _len_ are 0, meaning that the advice applies to the entire file. * * If an error occurs, one of the following exceptions will be raised: * * * IOError - The IO stream is closed. * * Errno::EBADF - The file descriptor of the current file is invalid. * * Errno::EINVAL - An invalid value for _advice_ was given. * * Errno::ESPIPE - The file descriptor of the current * * file refers to a FIFO or pipe. (Linux raises Errno::EINVAL * * in this case). * * TypeError - Either _advice_ was not a Symbol, or one of the other arguments was not an Integer. * * RangeError - One of the arguments given was too big/small. * * This list is not exhaustive; other Errno:: exceptions are also possible. */ static VALUE rb_io_advise(int argc, VALUE *argv, VALUE io) { VALUE advice, offset, len; off_t off, l; rb_io_t *fptr; rb_scan_args(argc, argv, "12", &advice, &offset, &len); advice_arg_check(advice); io = GetWriteIO(io); GetOpenFile(io, fptr); off = NIL_P(offset) ? 0 : NUM2OFFT(offset); l = NIL_P(len) ? 0 : NUM2OFFT(len); #ifdef HAVE_POSIX_FADVISE return do_io_advise(fptr, advice, off, l); #else ((void)off, (void)l); /* Ignore all hint */ return Qnil; #endif } /* * call-seq: * IO.select(read_array * [, write_array * [, error_array * [, timeout]]]) -> array or nil * * Calls select(2) system call. * It monitors given arrays of IO objects, waits one or more * of IO objects ready for reading, are ready for writing, * and have pending exceptions respectably, and returns an array that * contains arrays of those IO objects. It will return nil * if optional timeout value is given and no IO object * is ready in timeout seconds. * * === Parameters * read_array:: an array of IO objects that wait until ready for read * write_array:: an array of IO objects that wait until ready for write * error_array:: an array of IO objects that wait for exceptions * timeout:: a numeric value in second * * === Example * * rp, wp = IO.pipe * mesg = "ping " * 100.times { * rs, ws, = IO.select([rp], [wp]) * if r = rs[0] * ret = r.read(5) * print ret * case ret * when /ping/ * mesg = "pong\n" * when /pong/ * mesg = "ping " * end * end * if w = ws[0] * w.write(mesg) * end * } * * produces: * * ping pong * ping pong * ping pong * (snipped) * ping */ static VALUE rb_f_select(int argc, VALUE *argv, VALUE obj) { VALUE timeout; struct select_args args; struct timeval timerec; int i; rb_scan_args(argc, argv, "13", &args.read, &args.write, &args.except, &timeout); if (NIL_P(timeout)) { args.timeout = 0; } else { timerec = rb_time_interval(timeout); args.timeout = &timerec; } for (i = 0; i < numberof(args.fdsets); ++i) rb_fd_init(&args.fdsets[i]); return rb_ensure(select_call, (VALUE)&args, select_end, (VALUE)&args); } #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) typedef unsigned long ioctl_req_t; #define NUM2IOCTLREQ(num) NUM2ULONG(num) #else typedef int ioctl_req_t; #define NUM2IOCTLREQ(num) NUM2INT(num) #endif struct ioctl_arg { int fd; ioctl_req_t cmd; long narg; }; static VALUE nogvl_ioctl(void *ptr) { struct ioctl_arg *arg = ptr; return (VALUE)ioctl(arg->fd, arg->cmd, arg->narg); } static int do_ioctl(int fd, ioctl_req_t cmd, long narg) { int retval; struct ioctl_arg arg; arg.fd = fd; arg.cmd = cmd; arg.narg = narg; retval = (int)rb_thread_io_blocking_region(nogvl_ioctl, &arg, fd); return retval; } #define DEFULT_IOCTL_NARG_LEN (256) #ifdef __linux__ static long linux_iocparm_len(ioctl_req_t cmd) { long len; if ((cmd & 0xFFFF0000) == 0) { /* legacy and unstructured ioctl number. */ return DEFULT_IOCTL_NARG_LEN; } len = _IOC_SIZE(cmd); /* paranoia check for silly drivers which don't keep ioctl convention */ if (len < DEFULT_IOCTL_NARG_LEN) len = DEFULT_IOCTL_NARG_LEN; return len; } #endif static long ioctl_narg_len(ioctl_req_t cmd) { long len; #ifdef IOCPARM_MASK #ifndef IOCPARM_LEN #define IOCPARM_LEN(x) (((x) >> 16) & IOCPARM_MASK) #endif #endif #ifdef IOCPARM_LEN len = IOCPARM_LEN(cmd); /* on BSDish systems we're safe */ #elif defined(__linux__) len = linux_iocparm_len(cmd); #else /* otherwise guess at what's safe */ len = DEFULT_IOCTL_NARG_LEN; #endif return len; } #ifdef HAVE_FCNTL #ifdef __linux__ typedef long fcntl_arg_t; #else /* posix */ typedef int fcntl_arg_t; #endif static long fcntl_narg_len(int cmd) { long len; switch (cmd) { #ifdef F_DUPFD case F_DUPFD: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_DUP2FD /* bsd specific */ case F_DUP2FD: len = sizeof(int); break; #endif #ifdef F_DUPFD_CLOEXEC /* linux specific */ case F_DUPFD_CLOEXEC: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_GETFD case F_GETFD: len = 1; break; #endif #ifdef F_SETFD case F_SETFD: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_GETFL case F_GETFL: len = 1; break; #endif #ifdef F_SETFL case F_SETFL: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_GETOWN case F_GETOWN: len = 1; break; #endif #ifdef F_SETOWN case F_SETOWN: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_GETOWN_EX /* linux specific */ case F_GETOWN_EX: len = sizeof(struct f_owner_ex); break; #endif #ifdef F_SETOWN_EX /* linux specific */ case F_SETOWN_EX: len = sizeof(struct f_owner_ex); break; #endif #ifdef F_GETLK case F_GETLK: len = sizeof(struct flock); break; #endif #ifdef F_SETLK case F_SETLK: len = sizeof(struct flock); break; #endif #ifdef F_SETLKW case F_SETLKW: len = sizeof(struct flock); break; #endif #ifdef F_READAHEAD /* bsd specific */ case F_READAHEAD: len = sizeof(int); break; #endif #ifdef F_RDAHEAD /* Darwin specific */ case F_RDAHEAD: len = sizeof(int); break; #endif #ifdef F_GETSIG /* linux specific */ case F_GETSIG: len = 1; break; #endif #ifdef F_SETSIG /* linux specific */ case F_SETSIG: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_GETLEASE /* linux specific */ case F_GETLEASE: len = 1; break; #endif #ifdef F_SETLEASE /* linux specific */ case F_SETLEASE: len = sizeof(fcntl_arg_t); break; #endif #ifdef F_NOTIFY /* linux specific */ case F_NOTIFY: len = sizeof(fcntl_arg_t); break; #endif default: len = 256; break; } return len; } #else /* HAVE_FCNTL */ static long fcntl_narg_len(int cmd) { return 0; } #endif /* HAVE_FCNTL */ static long setup_narg(ioctl_req_t cmd, VALUE *argp, int io_p) { long narg = 0; VALUE arg = *argp; if (NIL_P(arg) || arg == Qfalse) { narg = 0; } else if (FIXNUM_P(arg)) { narg = FIX2LONG(arg); } else if (arg == Qtrue) { narg = 1; } else { VALUE tmp = rb_check_string_type(arg); if (NIL_P(tmp)) { narg = NUM2LONG(arg); } else { long len; *argp = arg = tmp; if (io_p) len = ioctl_narg_len(cmd); else len = fcntl_narg_len((int)cmd); rb_str_modify(arg); /* expand for data + sentinel. */ if (RSTRING_LEN(arg) < len+1) { rb_str_resize(arg, len+1); } /* a little sanity check here */ RSTRING_PTR(arg)[RSTRING_LEN(arg) - 1] = 17; narg = (long)(SIGNED_VALUE)RSTRING_PTR(arg); } } return narg; } static VALUE rb_ioctl(VALUE io, VALUE req, VALUE arg) { ioctl_req_t cmd = NUM2IOCTLREQ(req); rb_io_t *fptr; long narg; int retval; rb_secure(2); narg = setup_narg(cmd, &arg, 1); GetOpenFile(io, fptr); retval = do_ioctl(fptr->fd, cmd, narg); if (retval < 0) rb_sys_fail_path(fptr->pathv); if (RB_TYPE_P(arg, T_STRING)) { if (RSTRING_PTR(arg)[RSTRING_LEN(arg)-1] != 17) rb_raise(rb_eArgError, "return value overflowed string"); RSTRING_PTR(arg)[RSTRING_LEN(arg)-1] = '\0'; } return INT2NUM(retval); } /* * call-seq: * ios.ioctl(integer_cmd, arg) -> integer * * Provides a mechanism for issuing low-level commands to control or * query I/O devices. Arguments and results are platform dependent. If * arg is a number, its value is passed directly. If it is a * string, it is interpreted as a binary sequence of bytes. On Unix * platforms, see ioctl(2) for details. Not implemented on * all platforms. */ static VALUE rb_io_ioctl(int argc, VALUE *argv, VALUE io) { VALUE req, arg; rb_scan_args(argc, argv, "11", &req, &arg); return rb_ioctl(io, req, arg); } #ifdef HAVE_FCNTL struct fcntl_arg { int fd; int cmd; long narg; }; static VALUE nogvl_fcntl(void *ptr) { struct fcntl_arg *arg = ptr; #if defined(F_DUPFD) if (arg->cmd == F_DUPFD) return (VALUE)rb_cloexec_fcntl_dupfd(arg->fd, (int)arg->narg); #endif return (VALUE)fcntl(arg->fd, arg->cmd, arg->narg); } static int do_fcntl(int fd, int cmd, long narg) { int retval; struct fcntl_arg arg; arg.fd = fd; arg.cmd = cmd; arg.narg = narg; retval = (int)rb_thread_io_blocking_region(nogvl_fcntl, &arg, fd); #if defined(F_DUPFD) if (retval != -1 && cmd == F_DUPFD) { rb_update_max_fd(retval); } #endif return retval; } static VALUE rb_fcntl(VALUE io, VALUE req, VALUE arg) { int cmd = NUM2INT(req); rb_io_t *fptr; long narg; int retval; rb_secure(2); narg = setup_narg(cmd, &arg, 0); GetOpenFile(io, fptr); retval = do_fcntl(fptr->fd, cmd, narg); if (retval < 0) rb_sys_fail_path(fptr->pathv); if (RB_TYPE_P(arg, T_STRING)) { if (RSTRING_PTR(arg)[RSTRING_LEN(arg)-1] != 17) rb_raise(rb_eArgError, "return value overflowed string"); RSTRING_PTR(arg)[RSTRING_LEN(arg)-1] = '\0'; } if (cmd == F_SETFL) { if (narg & O_NONBLOCK) { fptr->mode |= FMODE_WSPLIT_INITIALIZED; fptr->mode &= ~FMODE_WSPLIT; } else { fptr->mode &= ~(FMODE_WSPLIT_INITIALIZED|FMODE_WSPLIT); } } return INT2NUM(retval); } /* * call-seq: * ios.fcntl(integer_cmd, arg) -> integer * * Provides a mechanism for issuing low-level commands to control or * query file-oriented I/O streams. Arguments and results are platform * dependent. If arg is a number, its value is passed * directly. If it is a string, it is interpreted as a binary sequence * of bytes (Array#pack might be a useful way to build this * string). On Unix platforms, see fcntl(2) for details. * Not implemented on all platforms. */ static VALUE rb_io_fcntl(int argc, VALUE *argv, VALUE io) { VALUE req, arg; rb_scan_args(argc, argv, "11", &req, &arg); return rb_fcntl(io, req, arg); } #else #define rb_io_fcntl rb_f_notimplement #endif #if defined(HAVE_SYSCALL) || defined(HAVE___SYSCALL) /* * call-seq: * syscall(num [, args...]) -> integer * * Calls the operating system function identified by _num_ and * returns the result of the function or raises SystemCallError if * it failed. * * Arguments for the function can follow _num_. They must be either * +String+ objects or +Integer+ objects. A +String+ object is passed * as a pointer to the byte sequence. An +Integer+ object is passed * as an integer whose bit size is same as a pointer. * Up to nine parameters may be passed (14 on the Atari-ST). * * The function identified by _num_ is system * dependent. On some Unix systems, the numbers may be obtained from a * header file called syscall.h. * * syscall 4, 1, "hello\n", 6 # '4' is write(2) on our box * * produces: * * hello * * * Calling +syscall+ on a platform which does not have any way to * an arbitrary system function just fails with NotImplementedError. * * Note:: * +syscall+ is essentially unsafe and unportable. Feel free to shoot your foot. * DL (Fiddle) library is preferred for safer and a bit more portable programming. */ static VALUE rb_f_syscall(int argc, VALUE *argv) { #ifdef atarist VALUE arg[13]; /* yes, we really need that many ! */ #else VALUE arg[8]; #endif #if SIZEOF_VOIDP == 8 && defined(HAVE___SYSCALL) && SIZEOF_INT != 8 /* mainly *BSD */ # define SYSCALL __syscall # define NUM2SYSCALLID(x) NUM2LONG(x) # define RETVAL2NUM(x) LONG2NUM(x) # if SIZEOF_LONG == 8 long num, retval = -1; # elif SIZEOF_LONG_LONG == 8 long long num, retval = -1; # else # error ---->> it is asserted that __syscall takes the first argument and returns retval in 64bit signed integer. <<---- # endif #elif defined(__linux__) # define SYSCALL syscall # define NUM2SYSCALLID(x) NUM2LONG(x) # define RETVAL2NUM(x) LONG2NUM(x) /* * Linux man page says, syscall(2) function prototype is below. * * int syscall(int number, ...); * * But, it's incorrect. Actual one takes and returned long. (see unistd.h) */ long num, retval = -1; #else # define SYSCALL syscall # define NUM2SYSCALLID(x) NUM2INT(x) # define RETVAL2NUM(x) INT2NUM(x) int num, retval = -1; #endif int i; if (RTEST(ruby_verbose)) { rb_warning("We plan to remove a syscall function at future release. DL(Fiddle) provides safer alternative."); } rb_secure(2); if (argc == 0) rb_raise(rb_eArgError, "too few arguments for syscall"); if (argc > numberof(arg)) rb_raise(rb_eArgError, "too many arguments for syscall"); num = NUM2SYSCALLID(argv[0]); ++argv; for (i = argc - 1; i--; ) { VALUE v = rb_check_string_type(argv[i]); if (!NIL_P(v)) { SafeStringValue(v); rb_str_modify(v); arg[i] = (VALUE)StringValueCStr(v); } else { arg[i] = (VALUE)NUM2LONG(argv[i]); } } switch (argc) { case 1: retval = SYSCALL(num); break; case 2: retval = SYSCALL(num, arg[0]); break; case 3: retval = SYSCALL(num, arg[0],arg[1]); break; case 4: retval = SYSCALL(num, arg[0],arg[1],arg[2]); break; case 5: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3]); break; case 6: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4]); break; case 7: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5]); break; case 8: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6]); break; #ifdef atarist case 9: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7]); break; case 10: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8]); break; case 11: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9]); break; case 12: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10]); break; case 13: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10], arg[11]); break; case 14: retval = SYSCALL(num, arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10], arg[11], arg[12]); break; #endif } if (retval == -1) rb_sys_fail(0); return RETVAL2NUM(retval); #undef SYSCALL #undef NUM2SYSCALLID #undef RETVAL2NUM } #else #define rb_f_syscall rb_f_notimplement #endif static VALUE io_new_instance(VALUE args) { return rb_class_new_instance(2, (VALUE*)args+1, *(VALUE*)args); } static void io_encoding_set(rb_io_t *fptr, VALUE v1, VALUE v2, VALUE opt) { rb_encoding *enc, *enc2; int ecflags = fptr->encs.ecflags; VALUE ecopts, tmp; if (!NIL_P(v2)) { enc2 = rb_to_encoding(v1); tmp = rb_check_string_type(v2); if (!NIL_P(tmp)) { if (RSTRING_LEN(tmp) == 1 && RSTRING_PTR(tmp)[0] == '-') { /* Special case - "-" => no transcoding */ enc = enc2; enc2 = NULL; } else enc = rb_to_encoding(v2); if (enc == enc2) { /* Special case - "-" => no transcoding */ enc2 = NULL; } } else enc = rb_to_encoding(v2); SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecflags = rb_econv_prepare_options(opt, &ecopts, ecflags); } else { if (NIL_P(v1)) { /* Set to default encodings */ rb_io_ext_int_to_encs(NULL, NULL, &enc, &enc2); SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecopts = Qnil; } else { tmp = rb_check_string_type(v1); if (!NIL_P(tmp) && rb_enc_asciicompat(rb_enc_get(tmp))) { parse_mode_enc(RSTRING_PTR(tmp), &enc, &enc2, NULL); SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecflags = rb_econv_prepare_options(opt, &ecopts, ecflags); } else { rb_io_ext_int_to_encs(rb_to_encoding(v1), NULL, &enc, &enc2); SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags); ecopts = Qnil; } } } validate_enc_binmode(&fptr->mode, ecflags, enc, enc2); fptr->encs.enc = enc; fptr->encs.enc2 = enc2; fptr->encs.ecflags = ecflags; fptr->encs.ecopts = ecopts; clear_codeconv(fptr); } static VALUE pipe_pair_close(VALUE rw) { VALUE *rwp = (VALUE *)rw; return rb_ensure(io_close, rwp[0], io_close, rwp[1]); } /* * call-seq: * IO.pipe -> [read_io, write_io] * IO.pipe(ext_enc) -> [read_io, write_io] * IO.pipe("ext_enc:int_enc" [, opt]) -> [read_io, write_io] * IO.pipe(ext_enc, int_enc [, opt]) -> [read_io, write_io] * * IO.pipe(...) {|read_io, write_io| ... } * * Creates a pair of pipe endpoints (connected to each other) and * returns them as a two-element array of IO objects: * [ read_io, write_io ]. * * If a block is given, the block is called and * returns the value of the block. * read_io and write_io are sent to the block as arguments. * If read_io and write_io are not closed when the block exits, they are closed. * i.e. closing read_io and/or write_io doesn't cause an error. * * Not available on all platforms. * * If an encoding (encoding name or encoding object) is specified as an optional argument, * read string from pipe is tagged with the encoding specified. * If the argument is a colon separated two encoding names "A:B", * the read string is converted from encoding A (external encoding) * to encoding B (internal encoding), then tagged with B. * If two optional arguments are specified, those must be * encoding objects or encoding names, * and the first one is the external encoding, * and the second one is the internal encoding. * If the external encoding and the internal encoding is specified, * optional hash argument specify the conversion option. * * In the example below, the two processes close the ends of the pipe * that they are not using. This is not just a cosmetic nicety. The * read end of a pipe will not generate an end of file condition if * there are any writers with the pipe still open. In the case of the * parent process, the rd.read will never return if it * does not first issue a wr.close. * * rd, wr = IO.pipe * * if fork * wr.close * puts "Parent got: <#{rd.read}>" * rd.close * Process.wait * else * rd.close * puts "Sending message to parent" * wr.write "Hi Dad" * wr.close * end * * produces: * * Sending message to parent * Parent got: */ static VALUE rb_io_s_pipe(int argc, VALUE *argv, VALUE klass) { int pipes[2], state; VALUE r, w, args[3], v1, v2; VALUE opt; rb_io_t *fptr, *fptr2; int fmode = 0; VALUE ret; argc = rb_scan_args(argc, argv, "02:", &v1, &v2, &opt); if (rb_pipe(pipes) == -1) rb_sys_fail(0); args[0] = klass; args[1] = INT2NUM(pipes[0]); args[2] = INT2FIX(O_RDONLY); r = rb_protect(io_new_instance, (VALUE)args, &state); if (state) { close(pipes[0]); close(pipes[1]); rb_jump_tag(state); } GetOpenFile(r, fptr); io_encoding_set(fptr, v1, v2, opt); args[1] = INT2NUM(pipes[1]); args[2] = INT2FIX(O_WRONLY); w = rb_protect(io_new_instance, (VALUE)args, &state); if (state) { close(pipes[1]); if (!NIL_P(r)) rb_io_close(r); rb_jump_tag(state); } GetOpenFile(w, fptr2); rb_io_synchronized(fptr2); extract_binmode(opt, &fmode); #if DEFAULT_TEXTMODE if ((fptr->mode & FMODE_TEXTMODE) && (fmode & FMODE_BINMODE)) { fptr->mode &= ~FMODE_TEXTMODE; setmode(fptr->fd, O_BINARY); } #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) if (fptr->encs.ecflags & ECONV_DEFAULT_NEWLINE_DECORATOR) { fptr->encs.ecflags |= ECONV_UNIVERSAL_NEWLINE_DECORATOR; } #endif #endif fptr->mode |= fmode; #if DEFAULT_TEXTMODE if ((fptr2->mode & FMODE_TEXTMODE) && (fmode & FMODE_BINMODE)) { fptr2->mode &= ~FMODE_TEXTMODE; setmode(fptr2->fd, O_BINARY); } #endif fptr2->mode |= fmode; ret = rb_assoc_new(r, w); if (rb_block_given_p()) { VALUE rw[2]; rw[0] = r; rw[1] = w; return rb_ensure(rb_yield, ret, pipe_pair_close, (VALUE)rw); } return ret; } struct foreach_arg { int argc; VALUE *argv; VALUE io; }; static void open_key_args(int argc, VALUE *argv, VALUE opt, struct foreach_arg *arg) { VALUE path, v; path = *argv++; argc--; FilePathValue(path); arg->io = 0; arg->argc = argc; arg->argv = argv; if (NIL_P(opt)) { arg->io = rb_io_open(path, INT2NUM(O_RDONLY), INT2FIX(0666), Qnil); return; } v = rb_hash_aref(opt, sym_open_args); if (!NIL_P(v)) { VALUE args; long n; v = rb_convert_type(v, T_ARRAY, "Array", "to_ary"); n = RARRAY_LEN(v) + 1; #if SIZEOF_LONG > SIZEOF_INT if (n > INT_MAX) { rb_raise(rb_eArgError, "too many arguments"); } #endif args = rb_ary_tmp_new(n); rb_ary_push(args, path); rb_ary_concat(args, v); arg->io = rb_io_open_with_args((int)n, RARRAY_PTR(args)); rb_ary_clear(args); /* prevent from GC */ return; } arg->io = rb_io_open(path, Qnil, Qnil, opt); } static VALUE io_s_foreach(struct foreach_arg *arg) { VALUE str; while (!NIL_P(str = rb_io_gets_m(arg->argc, arg->argv, arg->io))) { rb_yield(str); } return Qnil; } /* * call-seq: * IO.foreach(name, sep=$/ [, open_args]) {|line| block } -> nil * IO.foreach(name, limit [, open_args]) {|line| block } -> nil * IO.foreach(name, sep, limit [, open_args]) {|line| block } -> nil * IO.foreach(...) -> an_enumerator * * Executes the block for every line in the named I/O port, where lines * are separated by sep. * * If no block is given, an enumerator is returned instead. * * IO.foreach("testfile") {|x| print "GOT ", x } * * produces: * * GOT This is line one * GOT This is line two * GOT This is line three * GOT And so on... * * If the last argument is a hash, it's the keyword argument to open. * See IO.read for detail. * */ static VALUE rb_io_s_foreach(int argc, VALUE *argv, VALUE self) { VALUE opt; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); RETURN_ENUMERATOR(self, argc, argv); open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_foreach, (VALUE)&arg, rb_io_close, arg.io); } static VALUE io_s_readlines(struct foreach_arg *arg) { return rb_io_readlines(arg->argc, arg->argv, arg->io); } /* * call-seq: * IO.readlines(name, sep=$/ [, open_args]) -> array * IO.readlines(name, limit [, open_args]) -> array * IO.readlines(name, sep, limit [, open_args]) -> array * * Reads the entire file specified by name as individual * lines, and returns those lines in an array. Lines are separated by * sep. * * a = IO.readlines("testfile") * a[0] #=> "This is line one\n" * * If the last argument is a hash, it's the keyword argument to open. * See IO.read for detail. * */ static VALUE rb_io_s_readlines(int argc, VALUE *argv, VALUE io) { VALUE opt; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_readlines, (VALUE)&arg, rb_io_close, arg.io); } static VALUE io_s_read(struct foreach_arg *arg) { return io_read(arg->argc, arg->argv, arg->io); } struct seek_arg { VALUE io; VALUE offset; int mode; }; static VALUE seek_before_access(VALUE argp) { struct seek_arg *arg = (struct seek_arg *)argp; rb_io_binmode(arg->io); return rb_io_seek(arg->io, arg->offset, arg->mode); } /* * call-seq: * IO.read(name, [length [, offset]] ) -> string * IO.read(name, [length [, offset]], open_args) -> string * * Opens the file, optionally seeks to the given offset, then returns * length bytes (defaulting to the rest of the file). * read ensures the file is closed before returning. * * If the last argument is a hash, it specifies option for internal * open(). The key would be the following. open_args: is exclusive * to others. * * encoding: string or encoding * * specifies encoding of the read string. encoding will be ignored * if length is specified. * * mode: string * * specifies mode argument for open(). it should start with "r" * otherwise it would cause error. * * open_args: array of strings * * specifies arguments for open() as an array. * * IO.read("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" * IO.read("testfile", 20) #=> "This is line one\nThi" * IO.read("testfile", 20, 10) #=> "ne one\nThis is line " */ static VALUE rb_io_s_read(int argc, VALUE *argv, VALUE io) { VALUE opt, offset; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, &offset, NULL, &opt); open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; if (!NIL_P(offset)) { struct seek_arg sarg; int state = 0; sarg.io = arg.io; sarg.offset = offset; sarg.mode = SEEK_SET; rb_protect(seek_before_access, (VALUE)&sarg, &state); if (state) { rb_io_close(arg.io); rb_jump_tag(state); } if (arg.argc == 2) arg.argc = 1; } return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); } /* * call-seq: * IO.binread(name, [length [, offset]] ) -> string * * Opens the file, optionally seeks to the given offset, then returns * length bytes (defaulting to the rest of the file). * binread ensures the file is closed before returning. * The open mode would be "rb:ASCII-8BIT". * * IO.binread("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" * IO.binread("testfile", 20) #=> "This is line one\nThi" * IO.binread("testfile", 20, 10) #=> "ne one\nThis is line " */ static VALUE rb_io_s_binread(int argc, VALUE *argv, VALUE io) { VALUE offset; struct foreach_arg arg; rb_scan_args(argc, argv, "12", NULL, NULL, &offset); FilePathValue(argv[0]); arg.io = rb_io_open(argv[0], rb_str_new_cstr("rb:ASCII-8BIT"), Qnil, Qnil); if (NIL_P(arg.io)) return Qnil; arg.argv = argv+1; arg.argc = (argc > 1) ? 1 : 0; if (!NIL_P(offset)) { rb_io_seek(arg.io, offset, SEEK_SET); } return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); } static VALUE io_s_write0(struct write_arg *arg) { return io_write(arg->io,arg->str,arg->nosync); } static VALUE io_s_write(int argc, VALUE *argv, int binary) { VALUE string, offset, opt; struct foreach_arg arg; struct write_arg warg; rb_scan_args(argc, argv, "21:", NULL, &string, &offset, &opt); if (NIL_P(opt)) opt = rb_hash_new(); else opt = rb_hash_dup(opt); if (NIL_P(rb_hash_aref(opt,sym_mode))) { int mode = O_WRONLY|O_CREAT; #ifdef O_BINARY if (binary) mode |= O_BINARY; #endif if (NIL_P(offset)) mode |= O_TRUNC; rb_hash_aset(opt,sym_mode,INT2NUM(mode)); } open_key_args(argc,argv,opt,&arg); #ifndef O_BINARY if (binary) rb_io_binmode_m(arg.io); #endif if (NIL_P(arg.io)) return Qnil; if (!NIL_P(offset)) { struct seek_arg sarg; int state = 0; sarg.io = arg.io; sarg.offset = offset; sarg.mode = SEEK_SET; rb_protect(seek_before_access, (VALUE)&sarg, &state); if (state) { rb_io_close(arg.io); rb_jump_tag(state); } } warg.io = arg.io; warg.str = string; warg.nosync = 0; return rb_ensure(io_s_write0, (VALUE)&warg, rb_io_close, arg.io); } /* * call-seq: * IO.write(name, string, [offset] ) => fixnum * IO.write(name, string, [offset], open_args ) => fixnum * * Opens the file, optionally seeks to the given offset, writes * string, then returns the length written. * write ensures the file is closed before returning. * If offset is not given, the file is truncated. Otherwise, * it is not truncated. * * If the last argument is a hash, it specifies option for internal * open(). The key would be the following. open_args: is exclusive * to others. * * encoding: string or encoding * * specifies encoding of the read string. encoding will be ignored * if length is specified. * * mode: string * * specifies mode argument for open(). it should start with "w" or "a" or "r+" * otherwise it would cause error. * * perm: fixnum * * specifies perm argument for open(). * * open_args: array * * specifies arguments for open() as an array. * * IO.write("testfile", "0123456789", 20) # => 10 * # File could contain: "This is line one\nThi0123456789two\nThis is line three\nAnd so on...\n" * IO.write("testfile", "0123456789") #=> 10 * # File would now read: "0123456789" */ static VALUE rb_io_s_write(int argc, VALUE *argv, VALUE io) { return io_s_write(argc, argv, 0); } /* * call-seq: * IO.binwrite(name, string, [offset] ) => fixnum * * Opens the file, optionally seeks to the given offset, writes * string then returns the length written. * binwrite ensures the file is closed before returning. * The open mode would be "wb:ASCII-8BIT". * If offset is not given, the file is truncated. Otherwise, * it is not truncated. * * IO.binwrite("testfile", "0123456789", 20) # => 10 * # File could contain: "This is line one\nThi0123456789two\nThis is line three\nAnd so on...\n" * IO.binwrite("testfile", "0123456789") #=> 10 * # File would now read: "0123456789" */ static VALUE rb_io_s_binwrite(int argc, VALUE *argv, VALUE io) { return io_s_write(argc, argv, 1); } struct copy_stream_struct { VALUE src; VALUE dst; off_t copy_length; /* (off_t)-1 if not specified */ off_t src_offset; /* (off_t)-1 if not specified */ int src_fd; int dst_fd; int close_src; int close_dst; off_t total; const char *syserr; int error_no; const char *notimp; rb_fdset_t fds; VALUE th; }; static void * exec_interrupts(void *arg) { VALUE th = (VALUE)arg; rb_thread_execute_interrupts(th); return NULL; } /* * returns TRUE if the preceding system call was interrupted * so we can continue. If the thread was interrupted, we * reacquire the GVL to execute interrupts before continuing. */ static int maygvl_copy_stream_continue_p(int has_gvl, struct copy_stream_struct *stp) { switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif if (rb_thread_interrupted(stp->th)) { if (has_gvl) rb_thread_execute_interrupts(stp->th); else rb_thread_call_with_gvl(exec_interrupts, (void *)stp->th); } return TRUE; } return FALSE; } static int maygvl_select(int has_gvl, int n, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, struct timeval *timeout) { if (has_gvl) return rb_thread_fd_select(n, rfds, wfds, efds, timeout); else return rb_fd_select(n, rfds, wfds, efds, timeout); } static int maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp) { int ret; do { rb_fd_zero(&stp->fds); rb_fd_set(stp->src_fd, &stp->fds); ret = maygvl_select(has_gvl, rb_fd_max(&stp->fds), &stp->fds, NULL, NULL, NULL); } while (ret == -1 && maygvl_copy_stream_continue_p(has_gvl, stp)); if (ret == -1) { stp->syserr = "select"; stp->error_no = errno; return -1; } return 0; } static int nogvl_copy_stream_wait_write(struct copy_stream_struct *stp) { int ret; do { rb_fd_zero(&stp->fds); rb_fd_set(stp->dst_fd, &stp->fds); ret = rb_fd_select(rb_fd_max(&stp->fds), NULL, &stp->fds, NULL, NULL); } while (ret == -1 && maygvl_copy_stream_continue_p(0, stp)); if (ret == -1) { stp->syserr = "select"; stp->error_no = errno; return -1; } return 0; } #ifdef HAVE_SENDFILE # ifdef __linux__ # define USE_SENDFILE # ifdef HAVE_SYS_SENDFILE_H # include # endif static ssize_t simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count) { return sendfile(out_fd, in_fd, offset, (size_t)count); } # elif 0 /* defined(__FreeBSD__) || defined(__DragonFly__) */ || defined(__APPLE__) /* This runs on FreeBSD8.1 r30210, but sendfiles blocks its execution * without cpuset -l 0. */ # define USE_SENDFILE # ifdef HAVE_SYS_UIO_H # include # endif static ssize_t simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count) { int r; off_t pos = offset ? *offset : lseek(in_fd, 0, SEEK_CUR); off_t sbytes; # ifdef __APPLE__ r = sendfile(in_fd, out_fd, pos, &count, NULL, 0); sbytes = count; # else r = sendfile(in_fd, out_fd, pos, (size_t)count, NULL, &sbytes, 0); # endif if (r != 0 && sbytes == 0) return -1; if (offset) { *offset += sbytes; } else { lseek(in_fd, sbytes, SEEK_CUR); } return (ssize_t)sbytes; } # endif #endif #ifdef USE_SENDFILE static int nogvl_copy_stream_sendfile(struct copy_stream_struct *stp) { struct stat src_stat, dst_stat; ssize_t ss; int ret; off_t copy_length; off_t src_offset; int use_pread; ret = fstat(stp->src_fd, &src_stat); if (ret == -1) { stp->syserr = "fstat"; stp->error_no = errno; return -1; } if (!S_ISREG(src_stat.st_mode)) return 0; ret = fstat(stp->dst_fd, &dst_stat); if (ret == -1) { stp->syserr = "fstat"; stp->error_no = errno; return -1; } if ((dst_stat.st_mode & S_IFMT) != S_IFSOCK) return 0; src_offset = stp->src_offset; use_pread = src_offset != (off_t)-1; copy_length = stp->copy_length; if (copy_length == (off_t)-1) { if (use_pread) copy_length = src_stat.st_size - src_offset; else { off_t cur; errno = 0; cur = lseek(stp->src_fd, 0, SEEK_CUR); if (cur == (off_t)-1 && errno) { stp->syserr = "lseek"; stp->error_no = errno; return -1; } copy_length = src_stat.st_size - cur; } } retry_sendfile: # if SIZEOF_OFF_T > SIZEOF_SIZE_T /* we are limited by the 32-bit ssize_t return value on 32-bit */ ss = (copy_length > (off_t)SSIZE_MAX) ? SSIZE_MAX : (ssize_t)copy_length; # else ss = (ssize_t)copy_length; # endif if (use_pread) { ss = simple_sendfile(stp->dst_fd, stp->src_fd, &src_offset, ss); } else { ss = simple_sendfile(stp->dst_fd, stp->src_fd, NULL, ss); } if (0 < ss) { stp->total += ss; copy_length -= ss; if (0 < copy_length) { goto retry_sendfile; } } if (ss == -1) { if (maygvl_copy_stream_continue_p(0, stp)) goto retry_sendfile; switch (errno) { case EINVAL: #ifdef ENOSYS case ENOSYS: #endif return 0; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif #ifndef __linux__ /* * Linux requires stp->src_fd to be a mmap-able (regular) file, * select() reports regular files to always be "ready", so * there is no need to select() on it. * Other OSes may have the same limitation for sendfile() which * allow us to bypass maygvl_copy_stream_wait_read()... */ if (maygvl_copy_stream_wait_read(0, stp) == -1) return -1; #endif if (nogvl_copy_stream_wait_write(stp) == -1) return -1; goto retry_sendfile; } stp->syserr = "sendfile"; stp->error_no = errno; return -1; } return 1; } #endif static ssize_t maygvl_read(int has_gvl, int fd, void *buf, size_t count) { if (has_gvl) return rb_read_internal(fd, buf, count); else return read(fd, buf, count); } static ssize_t maygvl_copy_stream_read(int has_gvl, struct copy_stream_struct *stp, char *buf, size_t len, off_t offset) { ssize_t ss; retry_read: if (offset == (off_t)-1) { ss = maygvl_read(has_gvl, stp->src_fd, buf, len); } else { #ifdef HAVE_PREAD ss = pread(stp->src_fd, buf, len, offset); #else stp->notimp = "pread"; return -1; #endif } if (ss == 0) { return 0; } if (ss == -1) { if (maygvl_copy_stream_continue_p(has_gvl, stp)) goto retry_read; switch (errno) { case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif if (maygvl_copy_stream_wait_read(has_gvl, stp) == -1) return -1; goto retry_read; #ifdef ENOSYS case ENOSYS: #endif stp->notimp = "pread"; return -1; } stp->syserr = offset == (off_t)-1 ? "read" : "pread"; stp->error_no = errno; return -1; } return ss; } static int nogvl_copy_stream_write(struct copy_stream_struct *stp, char *buf, size_t len) { ssize_t ss; int off = 0; while (len) { ss = write(stp->dst_fd, buf+off, len); if (ss == -1) { if (maygvl_copy_stream_continue_p(0, stp)) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { if (nogvl_copy_stream_wait_write(stp) == -1) return -1; continue; } stp->syserr = "write"; stp->error_no = errno; return -1; } off += (int)ss; len -= (int)ss; stp->total += ss; } return 0; } static void nogvl_copy_stream_read_write(struct copy_stream_struct *stp) { char buf[1024*16]; size_t len; ssize_t ss; int ret; off_t copy_length; int use_eof; off_t src_offset; int use_pread; copy_length = stp->copy_length; use_eof = copy_length == (off_t)-1; src_offset = stp->src_offset; use_pread = src_offset != (off_t)-1; if (use_pread && stp->close_src) { off_t r; errno = 0; r = lseek(stp->src_fd, src_offset, SEEK_SET); if (r == (off_t)-1 && errno) { stp->syserr = "lseek"; stp->error_no = errno; return; } src_offset = (off_t)-1; use_pread = 0; } while (use_eof || 0 < copy_length) { if (!use_eof && copy_length < (off_t)sizeof(buf)) { len = (size_t)copy_length; } else { len = sizeof(buf); } if (use_pread) { ss = maygvl_copy_stream_read(0, stp, buf, len, src_offset); if (0 < ss) src_offset += ss; } else { ss = maygvl_copy_stream_read(0, stp, buf, len, (off_t)-1); } if (ss <= 0) /* EOF or error */ return; ret = nogvl_copy_stream_write(stp, buf, ss); if (ret < 0) return; if (!use_eof) copy_length -= ss; } } static VALUE nogvl_copy_stream_func(void *arg) { struct copy_stream_struct *stp = (struct copy_stream_struct *)arg; #ifdef USE_SENDFILE int ret; #endif #ifdef USE_SENDFILE ret = nogvl_copy_stream_sendfile(stp); if (ret != 0) goto finish; /* error or success */ #endif nogvl_copy_stream_read_write(stp); #ifdef USE_SENDFILE finish: #endif return Qnil; } static VALUE copy_stream_fallback_body(VALUE arg) { struct copy_stream_struct *stp = (struct copy_stream_struct *)arg; const int buflen = 16*1024; VALUE n; VALUE buf = rb_str_buf_new(buflen); off_t rest = stp->copy_length; off_t off = stp->src_offset; ID read_method = id_readpartial; if (stp->src_fd == -1) { if (!rb_respond_to(stp->src, read_method)) { read_method = id_read; } } while (1) { long numwrote; long l; if (stp->copy_length == (off_t)-1) { l = buflen; } else { if (rest == 0) break; l = buflen < rest ? buflen : (long)rest; } if (stp->src_fd == -1) { VALUE rc = rb_funcall(stp->src, read_method, 2, INT2FIX(l), buf); if (read_method == id_read && NIL_P(rc)) break; } else { ssize_t ss; rb_thread_wait_fd(stp->src_fd); rb_str_resize(buf, buflen); ss = maygvl_copy_stream_read(1, stp, RSTRING_PTR(buf), l, off); if (ss == -1) return Qnil; if (ss == 0) rb_eof_error(); rb_str_resize(buf, ss); if (off != (off_t)-1) off += ss; } n = rb_io_write(stp->dst, buf); numwrote = NUM2LONG(n); stp->total += numwrote; rest -= numwrote; if (read_method == id_read && RSTRING_LEN(buf) == 0) { break; } } return Qnil; } static VALUE copy_stream_fallback(struct copy_stream_struct *stp) { if (stp->src_fd == -1 && stp->src_offset != (off_t)-1) { rb_raise(rb_eArgError, "cannot specify src_offset for non-IO"); } rb_rescue2(copy_stream_fallback_body, (VALUE)stp, (VALUE (*) (ANYARGS))0, (VALUE)0, rb_eEOFError, (VALUE)0); return Qnil; } static VALUE copy_stream_body(VALUE arg) { struct copy_stream_struct *stp = (struct copy_stream_struct *)arg; VALUE src_io, dst_io; rb_io_t *src_fptr = 0, *dst_fptr = 0; int src_fd, dst_fd; stp->th = rb_thread_current(); stp->total = 0; if (stp->src == argf || !(TYPE(stp->src) == T_FILE || TYPE(stp->src) == T_STRING || rb_respond_to(stp->src, rb_intern("to_path")))) { src_fd = -1; } else { src_io = TYPE(stp->src) == T_FILE ? stp->src : Qnil; if (NIL_P(src_io)) { VALUE args[2]; int oflags = O_RDONLY; #ifdef O_NOCTTY oflags |= O_NOCTTY; #endif FilePathValue(stp->src); args[0] = stp->src; args[1] = INT2NUM(oflags); src_io = rb_class_new_instance(2, args, rb_cFile); stp->src = src_io; stp->close_src = 1; } GetOpenFile(src_io, src_fptr); rb_io_check_byte_readable(src_fptr); src_fd = src_fptr->fd; } stp->src_fd = src_fd; if (stp->dst == argf || !(TYPE(stp->dst) == T_FILE || TYPE(stp->dst) == T_STRING || rb_respond_to(stp->dst, rb_intern("to_path")))) { dst_fd = -1; } else { dst_io = TYPE(stp->dst) == T_FILE ? stp->dst : Qnil; if (NIL_P(dst_io)) { VALUE args[3]; int oflags = O_WRONLY|O_CREAT|O_TRUNC; #ifdef O_NOCTTY oflags |= O_NOCTTY; #endif FilePathValue(stp->dst); args[0] = stp->dst; args[1] = INT2NUM(oflags); args[2] = INT2FIX(0666); dst_io = rb_class_new_instance(3, args, rb_cFile); stp->dst = dst_io; stp->close_dst = 1; } else { dst_io = GetWriteIO(dst_io); stp->dst = dst_io; } GetOpenFile(dst_io, dst_fptr); rb_io_check_writable(dst_fptr); dst_fd = dst_fptr->fd; } stp->dst_fd = dst_fd; #ifdef O_BINARY if (src_fptr) SET_BINARY_MODE_WITH_SEEK_CUR(src_fptr); if (dst_fptr) setmode(dst_fd, O_BINARY); #endif if (stp->src_offset == (off_t)-1 && src_fptr && src_fptr->rbuf.len) { size_t len = src_fptr->rbuf.len; VALUE str; if (stp->copy_length != (off_t)-1 && stp->copy_length < (off_t)len) { len = (size_t)stp->copy_length; } str = rb_str_buf_new(len); rb_str_resize(str,len); read_buffered_data(RSTRING_PTR(str), len, src_fptr); if (dst_fptr) { /* IO or filename */ if (io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), dst_fptr, 0) < 0) rb_sys_fail(0); } else /* others such as StringIO */ rb_io_write(stp->dst, str); stp->total += len; if (stp->copy_length != (off_t)-1) stp->copy_length -= len; } if (dst_fptr && io_fflush(dst_fptr) < 0) { rb_raise(rb_eIOError, "flush failed"); } if (stp->copy_length == 0) return Qnil; if (src_fd == -1 || dst_fd == -1) { return copy_stream_fallback(stp); } rb_fd_set(src_fd, &stp->fds); rb_fd_set(dst_fd, &stp->fds); return rb_thread_blocking_region(nogvl_copy_stream_func, (void*)stp, RUBY_UBF_IO, 0); } static VALUE copy_stream_finalize(VALUE arg) { struct copy_stream_struct *stp = (struct copy_stream_struct *)arg; if (stp->close_src) { rb_io_close_m(stp->src); } if (stp->close_dst) { rb_io_close_m(stp->dst); } rb_fd_term(&stp->fds); if (stp->syserr) { errno = stp->error_no; rb_sys_fail(stp->syserr); } if (stp->notimp) { rb_raise(rb_eNotImpError, "%s() not implemented", stp->notimp); } return Qnil; } /* * call-seq: * IO.copy_stream(src, dst) * IO.copy_stream(src, dst, copy_length) * IO.copy_stream(src, dst, copy_length, src_offset) * * IO.copy_stream copies src to dst. * src and dst is either a filename or an IO. * * This method returns the number of bytes copied. * * If optional arguments are not given, * the start position of the copy is * the beginning of the filename or * the current file offset of the IO. * The end position of the copy is the end of file. * * If copy_length is given, * No more than copy_length bytes are copied. * * If src_offset is given, * it specifies the start position of the copy. * * When src_offset is specified and * src is an IO, * IO.copy_stream doesn't move the current file offset. * */ static VALUE rb_io_s_copy_stream(int argc, VALUE *argv, VALUE io) { VALUE src, dst, length, src_offset; struct copy_stream_struct st; MEMZERO(&st, struct copy_stream_struct, 1); rb_scan_args(argc, argv, "22", &src, &dst, &length, &src_offset); st.src = src; st.dst = dst; if (NIL_P(length)) st.copy_length = (off_t)-1; else st.copy_length = NUM2OFFT(length); if (NIL_P(src_offset)) st.src_offset = (off_t)-1; else st.src_offset = NUM2OFFT(src_offset); rb_fd_init(&st.fds); rb_ensure(copy_stream_body, (VALUE)&st, copy_stream_finalize, (VALUE)&st); return OFFT2NUM(st.total); } /* * call-seq: * io.external_encoding -> encoding * * Returns the Encoding object that represents the encoding of the file. * If io is write mode and no encoding is specified, returns nil. */ static VALUE rb_io_external_encoding(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); if (fptr->encs.enc2) { return rb_enc_from_encoding(fptr->encs.enc2); } if (fptr->mode & FMODE_WRITABLE) { if (fptr->encs.enc) return rb_enc_from_encoding(fptr->encs.enc); return Qnil; } return rb_enc_from_encoding(io_read_encoding(fptr)); } /* * call-seq: * io.internal_encoding -> encoding * * Returns the Encoding of the internal string if conversion is * specified. Otherwise returns nil. */ static VALUE rb_io_internal_encoding(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); if (!fptr->encs.enc2) return Qnil; return rb_enc_from_encoding(io_read_encoding(fptr)); } /* * call-seq: * io.set_encoding(ext_enc) -> io * io.set_encoding("ext_enc:int_enc") -> io * io.set_encoding(ext_enc, int_enc) -> io * io.set_encoding("ext_enc:int_enc", opt) -> io * io.set_encoding(ext_enc, int_enc, opt) -> io * * If single argument is specified, read string from io is tagged * with the encoding specified. If encoding is a colon separated two * encoding names "A:B", the read string is converted from encoding A * (external encoding) to encoding B (internal encoding), then tagged * with B. If two arguments are specified, those must be encoding * objects or encoding names, and the first one is the external encoding, and the * second one is the internal encoding. * If the external encoding and the internal encoding is specified, * optional hash argument specify the conversion option. */ static VALUE rb_io_set_encoding(int argc, VALUE *argv, VALUE io) { rb_io_t *fptr; VALUE v1, v2, opt; if (!RB_TYPE_P(io, T_FILE)) { return rb_funcall2(io, id_set_encoding, argc, argv); } argc = rb_scan_args(argc, argv, "11:", &v1, &v2, &opt); GetOpenFile(io, fptr); io_encoding_set(fptr, v1, v2, opt); return io; } void rb_stdio_set_default_encoding(void) { extern VALUE rb_stdin, rb_stdout, rb_stderr; VALUE val = Qnil; rb_io_set_encoding(1, &val, rb_stdin); rb_io_set_encoding(1, &val, rb_stdout); rb_io_set_encoding(1, &val, rb_stderr); } /* * call-seq: * ARGF.external_encoding -> encoding * * Returns the external encoding for files read from +ARGF+ as an +Encoding+ * object. The external encoding is the encoding of the text as stored in a * file. Contrast with +ARGF.internal_encoding+, which is the encoding used * to represent this text within Ruby. * * To set the external encoding use +ARGF.set_encoding+. * * For example: * * ARGF.external_encoding #=> # * */ static VALUE argf_external_encoding(VALUE argf) { if (!RTEST(ARGF.current_file)) { return rb_enc_from_encoding(rb_default_external_encoding()); } return rb_io_external_encoding(rb_io_check_io(ARGF.current_file)); } /* * call-seq: * ARGF.internal_encoding -> encoding * * Returns the internal encoding for strings read from +ARGF+ as an * +Encoding+ object. * * If +ARGF.set_encoding+ has been called with two encoding names, the second * is returned. Otherwise, if +Encoding.default_external+ has been set, that * value is returned. Failing that, if a default external encoding was * specified on the command-line, that value is used. If the encoding is * unknown, nil is returned. */ static VALUE argf_internal_encoding(VALUE argf) { if (!RTEST(ARGF.current_file)) { return rb_enc_from_encoding(rb_default_external_encoding()); } return rb_io_internal_encoding(rb_io_check_io(ARGF.current_file)); } /* * call-seq: * ARGF.set_encoding(ext_enc) -> ARGF * ARGF.set_encoding("ext_enc:int_enc") -> ARGF * ARGF.set_encoding(ext_enc, int_enc) -> ARGF * ARGF.set_encoding("ext_enc:int_enc", opt) -> ARGF * ARGF.set_encoding(ext_enc, int_enc, opt) -> ARGF * * If single argument is specified, strings read from ARGF are tagged with * the encoding specified. * * If two encoding names separated by a colon are given, e.g. "ascii:utf-8", * the read string is converted from the first encoding (external encoding) * to the second encoding (internal encoding), then tagged with the second * encoding. * * If two arguments are specified, they must be encoding objects or encoding * names. Again, the first specifies the external encoding; the second * specifies the internal encoding. * * If the external encoding and the internal encoding are specified, the * optional +Hash+ argument can be used to adjust the conversion process. The * structure of this hash is explained in the +String#encode+ documentation. * * For example: * * ARGF.set_encoding('ascii') # Tag the input as US-ASCII text * ARGF.set_encoding(Encoding::UTF_8) # Tag the input as UTF-8 text * ARGF.set_encoding('utf-8','ascii') # Transcode the input from US-ASCII * # to UTF-8. */ static VALUE argf_set_encoding(int argc, VALUE *argv, VALUE argf) { rb_io_t *fptr; if (!next_argv()) { rb_raise(rb_eArgError, "no stream to set encoding"); } rb_io_set_encoding(argc, argv, ARGF.current_file); GetOpenFile(ARGF.current_file, fptr); ARGF.encs = fptr->encs; return argf; } /* * call-seq: * ARGF.tell -> Integer * ARGF.pos -> Integer * * Returns the current offset (in bytes) of the current file in +ARGF+. * * ARGF.pos #=> 0 * ARGF.gets #=> "This is line one\n" * ARGF.pos #=> 17 * */ static VALUE argf_tell(VALUE argf) { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to tell"); } ARGF_FORWARD(0, 0); return rb_io_tell(ARGF.current_file); } /* * call-seq: * ARGF.seek(amount, whence=IO::SEEK_SET) -> 0 * * Seeks to offset _amount_ (an +Integer+) in the +ARGF+ stream according to * the value of _whence_. See +IO#seek+ for further details. */ static VALUE argf_seek_m(int argc, VALUE *argv, VALUE argf) { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to seek"); } ARGF_FORWARD(argc, argv); return rb_io_seek_m(argc, argv, ARGF.current_file); } /* * call-seq: * ARGF.pos = position -> Integer * * Seeks to the position given by _position_ (in bytes) in +ARGF+. * * For example: * * ARGF.pos = 17 * ARGF.gets #=> "This is line two\n" */ static VALUE argf_set_pos(VALUE argf, VALUE offset) { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to set position"); } ARGF_FORWARD(1, &offset); return rb_io_set_pos(ARGF.current_file, offset); } /* * call-seq: * ARGF.rewind -> 0 * * Positions the current file to the beginning of input, resetting * +ARGF.lineno+ to zero. * * ARGF.readline #=> "This is line one\n" * ARGF.rewind #=> 0 * ARGF.lineno #=> 0 * ARGF.readline #=> "This is line one\n" */ static VALUE argf_rewind(VALUE argf) { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to rewind"); } ARGF_FORWARD(0, 0); return rb_io_rewind(ARGF.current_file); } /* * call-seq: * ARGF.fileno -> fixnum * ARGF.to_i -> fixnum * * Returns an integer representing the numeric file descriptor for * the current file. Raises an +ArgumentError+ if there isn't a current file. * * ARGF.fileno #=> 3 */ static VALUE argf_fileno(VALUE argf) { if (!next_argv()) { rb_raise(rb_eArgError, "no stream"); } ARGF_FORWARD(0, 0); return rb_io_fileno(ARGF.current_file); } /* * call-seq: * ARGF.to_io -> IO * * Returns an +IO+ object representing the current file. This will be a * +File+ object unless the current file is a stream such as STDIN. * * For example: * * ARGF.to_io #=> # * ARGF.to_io #=> #> */ static VALUE argf_to_io(VALUE argf) { next_argv(); ARGF_FORWARD(0, 0); return ARGF.current_file; } /* * call-seq: * ARGF.eof? -> true or false * ARGF.eof -> true or false * * Returns true if the current file in +ARGF+ is at end of file, i.e. it has * no data to read. The stream must be opened for reading or an +IOError+ * will be raised. * * $ echo "eof" | ruby argf.rb * * ARGF.eof? #=> false * 3.times { ARGF.readchar } * ARGF.eof? #=> false * ARGF.readchar #=> "\n" * ARGF.eof? #=> true */ static VALUE argf_eof(VALUE argf) { next_argv(); if (RTEST(ARGF.current_file)) { if (ARGF.init_p == 0) return Qtrue; next_argv(); ARGF_FORWARD(0, 0); if (rb_io_eof(ARGF.current_file)) { return Qtrue; } } return Qfalse; } /* * call-seq: * ARGF.read([length [, buffer]]) -> string, buffer, or nil * * Reads _length_ bytes from ARGF. The files named on the command line * are concatenated and treated as a single file by this method, so when * called without arguments the contents of this pseudo file are returned in * their entirety. * * _length_ must be a non-negative integer or nil. If it is a positive * integer, +read+ tries to read at most _length_ bytes. It returns nil * if an EOF was encountered before anything could be read. Fewer than * _length_ bytes may be returned if an EOF is encountered during the read. * * If _length_ is omitted or is _nil_, it reads until EOF. A String is * returned even if EOF is encountered before any data is read. * * If _length_ is zero, it returns _""_. * * If the optional _buffer_ argument is present, it must reference a String, * which will receive the data. * * For example: * * $ echo "small" > small.txt * $ echo "large" > large.txt * $ ./glark.rb small.txt large.txt * * ARGF.read #=> "small\nlarge" * ARGF.read(200) #=> "small\nlarge" * ARGF.read(2) #=> "sm" * ARGF.read(0) #=> "" * * Note that this method behaves like fread() function in C. If you need the * behavior like read(2) system call, consider +ARGF.readpartial+. */ static VALUE argf_read(int argc, VALUE *argv, VALUE argf) { VALUE tmp, str, length; long len = 0; rb_scan_args(argc, argv, "02", &length, &str); if (!NIL_P(length)) { len = NUM2LONG(argv[0]); } if (!NIL_P(str)) { StringValue(str); rb_str_resize(str,0); argv[1] = Qnil; } retry: if (!next_argv()) { return str; } if (ARGF_GENERIC_INPUT_P()) { tmp = argf_forward(argc, argv, argf); } else { tmp = io_read(argc, argv, ARGF.current_file); } if (NIL_P(str)) str = tmp; else if (!NIL_P(tmp)) rb_str_append(str, tmp); if (NIL_P(tmp) || NIL_P(length)) { if (ARGF.next_p != -1) { argf_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } } else if (argc >= 1) { if (RSTRING_LEN(str) < len) { len -= RSTRING_LEN(str); argv[0] = INT2NUM(len); goto retry; } } return str; } struct argf_call_arg { int argc; VALUE *argv; VALUE argf; }; static VALUE argf_forward_call(VALUE arg) { struct argf_call_arg *p = (struct argf_call_arg *)arg; argf_forward(p->argc, p->argv, p->argf); return Qnil; } static VALUE argf_getpartial(int argc, VALUE *argv, VALUE argf, int nonblock); /* * call-seq: * ARGF.readpartial(maxlen) -> string * ARGF.readpartial(maxlen, outbuf) -> outbuf * * Reads at most _maxlen_ bytes from the ARGF stream. It blocks only if * +ARGF+ has no data immediately available. If the optional _outbuf_ * argument is present, it must reference a String, which will receive the * data. It raises EOFError on end of file. * * +readpartial+ is designed for streams such as pipes, sockets, and ttys. It * blocks only when no data is 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 has not reached EOF. * * When +readpartial+ blocks, it waits for data or EOF. If some data is read, * +readpartial+ returns with the data. If EOF is reached, readpartial raises * an +EOFError+. * * When +readpartial+ doesn't block, 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. If the * stream reaches EOF an +EOFError+ is raised. */ static VALUE argf_readpartial(int argc, VALUE *argv, VALUE argf) { return argf_getpartial(argc, argv, argf, 0); } /* * call-seq: * ARGF.read_nonblock(maxlen) -> string * ARGF.read_nonblock(maxlen, outbuf) -> outbuf * * Reads at most _maxlen_ bytes from the ARGF stream in non-blocking mode. */ static VALUE argf_read_nonblock(int argc, VALUE *argv, VALUE argf) { return argf_getpartial(argc, argv, argf, 1); } static VALUE argf_getpartial(int argc, VALUE *argv, VALUE argf, int nonblock) { VALUE tmp, str, length; rb_scan_args(argc, argv, "11", &length, &str); if (!NIL_P(str)) { StringValue(str); argv[1] = str; } if (!next_argv()) { rb_str_resize(str, 0); rb_eof_error(); } if (ARGF_GENERIC_INPUT_P()) { struct argf_call_arg arg; arg.argc = argc; arg.argv = argv; arg.argf = argf; tmp = rb_rescue2(argf_forward_call, (VALUE)&arg, RUBY_METHOD_FUNC(0), Qnil, rb_eEOFError, (VALUE)0); } else { tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock); } if (NIL_P(tmp)) { if (ARGF.next_p == -1) { rb_eof_error(); } argf_close(ARGF.current_file); ARGF.next_p = 1; if (RARRAY_LEN(ARGF.argv) == 0) rb_eof_error(); if (NIL_P(str)) str = rb_str_new(NULL, 0); return str; } return tmp; } /* * call-seq: * ARGF.getc -> String or nil * * Reads the next character from +ARGF+ and returns it as a +String+. Returns * +nil+ at the end of the stream. * * +ARGF+ treats the files named on the command line as a single file created * by concatenating their contents. After returning the last character of the * first file, it returns the first character of the second file, and so on. * * For example: * * $ echo "foo" > file * $ ruby argf.rb file * * ARGF.getc #=> "f" * ARGF.getc #=> "o" * ARGF.getc #=> "o" * ARGF.getc #=> "\n" * ARGF.getc #=> nil * ARGF.getc #=> nil */ static VALUE argf_getc(VALUE argf) { VALUE ch; retry: if (!next_argv()) return Qnil; if (ARGF_GENERIC_INPUT_P()) { ch = rb_funcall3(ARGF.current_file, rb_intern("getc"), 0, 0); } else { ch = rb_io_getc(ARGF.current_file); } if (NIL_P(ch) && ARGF.next_p != -1) { argf_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } return ch; } /* * call-seq: * ARGF.getbyte -> Fixnum or nil * * Gets the next 8-bit byte (0..255) from +ARGF+. Returns +nil+ if called at * the end of the stream. * * For example: * * $ echo "foo" > file * $ ruby argf.rb file * * ARGF.getbyte #=> 102 * ARGF.getbyte #=> 111 * ARGF.getbyte #=> 111 * ARGF.getbyte #=> 10 * ARGF.getbyte #=> nil */ static VALUE argf_getbyte(VALUE argf) { VALUE ch; retry: if (!next_argv()) return Qnil; if (TYPE(ARGF.current_file) != T_FILE) { ch = rb_funcall3(ARGF.current_file, rb_intern("getbyte"), 0, 0); } else { ch = rb_io_getbyte(ARGF.current_file); } if (NIL_P(ch) && ARGF.next_p != -1) { argf_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } return ch; } /* * call-seq: * ARGF.readchar -> String or nil * * Reads the next character from +ARGF+ and returns it as a +String+. Raises * an +EOFError+ after the last character of the last file has been read. * * For example: * * $ echo "foo" > file * $ ruby argf.rb file * * ARGF.readchar #=> "f" * ARGF.readchar #=> "o" * ARGF.readchar #=> "o" * ARGF.readchar #=> "\n" * ARGF.readchar #=> end of file reached (EOFError) */ static VALUE argf_readchar(VALUE argf) { VALUE ch; retry: if (!next_argv()) rb_eof_error(); if (TYPE(ARGF.current_file) != T_FILE) { ch = rb_funcall3(ARGF.current_file, rb_intern("getc"), 0, 0); } else { ch = rb_io_getc(ARGF.current_file); } if (NIL_P(ch) && ARGF.next_p != -1) { argf_close(ARGF.current_file); ARGF.next_p = 1; goto retry; } return ch; } /* * call-seq: * ARGF.readbyte -> Fixnum * * Reads the next 8-bit byte from ARGF and returns it as a +Fixnum+. Raises * an +EOFError+ after the last byte of the last file has been read. * * For example: * * $ echo "foo" > file * $ ruby argf.rb file * * ARGF.readbyte #=> 102 * ARGF.readbyte #=> 111 * ARGF.readbyte #=> 111 * ARGF.readbyte #=> 10 * ARGF.readbyte #=> end of file reached (EOFError) */ static VALUE argf_readbyte(VALUE argf) { VALUE c; NEXT_ARGF_FORWARD(0, 0); c = argf_getbyte(argf); if (NIL_P(c)) { rb_eof_error(); } return c; } /* * call-seq: * ARGF.each(sep=$/) {|line| block } -> ARGF * ARGF.each(sep=$/,limit) {|line| block } -> ARGF * ARGF.each(...) -> an_enumerator * * ARGF.each_line(sep=$/) {|line| block } -> ARGF * ARGF.each_line(sep=$/,limit) {|line| block } -> ARGF * ARGF.each_line(...) -> an_enumerator * * ARGF.lines(sep=$/) {|line| block } -> ARGF * ARGF.lines(sep=$/,limit) {|line| block } -> ARGF * ARGF.lines(...) -> an_enumerator * * Returns an enumerator which iterates over each line (separated by _sep_, * which defaults to your platform's newline character) of each file in * +ARGV+. If a block is supplied, each line in turn will be yielded to the * block, otherwise an enumerator is returned. * The optional _limit_ argument is a +Fixnum+ specifying the maximum * length of each line; longer lines will be split according to this limit. * * This method allows you to treat the files supplied on the command line as * a single file consisting of the concatenation of each named file. After * the last line of the first file has been returned, the first line of the * second file is returned. The +ARGF.filename+ and +ARGF.lineno+ methods can * be used to determine the filename and line number, respectively, of the * current line. * * For example, the following code prints out each line of each named file * prefixed with its line number, displaying the filename once per file: * * ARGF.lines do |line| * puts ARGF.filename if ARGF.lineno == 1 * puts "#{ARGF.lineno}: #{line}" * end */ static VALUE argf_each_line(int argc, VALUE *argv, VALUE argf) { RETURN_ENUMERATOR(argf, argc, argv); for (;;) { if (!next_argv()) return argf; rb_block_call(ARGF.current_file, rb_intern("each_line"), argc, argv, 0, 0); ARGF.next_p = 1; } } /* * call-seq: * ARGF.bytes {|byte| block } -> ARGF * ARGF.bytes -> an_enumerator * * ARGF.each_byte {|byte| block } -> ARGF * ARGF.each_byte -> an_enumerator * * Iterates over each byte of each file in +ARGV+. * A byte is returned as a +Fixnum+ in the range 0..255. * * This method allows you to treat the files supplied on the command line as * a single file consisting of the concatenation of each named file. After * the last byte of the first file has been returned, the first byte of the * second file is returned. The +ARGF.filename+ method can be used to * determine the filename of the current byte. * * If no block is given, an enumerator is returned instead. * * For example: * * ARGF.bytes.to_a #=> [35, 32, ... 95, 10] * */ static VALUE argf_each_byte(VALUE argf) { RETURN_ENUMERATOR(argf, 0, 0); for (;;) { if (!next_argv()) return argf; rb_block_call(ARGF.current_file, rb_intern("each_byte"), 0, 0, 0, 0); ARGF.next_p = 1; } } /* * call-seq: * ARGF.chars {|char| block } -> ARGF * ARGF.chars -> an_enumerator * * ARGF.each_char {|char| block } -> ARGF * ARGF.each_char -> an_enumerator * * Iterates over each character of each file in +ARGF+. * * This method allows you to treat the files supplied on the command line as * a single file consisting of the concatenation of each named file. After * the last character of the first file has been returned, the first * character of the second file is returned. The +ARGF.filename+ method can * be used to determine the name of the file in which the current character * appears. * * If no block is given, an enumerator is returned instead. */ static VALUE argf_each_char(VALUE argf) { RETURN_ENUMERATOR(argf, 0, 0); for (;;) { if (!next_argv()) return argf; rb_block_call(ARGF.current_file, rb_intern("each_char"), 0, 0, 0, 0); ARGF.next_p = 1; } } /* * call-seq: * ARGF.filename -> String * ARGF.path -> String * * Returns the current filename. "-" is returned when the current file is * STDIN. * * For example: * * $ echo "foo" > foo * $ echo "bar" > bar * $ echo "glark" > glark * * $ ruby argf.rb foo bar glark * * ARGF.filename #=> "foo" * ARGF.read(5) #=> "foo\nb" * ARGF.filename #=> "bar" * ARGF.skip * ARGF.filename #=> "glark" */ static VALUE argf_filename(VALUE argf) { next_argv(); return ARGF.filename; } static VALUE argf_filename_getter(ID id, VALUE *var) { return argf_filename(*var); } /* * call-seq: * ARGF.file -> IO or File object * * Returns the current file as an +IO+ or +File+ object. #> is * returned when the current file is STDIN. * * For example: * * $ echo "foo" > foo * $ echo "bar" > bar * * $ ruby argf.rb foo bar * * ARGF.file #=> # * ARGF.read(5) #=> "foo\nb" * ARGF.file #=> # */ static VALUE argf_file(VALUE argf) { next_argv(); return ARGF.current_file; } /* * call-seq: * ARGF.binmode -> ARGF * * Puts +ARGF+ into binary mode. Once a stream is in binary mode, it cannot * be reset to non-binary mode. This option has the following effects: * * * Newline conversion is disabled. * * Encoding conversion is disabled. * * Content is treated as ASCII-8BIT. */ static VALUE argf_binmode_m(VALUE argf) { ARGF.binmode = 1; next_argv(); ARGF_FORWARD(0, 0); rb_io_ascii8bit_binmode(ARGF.current_file); return argf; } /* * call-seq: * ARGF.binmode? -> true or false * * Returns true if +ARGF+ is being read in binary mode; false otherwise. (To * enable binary mode use +ARGF.binmode+. * * For example: * * ARGF.binmode? #=> false * ARGF.binmode * ARGF.binmode? #=> true */ static VALUE argf_binmode_p(VALUE argf) { return ARGF.binmode ? Qtrue : Qfalse; } /* * call-seq: * ARGF.skip -> ARGF * * Sets the current file to the next file in ARGV. If there aren't any more * files it has no effect. * * For example: * * $ ruby argf.rb foo bar * ARGF.filename #=> "foo" * ARGF.skip * ARGF.filename #=> "bar" */ static VALUE argf_skip(VALUE argf) { if (ARGF.init_p && ARGF.next_p == 0) { argf_close(ARGF.current_file); ARGF.next_p = 1; } return argf; } /* * call-seq: * ARGF.close -> ARGF * * Closes the current file and skips to the next in the stream. Trying to * close a file that has already been closed causes an +IOError+ to be * raised. * * For example: * * $ ruby argf.rb foo bar * * ARGF.filename #=> "foo" * ARGF.close * ARGF.filename #=> "bar" * ARGF.close * ARGF.close #=> closed stream (IOError) */ static VALUE argf_close_m(VALUE argf) { next_argv(); argf_close(ARGF.current_file); if (ARGF.next_p != -1) { ARGF.next_p = 1; } ARGF.lineno = 0; return argf; } /* * call-seq: * ARGF.closed? -> true or false * * Returns _true_ if the current file has been closed; _false_ otherwise. Use * +ARGF.close+ to actually close the current file. */ static VALUE argf_closed(VALUE argf) { next_argv(); ARGF_FORWARD(0, 0); return rb_io_closed(ARGF.current_file); } /* * call-seq: * ARGF.to_s -> String * * Returns "ARGF". */ static VALUE argf_to_s(VALUE argf) { return rb_str_new2("ARGF"); } /* * call-seq: * ARGF.inplace_mode -> String * * Returns the file extension appended to the names of modified files under * inplace-edit mode. This value can be set using +ARGF.inplace_mode=+ or * passing the +-i+ switch to the Ruby binary. */ static VALUE argf_inplace_mode_get(VALUE argf) { if (!ARGF.inplace) return Qnil; return rb_str_new2(ARGF.inplace); } static VALUE opt_i_get(ID id, VALUE *var) { return argf_inplace_mode_get(*var); } /* * call-seq: * ARGF.inplace_mode = ext -> ARGF * * Sets the filename extension for inplace editing mode to the given String. * Each file being edited has this value appended to its filename. The * modified file is saved under this new name. * * For example: * * $ ruby argf.rb file.txt * * ARGF.inplace_mode = '.bak' * ARGF.lines do |line| * print line.sub("foo","bar") * end * * Each line of _file.txt_ has the first occurrence of "foo" replaced with * "bar", then the new line is written out to _file.txt.bak_. */ static VALUE argf_inplace_mode_set(VALUE argf, VALUE val) { if (rb_safe_level() >= 1 && OBJ_TAINTED(val)) rb_insecure_operation(); if (!RTEST(val)) { if (ARGF.inplace) free(ARGF.inplace); ARGF.inplace = 0; } else { StringValue(val); if (ARGF.inplace) free(ARGF.inplace); ARGF.inplace = 0; ARGF.inplace = strdup(RSTRING_PTR(val)); } return argf; } static void opt_i_set(VALUE val, ID id, VALUE *var) { argf_inplace_mode_set(*var, val); } const char * ruby_get_inplace_mode(void) { return ARGF.inplace; } void ruby_set_inplace_mode(const char *suffix) { if (ARGF.inplace) free(ARGF.inplace); ARGF.inplace = 0; if (suffix) ARGF.inplace = strdup(suffix); } /* * call-seq: * ARGF.argv -> ARGV * * Returns the +ARGV+ array, which contains the arguments passed to your * script, one per element. * * For example: * * $ ruby argf.rb -v glark.txt * * ARGF.argv #=> ["-v", "glark.txt"] * */ static VALUE argf_argv(VALUE argf) { return ARGF.argv; } static VALUE argf_argv_getter(ID id, VALUE *var) { return argf_argv(*var); } VALUE rb_get_argv(void) { return ARGF.argv; } /* * call-seq: * ARGF.to_write_io -> io * * Returns IO instance tied to _ARGF_ for writing if inplace mode is * enabled. */ static VALUE argf_write_io(VALUE argf) { if (!RTEST(ARGF.current_file)) { rb_raise(rb_eIOError, "not opened for writing"); } return GetWriteIO(ARGF.current_file); } /* * call-seq: * ARGF.write(string) -> integer * * Writes _string_ if inplace mode. */ static VALUE argf_write(VALUE argf, VALUE str) { return rb_io_write(argf_write_io(argf), str); } /* * Document-class: IOError * * Raised when an IO operation fails. * * File.open("/etc/hosts") {|f| f << "example"} * #=> IOError: not opened for writing * * File.open("/etc/hosts") {|f| f.close; f.read } * #=> IOError: closed stream * * Note that some IO failures raise +SystemCallError+s and these are not * subclasses of IOError: * * File.open("does/not/exist") * #=> Errno::ENOENT: No such file or directory - does/not/exist */ /* * Document-class: EOFError * * Raised by some IO operations when reaching the end of file. Many IO * methods exist in two forms, * * one that returns +nil+ when the end of file is reached, the other * raises EOFError +EOFError+. * * +EOFError+ is a subclass of +IOError+. * * file = File.open("/etc/hosts") * file.read * file.gets #=> nil * file.readline #=> EOFError: end of file reached */ /* * Document-class: ARGF * * +ARGF+ is a stream designed for use in scripts that process files given as * command-line arguments or passed in via STDIN. * * The arguments passed to your script are stored in the +ARGV+ Array, one * argument per element. +ARGF+ assumes that any arguments that aren't * filenames have been removed from +ARGV+. For example: * * $ ruby argf.rb --verbose file1 file2 * * ARGV #=> ["--verbose", "file1", "file2"] * option = ARGV.shift #=> "--verbose" * ARGV #=> ["file1", "file2"] * * You can now use +ARGF+ to work with a concatenation of each of these named * files. For instance, +ARGF.read+ will return the contents of _file1_ * followed by the contents of _file2_. * * After a file in +ARGV+ has been read +ARGF+ removes it from the Array. * Thus, after all files have been read +ARGV+ will be empty. * * You can manipulate +ARGV+ yourself to control what +ARGF+ operates on. If * you remove a file from +ARGV+, it is ignored by +ARGF+; if you add files to * +ARGV+, they are treated as if they were named on the command line. For * example: * * ARGV.replace ["file1"] * ARGF.readlines # Returns the contents of file1 as an Array * ARGV #=> [] * ARGV.replace ["file2", "file3"] * ARGF.read # Returns the contents of file2 and file3 * * If +ARGV+ is empty, +ARGF+ acts as if it contained STDIN, i.e. the data * piped to your script. For example: * * $ echo "glark" | ruby -e 'p ARGF.read' * "glark\n" */ /* * Class IO is the basis for all input and output in Ruby. * An I/O stream may be duplexed (that is, bidirectional), and * so may use more than one native operating system stream. * * Many of the examples in this section use class File, * the only standard subclass of IO. The two classes are * closely associated. * * As used in this section, portname may take any of the * following forms. * * * A plain string represents a filename suitable for the underlying * operating system. * * * A string starting with ``|'' indicates a subprocess. * The remainder of the string following the ``|'' is * invoked as a process with appropriate input/output channels * connected to it. * * * A string equal to ``|-'' will create another Ruby * instance as a subprocess. * * Ruby will convert pathnames between different operating system * conventions if possible. For instance, on a Windows system the * filename ``/gumby/ruby/test.rb'' will be opened as * ``\gumby\ruby\test.rb''. When specifying a * Windows-style filename in a Ruby string, remember to escape the * backslashes: * * "c:\\gumby\\ruby\\test.rb" * * Our examples here will use the Unix-style forward slashes; * File::SEPARATOR can be used to get the * platform-specific separator character. * * I/O ports may be opened in any one of several different modes, which * are shown in this section as mode. The mode may * either be a Fixnum or a String. If numeric, it should be * one of the operating system specific constants (O_RDONLY, * O_WRONLY, O_RDWR, O_APPEND and so on). See man open(2) for * more information. * * If the mode is given as a String, it must be one of the * values listed in the following table. * * Mode | Meaning * -----+-------------------------------------------------------- * "r" | Read-only, starts at beginning of file (default mode). * -----+-------------------------------------------------------- * "r+" | Read-write, starts at beginning of file. * -----+-------------------------------------------------------- * "w" | Write-only, truncates existing file * | to zero length or creates a new file for writing. * -----+-------------------------------------------------------- * "w+" | Read-write, truncates existing file to zero length * | or creates a new file for reading and writing. * -----+-------------------------------------------------------- * "a" | Write-only, starts at end of file if file exists, * | otherwise creates a new file for writing. * -----+-------------------------------------------------------- * "a+" | Read-write, starts at end of file if file exists, * | otherwise creates a new file for reading and * | writing. * -----+-------------------------------------------------------- * "b" | Binary file mode (may appear with * | any of the key letters listed above). * | Suppresses EOL <-> CRLF conversion on Windows. And * | sets external encoding to ASCII-8BIT unless explicitly * | specified. * -----+-------------------------------------------------------- * "t" | Text file mode (may appear with * | any of the key letters listed above except "b"). * * * The global constant ARGF (also accessible as $<) provides an * IO-like stream which allows access to all files mentioned on the * command line (or STDIN if no files are mentioned). ARGF provides * the methods #path and #filename to access * the name of the file currently being read. * * == io/console * * The io/console extension provides methods for interacting with the * console. The console can be accessed from IO.console or * the standard input/output/error IO objects. * * Requiring io/console adds the following methods: * * * IO::console * * IO#raw * * IO#raw! * * IO#cooked * * IO#cooked! * * IO#getch * * IO#echo= * * IO#echo? * * IO#noecho * * IO#winsize * * IO#winsize= * * IO#iflush * * IO#ioflush * * IO#oflush * * Example: * * require 'io/console' * rows, columns = $stdin.winsize * puts "You screen is #{columns} wide and #{rows} tall" */ void Init_IO(void) { #undef rb_intern #define rb_intern(str) rb_intern_const(str) VALUE rb_cARGF; #ifdef __CYGWIN__ #include static struct __cygwin_perfile pf[] = { {"", O_RDONLY | O_BINARY}, {"", O_WRONLY | O_BINARY}, {"", O_RDWR | O_BINARY}, {"", O_APPEND | O_BINARY}, {NULL, 0} }; cygwin_internal(CW_PERFILE, pf); #endif rb_eIOError = rb_define_class("IOError", rb_eStandardError); rb_eEOFError = rb_define_class("EOFError", rb_eIOError); id_write = rb_intern("write"); id_read = rb_intern("read"); id_getc = rb_intern("getc"); id_flush = rb_intern("flush"); id_readpartial = rb_intern("readpartial"); id_set_encoding = rb_intern("set_encoding"); rb_define_global_function("syscall", rb_f_syscall, -1); rb_define_global_function("open", rb_f_open, -1); rb_define_global_function("printf", rb_f_printf, -1); rb_define_global_function("print", rb_f_print, -1); rb_define_global_function("putc", rb_f_putc, 1); rb_define_global_function("puts", rb_f_puts, -1); rb_define_global_function("gets", rb_f_gets, -1); rb_define_global_function("readline", rb_f_readline, -1); rb_define_global_function("select", rb_f_select, -1); rb_define_global_function("readlines", rb_f_readlines, -1); rb_define_global_function("`", rb_f_backquote, 1); rb_define_global_function("p", rb_f_p, -1); rb_define_method(rb_mKernel, "display", rb_obj_display, -1); rb_cIO = rb_define_class("IO", rb_cObject); rb_include_module(rb_cIO, rb_mEnumerable); rb_mWaitReadable = rb_define_module_under(rb_cIO, "WaitReadable"); rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #if 0 /* This is necessary only for forcing rdoc handle File::open */ rb_define_singleton_method(rb_cFile, "open", rb_io_s_open, -1); #endif rb_define_alloc_func(rb_cIO, io_alloc); rb_define_singleton_method(rb_cIO, "new", rb_io_s_new, -1); rb_define_singleton_method(rb_cIO, "open", rb_io_s_open, -1); rb_define_singleton_method(rb_cIO, "sysopen", rb_io_s_sysopen, -1); rb_define_singleton_method(rb_cIO, "for_fd", rb_io_s_for_fd, -1); rb_define_singleton_method(rb_cIO, "popen", rb_io_s_popen, -1); rb_define_singleton_method(rb_cIO, "foreach", rb_io_s_foreach, -1); rb_define_singleton_method(rb_cIO, "readlines", rb_io_s_readlines, -1); rb_define_singleton_method(rb_cIO, "read", rb_io_s_read, -1); rb_define_singleton_method(rb_cIO, "binread", rb_io_s_binread, -1); rb_define_singleton_method(rb_cIO, "write", rb_io_s_write, -1); rb_define_singleton_method(rb_cIO, "binwrite", rb_io_s_binwrite, -1); rb_define_singleton_method(rb_cIO, "select", rb_f_select, -1); rb_define_singleton_method(rb_cIO, "pipe", rb_io_s_pipe, -1); rb_define_singleton_method(rb_cIO, "try_convert", rb_io_s_try_convert, 1); rb_define_singleton_method(rb_cIO, "copy_stream", rb_io_s_copy_stream, -1); rb_define_method(rb_cIO, "initialize", rb_io_initialize, -1); rb_output_fs = Qnil; rb_define_hooked_variable("$,", &rb_output_fs, 0, rb_str_setter); rb_rs = rb_default_rs = rb_usascii_str_new2("\n"); rb_gc_register_mark_object(rb_default_rs); rb_output_rs = Qnil; OBJ_FREEZE(rb_default_rs); /* avoid modifying RS_default */ rb_define_hooked_variable("$/", &rb_rs, 0, rb_str_setter); rb_define_hooked_variable("$-0", &rb_rs, 0, rb_str_setter); rb_define_hooked_variable("$\\", &rb_output_rs, 0, rb_str_setter); rb_define_virtual_variable("$_", rb_lastline_get, rb_lastline_set); rb_define_method(rb_cIO, "initialize_copy", rb_io_init_copy, 1); rb_define_method(rb_cIO, "reopen", rb_io_reopen, -1); rb_define_method(rb_cIO, "print", rb_io_print, -1); rb_define_method(rb_cIO, "putc", rb_io_putc, 1); rb_define_method(rb_cIO, "puts", rb_io_puts, -1); rb_define_method(rb_cIO, "printf", rb_io_printf, -1); rb_define_method(rb_cIO, "each", rb_io_each_line, -1); rb_define_method(rb_cIO, "each_line", rb_io_each_line, -1); rb_define_method(rb_cIO, "each_byte", rb_io_each_byte, 0); rb_define_method(rb_cIO, "each_char", rb_io_each_char, 0); rb_define_method(rb_cIO, "each_codepoint", rb_io_each_codepoint, 0); rb_define_method(rb_cIO, "lines", rb_io_each_line, -1); rb_define_method(rb_cIO, "bytes", rb_io_each_byte, 0); rb_define_method(rb_cIO, "chars", rb_io_each_char, 0); rb_define_method(rb_cIO, "codepoints", rb_io_each_codepoint, 0); rb_define_method(rb_cIO, "syswrite", rb_io_syswrite, 1); rb_define_method(rb_cIO, "sysread", rb_io_sysread, -1); rb_define_method(rb_cIO, "fileno", rb_io_fileno, 0); rb_define_alias(rb_cIO, "to_i", "fileno"); rb_define_method(rb_cIO, "to_io", rb_io_to_io, 0); rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0); rb_define_method(rb_cIO, "fdatasync", rb_io_fdatasync, 0); rb_define_method(rb_cIO, "sync", rb_io_sync, 0); rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1); rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0); rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1); rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); rb_define_method(rb_cIO, "read_nonblock", io_read_nonblock, -1); rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, 1); rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, 1); rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); rb_define_method(rb_cIO, "readline", rb_io_readline, -1); rb_define_method(rb_cIO, "getc", rb_io_getc, 0); rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0); rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0); rb_define_method(rb_cIO, "readbyte", rb_io_readbyte, 0); rb_define_method(rb_cIO, "ungetbyte",rb_io_ungetbyte, 1); rb_define_method(rb_cIO, "ungetc",rb_io_ungetc, 1); rb_define_method(rb_cIO, "<<", rb_io_addstr, 1); rb_define_method(rb_cIO, "flush", rb_io_flush, 0); rb_define_method(rb_cIO, "tell", rb_io_tell, 0); rb_define_method(rb_cIO, "seek", rb_io_seek_m, -1); rb_define_const(rb_cIO, "SEEK_SET", INT2FIX(SEEK_SET)); rb_define_const(rb_cIO, "SEEK_CUR", INT2FIX(SEEK_CUR)); rb_define_const(rb_cIO, "SEEK_END", INT2FIX(SEEK_END)); rb_define_method(rb_cIO, "rewind", rb_io_rewind, 0); rb_define_method(rb_cIO, "pos", rb_io_tell, 0); rb_define_method(rb_cIO, "pos=", rb_io_set_pos, 1); rb_define_method(rb_cIO, "eof", rb_io_eof, 0); rb_define_method(rb_cIO, "eof?", rb_io_eof, 0); rb_define_method(rb_cIO, "close_on_exec?", rb_io_close_on_exec_p, 0); rb_define_method(rb_cIO, "close_on_exec=", rb_io_set_close_on_exec, 1); rb_define_method(rb_cIO, "close", rb_io_close_m, 0); rb_define_method(rb_cIO, "closed?", rb_io_closed, 0); rb_define_method(rb_cIO, "close_read", rb_io_close_read, 0); rb_define_method(rb_cIO, "close_write", rb_io_close_write, 0); rb_define_method(rb_cIO, "isatty", rb_io_isatty, 0); rb_define_method(rb_cIO, "tty?", rb_io_isatty, 0); rb_define_method(rb_cIO, "binmode", rb_io_binmode_m, 0); rb_define_method(rb_cIO, "binmode?", rb_io_binmode_p, 0); rb_define_method(rb_cIO, "sysseek", rb_io_sysseek, -1); rb_define_method(rb_cIO, "advise", rb_io_advise, -1); rb_define_method(rb_cIO, "ioctl", rb_io_ioctl, -1); rb_define_method(rb_cIO, "fcntl", rb_io_fcntl, -1); rb_define_method(rb_cIO, "pid", rb_io_pid, 0); rb_define_method(rb_cIO, "inspect", rb_io_inspect, 0); rb_define_method(rb_cIO, "external_encoding", rb_io_external_encoding, 0); rb_define_method(rb_cIO, "internal_encoding", rb_io_internal_encoding, 0); rb_define_method(rb_cIO, "set_encoding", rb_io_set_encoding, -1); rb_define_method(rb_cIO, "autoclose?", rb_io_autoclose_p, 0); rb_define_method(rb_cIO, "autoclose=", rb_io_set_autoclose, 1); rb_define_variable("$stdin", &rb_stdin); rb_stdin = prep_stdio(stdin, FMODE_READABLE, rb_cIO, ""); rb_define_hooked_variable("$stdout", &rb_stdout, 0, stdout_setter); rb_stdout = prep_stdio(stdout, FMODE_WRITABLE, rb_cIO, ""); rb_define_hooked_variable("$stderr", &rb_stderr, 0, stdout_setter); rb_stderr = prep_stdio(stderr, FMODE_WRITABLE|FMODE_SYNC, rb_cIO, ""); rb_define_hooked_variable("$>", &rb_stdout, 0, stdout_setter); orig_stdout = rb_stdout; rb_deferr = orig_stderr = rb_stderr; /* Holds the original stdin */ rb_define_global_const("STDIN", rb_stdin); /* Holds the original stdout */ rb_define_global_const("STDOUT", rb_stdout); /* Holds the original stderr */ rb_define_global_const("STDERR", rb_stderr); /* * Hack to get rdoc to regard ARGF as a class: * rb_cARGF = rb_define_class("ARGF", rb_cObject); */ rb_cARGF = rb_class_new(rb_cObject); rb_set_class_path(rb_cARGF, rb_cObject, "ARGF.class"); rb_define_alloc_func(rb_cARGF, argf_alloc); rb_include_module(rb_cARGF, rb_mEnumerable); rb_define_method(rb_cARGF, "initialize", argf_initialize, -2); rb_define_method(rb_cARGF, "initialize_copy", argf_initialize_copy, 1); rb_define_method(rb_cARGF, "to_s", argf_to_s, 0); rb_define_method(rb_cARGF, "argv", argf_argv, 0); rb_define_method(rb_cARGF, "fileno", argf_fileno, 0); rb_define_method(rb_cARGF, "to_i", argf_fileno, 0); rb_define_method(rb_cARGF, "to_io", argf_to_io, 0); rb_define_method(rb_cARGF, "to_write_io", argf_write_io, 0); rb_define_method(rb_cARGF, "each", argf_each_line, -1); rb_define_method(rb_cARGF, "each_line", argf_each_line, -1); rb_define_method(rb_cARGF, "each_byte", argf_each_byte, 0); rb_define_method(rb_cARGF, "each_char", argf_each_char, 0); rb_define_method(rb_cARGF, "lines", argf_each_line, -1); rb_define_method(rb_cARGF, "bytes", argf_each_byte, 0); rb_define_method(rb_cARGF, "chars", argf_each_char, 0); rb_define_method(rb_cARGF, "read", argf_read, -1); rb_define_method(rb_cARGF, "readpartial", argf_readpartial, -1); rb_define_method(rb_cARGF, "read_nonblock", argf_read_nonblock, -1); rb_define_method(rb_cARGF, "readlines", argf_readlines, -1); rb_define_method(rb_cARGF, "to_a", argf_readlines, -1); rb_define_method(rb_cARGF, "gets", argf_gets, -1); rb_define_method(rb_cARGF, "readline", argf_readline, -1); rb_define_method(rb_cARGF, "getc", argf_getc, 0); rb_define_method(rb_cARGF, "getbyte", argf_getbyte, 0); rb_define_method(rb_cARGF, "readchar", argf_readchar, 0); rb_define_method(rb_cARGF, "readbyte", argf_readbyte, 0); rb_define_method(rb_cARGF, "tell", argf_tell, 0); rb_define_method(rb_cARGF, "seek", argf_seek_m, -1); rb_define_method(rb_cARGF, "rewind", argf_rewind, 0); rb_define_method(rb_cARGF, "pos", argf_tell, 0); rb_define_method(rb_cARGF, "pos=", argf_set_pos, 1); rb_define_method(rb_cARGF, "eof", argf_eof, 0); rb_define_method(rb_cARGF, "eof?", argf_eof, 0); rb_define_method(rb_cARGF, "binmode", argf_binmode_m, 0); rb_define_method(rb_cARGF, "binmode?", argf_binmode_p, 0); rb_define_method(rb_cARGF, "write", argf_write, 1); rb_define_method(rb_cARGF, "print", rb_io_print, -1); rb_define_method(rb_cARGF, "putc", rb_io_putc, 1); rb_define_method(rb_cARGF, "puts", rb_io_puts, -1); rb_define_method(rb_cARGF, "printf", rb_io_printf, -1); rb_define_method(rb_cARGF, "filename", argf_filename, 0); rb_define_method(rb_cARGF, "path", argf_filename, 0); rb_define_method(rb_cARGF, "file", argf_file, 0); rb_define_method(rb_cARGF, "skip", argf_skip, 0); rb_define_method(rb_cARGF, "close", argf_close_m, 0); rb_define_method(rb_cARGF, "closed?", argf_closed, 0); rb_define_method(rb_cARGF, "lineno", argf_lineno, 0); rb_define_method(rb_cARGF, "lineno=", argf_set_lineno, 1); rb_define_method(rb_cARGF, "inplace_mode", argf_inplace_mode_get, 0); rb_define_method(rb_cARGF, "inplace_mode=", argf_inplace_mode_set, 1); rb_define_method(rb_cARGF, "external_encoding", argf_external_encoding, 0); rb_define_method(rb_cARGF, "internal_encoding", argf_internal_encoding, 0); rb_define_method(rb_cARGF, "set_encoding", argf_set_encoding, -1); argf = rb_class_new_instance(0, 0, rb_cARGF); rb_define_readonly_variable("$<", &argf); /* * ARGF is a stream designed for use in scripts that process files given * as command-line arguments or passed in via STDIN. * * See ARGF (the class) for more details. */ rb_define_global_const("ARGF", argf); rb_define_hooked_variable("$.", &argf, argf_lineno_getter, argf_lineno_setter); rb_define_hooked_variable("$FILENAME", &argf, argf_filename_getter, rb_gvar_readonly_setter); ARGF.filename = rb_str_new2("-"); rb_define_hooked_variable("$-i", &argf, opt_i_get, opt_i_set); rb_define_hooked_variable("$*", &argf, argf_argv_getter, rb_gvar_readonly_setter); #if defined (_WIN32) || defined(__CYGWIN__) atexit(pipe_atexit); #endif Init_File(); rb_define_method(rb_cFile, "initialize", rb_file_initialize, -1); /* open for reading only */ rb_file_const("RDONLY", INT2FIX(O_RDONLY)); /* open for writing only */ rb_file_const("WRONLY", INT2FIX(O_WRONLY)); /* open for reading and writing */ rb_file_const("RDWR", INT2FIX(O_RDWR)); /* append on each write */ rb_file_const("APPEND", INT2FIX(O_APPEND)); /* create file if it does not exist */ rb_file_const("CREAT", INT2FIX(O_CREAT)); /* error if CREAT and the file exists */ rb_file_const("EXCL", INT2FIX(O_EXCL)); #if defined(O_NDELAY) || defined(O_NONBLOCK) # ifndef O_NONBLOCK # define O_NONBLOCK O_NDELAY # endif /* do not block on open or for data to become available */ rb_file_const("NONBLOCK", INT2FIX(O_NONBLOCK)); #endif /* truncate size to 0 */ rb_file_const("TRUNC", INT2FIX(O_TRUNC)); #ifdef O_NOCTTY /* not to make opened IO the controlling terminal device */ rb_file_const("NOCTTY", INT2FIX(O_NOCTTY)); #endif #ifndef O_BINARY # define O_BINARY 0 #endif /* disable line code conversion */ rb_file_const("BINARY", INT2FIX(O_BINARY)); #ifdef O_SYNC rb_file_const("SYNC", INT2FIX(O_SYNC)); #endif #ifdef O_DSYNC rb_file_const("DSYNC", INT2FIX(O_DSYNC)); #endif #ifdef O_RSYNC rb_file_const("RSYNC", INT2FIX(O_RSYNC)); #endif #ifdef O_NOFOLLOW /* do not follow symlinks */ rb_file_const("NOFOLLOW", INT2FIX(O_NOFOLLOW)); /* FreeBSD, Linux */ #endif #ifdef O_NOATIME /* do not change atime */ rb_file_const("NOATIME", INT2FIX(O_NOATIME)); /* Linux */ #endif #ifdef O_DIRECT /* Try to minimize cache effects of the I/O to and from this file. */ rb_file_const("DIRECT", INT2FIX(O_DIRECT)); #endif sym_mode = ID2SYM(rb_intern("mode")); sym_perm = ID2SYM(rb_intern("perm")); sym_extenc = ID2SYM(rb_intern("external_encoding")); sym_intenc = ID2SYM(rb_intern("internal_encoding")); sym_encoding = ID2SYM(rb_intern("encoding")); sym_open_args = ID2SYM(rb_intern("open_args")); sym_textmode = ID2SYM(rb_intern("textmode")); sym_binmode = ID2SYM(rb_intern("binmode")); sym_autoclose = ID2SYM(rb_intern("autoclose")); sym_normal = ID2SYM(rb_intern("normal")); sym_sequential = ID2SYM(rb_intern("sequential")); sym_random = ID2SYM(rb_intern("random")); sym_willneed = ID2SYM(rb_intern("willneed")); sym_dontneed = ID2SYM(rb_intern("dontneed")); sym_noreuse = ID2SYM(rb_intern("noreuse")); }