use crate::rubysys::{
io_buffer::rb_off_t,
libc::{timeval, FILE},
types::{c_char, c_int, c_long, c_void, size_t, ssize_t, Argc, EncodingType, Value},
};
#[cfg(unix)]
use crate::rubysys::libc::fd_set;
pub const RUBY_IO_READABLE: c_int = 0x001;
pub const RUBY_IO_PRIORITY: c_int = 0x002;
pub const RUBY_IO_WRITABLE: c_int = 0x004;
pub const FMODE_READABLE: c_int = 0x0000_0001;
pub const FMODE_WRITABLE: c_int = 0x0000_0002;
pub const FMODE_READWRITE: c_int = FMODE_READABLE | FMODE_WRITABLE;
pub const FMODE_BINMODE: c_int = 0x0000_0004;
pub const FMODE_SYNC: c_int = 0x0000_0008;
pub const FMODE_TTY: c_int = 0x0000_0010;
pub const FMODE_DUPLEX: c_int = 0x0000_0020;
pub const FMODE_APPEND: c_int = 0x0000_0040;
pub const FMODE_CREATE: c_int = 0x0000_0080;
pub const FMODE_EXCL: c_int = 0x0000_0400;
pub const FMODE_TRUNC: c_int = 0x0000_0800;
pub const FMODE_TEXTMODE: c_int = 0x0000_1000;
pub const FMODE_EXTERNAL: c_int = 0x0001_0000;
pub const FMODE_SETENC_BY_BOM: c_int = 0x0010_0000;
#[repr(C)]
pub struct rb_io_encoding {
pub enc: EncodingType,
pub enc2: EncodingType,
pub ecflags: c_int,
pub ecopts: Value,
}
pub use crate::rubysys::process::rb_pid_t;
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub static rb_fs: Value;
pub static rb_output_fs: Value;
pub static rb_rs: Value;
pub static rb_default_rs: Value;
pub static rb_output_rs: Value;
pub static rb_cFile: Value;
pub static rb_cIO: Value;
pub static rb_stderr: Value;
pub static rb_stdin: Value;
pub static rb_stdout: Value;
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_dir_getwd() -> Value;
pub fn rb_file_absolute_path(path: Value, directory: Value) -> Value;
pub fn rb_file_dirname(path: Value) -> Value;
pub fn rb_file_expand_path(path: Value, directory: Value) -> Value;
pub fn rb_file_open(path: *const c_char, mode: *const c_char) -> Value;
pub fn rb_file_open_str(path: Value, mode: *const c_char) -> Value;
pub fn rb_find_file(path: Value) -> Value;
pub fn rb_io_addstr(io: Value, string: Value) -> Value;
pub fn rb_io_binmode(io: Value) -> Value;
pub fn rb_io_ascii8bit_binmode(io: Value) -> Value;
pub fn rb_io_closed_p(io: Value) -> Value;
pub fn rb_io_wait(io: Value, events: Value, timeout: Value) -> Value;
pub fn rb_io_descriptor(io: Value) -> c_int;
pub fn rb_io_mode(io: Value) -> c_int;
pub fn rb_io_maybe_wait(error: c_int, io: Value, events: Value, timeout: Value) -> Value;
pub fn rb_io_maybe_wait_readable(error: c_int, io: Value, timeout: Value) -> c_int;
pub fn rb_io_maybe_wait_writable(error: c_int, io: Value, timeout: Value) -> c_int;
pub fn rb_io_timeout(io: Value) -> Value;
pub fn rb_io_set_timeout(io: Value, timeout: Value) -> Value;
pub fn rb_io_open_descriptor(
klass: Value,
descriptor: c_int,
mode: c_int,
path: Value,
timeout: Value,
encoding: *mut rb_io_encoding,
) -> Value;
pub fn rb_file_size(file: Value) -> rb_off_t;
pub fn rb_io_close(io: Value) -> Value;
pub fn rb_io_eof(io: Value) -> Value;
pub fn rb_io_flush(io: Value) -> Value;
pub fn rb_io_getbyte(io: Value) -> Value;
pub fn rb_io_gets(io: Value) -> Value;
pub fn rb_io_print(argc: Argc, argv: *const Value, io: Value) -> Value;
pub fn rb_io_puts(argc: Argc, argv: *const Value, io: Value) -> Value;
pub fn rb_io_ungetc(io: Value, character: Value) -> Value;
pub fn rb_io_write(io: Value, string: Value) -> Value;
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_process_status_wait(pid: rb_pid_t, flags: c_int) -> Value;
}
pub type MarshalDumper = rutie_callback!(type fn(object: Value) -> Value);
pub type MarshalLoader = rutie_callback!(type fn(object: Value, old: Value) -> Value);
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_marshal_dump(object: Value, port: Value) -> Value;
pub fn rb_marshal_load(port: Value) -> Value;
pub fn rb_marshal_define_compat(
new_class: Value,
old_class: Value,
dumper: MarshalDumper,
loader: MarshalLoader,
);
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_feature_provided(feature: *const c_char, loading: *mut *const c_char) -> c_int;
pub fn rb_f_require(object: Value, name: Value) -> Value;
pub fn rb_load(path: Value, wrap: c_int);
pub fn rb_load_protect(path: Value, wrap: c_int, state: *mut c_int);
pub fn rb_provide(feature: *const c_char);
pub fn rb_provided(feature: *const c_char) -> c_int;
pub fn rb_require_string(name: Value) -> Value;
pub fn ruby_incpush(path: *const c_char);
}
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct rb_io_t {
_private: [u8; 0],
}
#[allow(non_camel_case_types)]
pub type rb_io_mode = c_int;
pub const RUBY_IO_MODE_EXTERNAL: rb_io_mode = FMODE_EXTERNAL;
pub const RUBY_IO_MODE_READABLE: rb_io_mode = FMODE_READABLE;
pub const RUBY_IO_MODE_WRITABLE: rb_io_mode = FMODE_WRITABLE;
pub const RUBY_IO_MODE_READABLE_WRITABLE: rb_io_mode = FMODE_READWRITE;
pub const RUBY_IO_MODE_BINARY: rb_io_mode = FMODE_BINMODE;
pub const RUBY_IO_MODE_TEXT: rb_io_mode = FMODE_TEXTMODE;
pub const RUBY_IO_MODE_TEXT_SET_ENCODING_FROM_BOM: rb_io_mode = FMODE_SETENC_BY_BOM;
pub const RUBY_IO_MODE_SYNCHRONISED: rb_io_mode = FMODE_SYNC;
pub const RUBY_IO_MODE_TTY: rb_io_mode = FMODE_TTY;
pub const RUBY_IO_MODE_DUPLEX: rb_io_mode = FMODE_DUPLEX;
pub const RUBY_IO_MODE_APPEND: rb_io_mode = FMODE_APPEND;
pub const RUBY_IO_MODE_CREATE: rb_io_mode = FMODE_CREATE;
pub const RUBY_IO_MODE_EXCLUSIVE: rb_io_mode = FMODE_EXCL;
pub const RUBY_IO_MODE_TRUNCATE: rb_io_mode = FMODE_TRUNC;
#[cfg(unix)]
#[allow(non_camel_case_types)]
pub type mode_t = crate::rubysys::libc::mode_t;
#[cfg(windows)]
#[allow(non_camel_case_types)]
pub type mode_t = c_int;
#[repr(C)]
pub struct RbFdset {
pub maxfd: c_int,
pub fdset: *mut c_void,
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_eof_error() -> !;
pub fn rb_fdopen(fd: c_int, modestr: *const c_char) -> *mut FILE;
pub fn rb_io_bufwrite(io: Value, buf: *const c_void, size: size_t) -> ssize_t;
pub fn rb_io_check_byte_readable(fptr: *mut rb_io_t);
pub fn rb_io_check_char_readable(fptr: *mut rb_io_t);
pub fn rb_io_check_closed(fptr: *mut rb_io_t);
pub fn rb_io_check_initialized(fptr: *mut rb_io_t);
pub fn rb_io_check_io(io: Value) -> Value;
pub fn rb_io_check_readable(fptr: *mut rb_io_t);
pub fn rb_io_check_writable(fptr: *mut rb_io_t);
pub fn rb_io_extract_encoding_option(
opt: Value,
enc_p: *mut EncodingType,
enc2_p: *mut EncodingType,
fmode_p: *mut rb_io_mode,
) -> c_int;
pub fn rb_io_extract_modeenc(
vmode_p: *mut Value,
vperm_p: *mut Value,
opthash: Value,
oflags_p: *mut c_int,
fmode_p: *mut rb_io_mode,
convconfig_p: *mut rb_io_encoding,
);
pub fn rb_io_fptr_finalize(fptr: *mut rb_io_t) -> c_int;
pub fn rb_io_get_io(io: Value) -> Value;
pub fn rb_io_make_open_file(obj: Value) -> *mut rb_io_t;
pub fn rb_io_modestr_fmode(modestr: *const c_char) -> rb_io_mode;
pub fn rb_io_modestr_oflags(modestr: *const c_char) -> c_int;
pub fn rb_io_oflags_fmode(oflags: c_int) -> rb_io_mode;
pub fn rb_io_read_check(fptr: *mut rb_io_t);
pub fn rb_io_read_pending(fptr: *mut rb_io_t) -> c_int;
pub fn rb_io_set_nonblock(fptr: *mut rb_io_t);
pub fn rb_io_set_write_io(io: Value, w: Value) -> Value;
pub fn rb_io_stdio_file(fptr: *mut rb_io_t) -> *mut FILE;
pub fn rb_io_synchronized(fptr: *mut rb_io_t);
pub fn rb_io_wait_readable(fd: c_int) -> c_int;
pub fn rb_io_wait_writable(fd: c_int) -> c_int;
pub fn rb_stat_new(st: *const c_void) -> Value;
#[cfg(target_os = "linux")]
pub fn rb_statx_new(st: *const c_void) -> Value;
pub fn rb_wait_for_single_fd(fd: c_int, events: c_int, tv: *mut timeval) -> c_int;
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_cloexec_dup(oldfd: c_int) -> c_int;
pub fn rb_cloexec_dup2(oldfd: c_int, newfd: c_int) -> c_int;
pub fn rb_cloexec_fcntl_dupfd(fd: c_int, minfd: c_int) -> c_int;
pub fn rb_cloexec_open(pathname: *const c_char, flags: c_int, mode: mode_t) -> c_int;
pub fn rb_cloexec_pipe(fildes: *mut c_int) -> c_int;
pub fn rb_close_before_exec(lowfd: c_int, maxhint: c_int, noclose_fds: Value);
pub fn rb_fd_fix_cloexec(fd: c_int);
pub fn rb_gets() -> Value;
pub fn rb_io_fdopen(fd: c_int, flags: c_int, path: *const c_char) -> Value;
pub fn rb_io_printf(argc: Argc, argv: *const Value, io: Value) -> Value;
pub fn rb_io_ungetbyte(io: Value, byte: Value) -> Value;
pub fn rb_pipe(pipes: *mut c_int) -> c_int;
pub fn rb_reserved_fd_p(fd: c_int) -> c_int;
pub fn rb_update_max_fd(fd: c_int);
pub fn rb_write_error(str: *const c_char);
pub fn rb_write_error2(str: *const c_char, len: c_long);
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_thread_fd_select(
nfds: c_int,
rfds: *mut RbFdset,
wfds: *mut RbFdset,
efds: *mut RbFdset,
timeout: *mut timeval,
) -> c_int;
#[cfg(unix)]
pub fn rb_fd_clr(fd: c_int, set: *mut RbFdset);
#[cfg(unix)]
pub fn rb_fd_copy(dst: *mut RbFdset, src: *const fd_set, max: c_int);
#[cfg(unix)]
pub fn rb_fd_dup(dst: *mut RbFdset, src: *const RbFdset);
#[cfg(unix)]
pub fn rb_fd_init(set: *mut RbFdset);
#[cfg(unix)]
pub fn rb_fd_isset(fd: c_int, set: *const RbFdset) -> c_int;
#[cfg(unix)]
pub fn rb_fd_select(
nfds: c_int,
rfds: *mut RbFdset,
wfds: *mut RbFdset,
efds: *mut RbFdset,
timeout: *mut timeval,
) -> c_int;
#[cfg(unix)]
pub fn rb_fd_set(fd: c_int, set: *mut RbFdset);
#[cfg(unix)]
pub fn rb_fd_term(set: *mut RbFdset);
#[cfg(unix)]
pub fn rb_fd_zero(set: *mut RbFdset);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binding::{fixnum, global::RubySpecialConsts, vm};
use std::ptr;
fn nil() -> Value {
Value::from(RubySpecialConsts::Nil as crate::types::InternalValue)
}
unsafe fn fptr(io: Value) -> *mut rb_io_t {
*(io.value as *const *mut rb_io_t).add(2)
}
fn fileno(io: Value) -> c_int {
fixnum::num_to_i32(vm::call_method(io, "fileno", &[]))
}
#[cfg(unix)]
#[test]
fn test_descriptors_and_fptr() {
crate::on_ruby_thread(|| unsafe {
let mut fds = [-1; 2];
assert_eq!(rb_pipe(fds.as_mut_ptr()), 0);
assert_eq!(rb_reserved_fd_p(fds[0]), 0);
rb_update_max_fd(fds[1]);
rb_fd_fix_cloexec(fds[1]);
let reader = rb_io_fdopen(fds[0], 0, ptr::null());
let writer = rb_io_fdopen(fds[1], 1, b"pipe\0".as_ptr() as *const c_char);
assert_eq!(rb_io_check_io(reader).value, reader.value);
assert!(rb_io_check_io(fixnum::i32_to_num(1)).is_nil());
assert_eq!(rb_io_get_io(writer).value, writer.value);
assert!(vm::protect_value(|| rb_io_get_io(nil())).is_err());
let (read_fptr, write_fptr) = (fptr(reader), fptr(writer));
rb_io_check_initialized(read_fptr);
rb_io_check_closed(read_fptr);
rb_io_check_readable(read_fptr);
rb_io_check_char_readable(read_fptr);
rb_io_check_byte_readable(read_fptr);
rb_io_check_writable(write_fptr);
assert!(vm::protect_value(|| {
rb_io_check_writable(read_fptr);
nil()
})
.is_err());
rb_io_synchronized(write_fptr);
rb_io_set_nonblock(read_fptr);
let bytes = b"abc\n";
assert_eq!(
rb_io_bufwrite(writer, bytes.as_ptr() as *const c_void, 4),
4
);
assert_eq!(rb_io_read_pending(read_fptr), 0);
rb_io_read_check(read_fptr);
assert_eq!(
rb_wait_for_single_fd(fds[0], RUBY_IO_READABLE, ptr::null_mut()),
RUBY_IO_READABLE
);
assert_eq!(
fixnum::num_to_i32(vm::call_method(reader, "getbyte", &[])),
b'a' as i32
);
assert_eq!(rb_io_read_pending(read_fptr), 3);
rb_io_ungetbyte(reader, fixnum::i32_to_num(b'y' as i32));
assert_eq!(
fixnum::num_to_i32(vm::call_method(reader, "getbyte", &[])),
b'y' as i32
);
let format = vm::eval_string("'%s!'");
let arguments = [format, vm::eval_string("'ok'")];
rb_io_printf(2, arguments.as_ptr(), writer);
let mut timeout = timeval {
tv_sec: 0,
tv_usec: 0,
};
assert_eq!(
rb_wait_for_single_fd(fds[1], RUBY_IO_WRITABLE, &mut timeout),
RUBY_IO_WRITABLE
);
assert!(rb_io_set_write_io(reader, writer).is_nil());
assert_eq!(rb_io_set_write_io(reader, nil()).value, writer.value);
let stdio = rb_io_stdio_file(write_fptr);
assert!(!stdio.is_null());
let copy = rb_cloexec_dup(fileno(reader));
assert!(copy > 2);
let higher = rb_cloexec_fcntl_dupfd(copy, 100);
assert!(higher >= 100);
assert_eq!(rb_cloexec_dup2(copy, higher), higher);
let file = rb_fdopen(higher, b"r\0".as_ptr() as *const c_char);
assert!(!file.is_null());
crate::rubysys::libc::fclose(file);
crate::rubysys::libc::close(copy);
let mut more = [-1; 2];
assert_eq!(rb_cloexec_pipe(more.as_mut_ptr()), 0);
crate::rubysys::libc::close(more[0]);
crate::rubysys::libc::close(more[1]);
let path =
std::env::temp_dir().join(format!("rutie_cloexec_open_{}", std::process::id()));
let cpath = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let fd = rb_cloexec_open(
cpath.as_ptr(),
crate::rubysys::libc::O_WRONLY | crate::rubysys::libc::O_CREAT,
0o600,
);
assert!(fd > 2);
crate::rubysys::libc::close(fd);
assert_eq!(
rb_cloexec_open(b"/no/such/rutie\0".as_ptr() as *const c_char, 0, 0),
-1
);
std::fs::remove_file(path).unwrap();
vm::call_method(reader, "close", &[]);
vm::call_method(writer, "close", &[]);
assert_eq!(
rb_io_modestr_fmode(b"w\0".as_ptr() as *const c_char),
0x2 | 0x80 | 0x800
);
let oflags = rb_io_modestr_oflags(b"r+\0".as_ptr() as *const c_char);
assert_eq!(rb_io_oflags_fmode(oflags), 0x3);
let options = vm::eval_string("{ encoding: 'UTF-8:EUC-JP' }");
let (mut enc, mut enc2, mut fmode) = (ptr::null(), ptr::null(), 0);
assert_eq!(
rb_io_extract_encoding_option(options, &mut enc, &mut enc2, &mut fmode),
1
);
assert!(!enc.is_null() && !enc2.is_null());
assert_eq!(
rb_io_extract_encoding_option(nil(), &mut enc, &mut enc2, &mut fmode),
0
);
let mut vmode = vm::eval_string("'rb'");
let mut vperm = nil();
let (mut oflags, mut fmode) = (0, 0);
let mut convconfig = rb_io_encoding {
enc: ptr::null(),
enc2: ptr::null(),
ecflags: 0,
ecopts: nil(),
};
rb_io_extract_modeenc(
&mut vmode,
&mut vperm,
nil(),
&mut oflags,
&mut fmode,
&mut convconfig,
);
assert_eq!(fmode & 0x5, 0x5);
let blank = vm::call_method(rb_cIO, "allocate", &[]);
let blank_fptr = rb_io_make_open_file(blank);
assert!(!blank_fptr.is_null());
assert!(vm::protect_value(|| {
rb_io_check_closed(blank_fptr);
nil()
})
.is_err());
assert!(vm::protect_value(|| rb_eof_error()).is_err());
vm::eval_string("require 'stringio'; $rutie_err = $stderr; $stderr = StringIO.new");
rb_write_error(b"one \0".as_ptr() as *const c_char);
rb_write_error2(b"two".as_ptr() as *const c_char, 3);
let written = vm::eval_string("s = $stderr.string; $stderr = $rutie_err; s");
assert_eq!(crate::binding::string::value_to_string(written), "one two");
});
}
#[cfg(unix)]
#[test]
fn test_fdsets_select_and_stat() {
use crate::rubysys::libc;
crate::on_ruby_thread(|| unsafe {
let mut fds = [-1; 2];
assert_eq!(rb_pipe(fds.as_mut_ptr()), 0);
let mut set = RbFdset {
maxfd: 0,
fdset: ptr::null_mut(),
};
rb_fd_init(&mut set);
rb_fd_set(fds[0], &mut set);
assert_eq!(rb_fd_isset(fds[0], &set), 1);
rb_fd_clr(fds[0], &mut set);
assert_eq!(rb_fd_isset(fds[0], &set), 0);
rb_fd_set(fds[0], &mut set);
let mut copy = RbFdset {
maxfd: 0,
fdset: ptr::null_mut(),
};
rb_fd_init(&mut copy);
rb_fd_dup(&mut copy, &set);
assert_eq!(rb_fd_isset(fds[0], ©), 1);
let mut zero = timeval {
tv_sec: 0,
tv_usec: 0,
};
assert_eq!(
rb_thread_fd_select(
fds[0] + 1,
&mut copy,
ptr::null_mut(),
ptr::null_mut(),
&mut zero
),
0
);
assert_eq!(libc::write(fds[1], b"x".as_ptr() as *const c_void, 1), 1);
rb_fd_dup(&mut copy, &set);
let mut zero = timeval {
tv_sec: 0,
tv_usec: 0,
};
assert_eq!(
rb_thread_fd_select(
fds[0] + 1,
&mut copy,
ptr::null_mut(),
ptr::null_mut(),
&mut zero
),
1
);
assert_eq!(rb_fd_isset(fds[0], ©), 1);
rb_fd_dup(&mut copy, &set);
let mut zero = timeval {
tv_sec: 0,
tv_usec: 0,
};
assert_eq!(
rb_fd_select(
fds[0] + 1,
&mut copy,
ptr::null_mut(),
ptr::null_mut(),
&mut zero
),
1
);
let mut raw: libc::fd_set = std::mem::zeroed();
libc::FD_SET(fds[1], &mut raw);
rb_fd_copy(&mut copy, &raw, fds[1] + 1);
assert_eq!(rb_fd_isset(fds[1], ©), 1);
assert_eq!(rb_fd_isset(fds[0], ©), 0);
rb_fd_zero(&mut copy);
assert_eq!(rb_fd_isset(fds[1], ©), 0);
rb_fd_term(&mut copy);
rb_fd_term(&mut set);
let plain = libc::dup(fds[0]);
assert_eq!(libc::fcntl(plain, libc::F_GETFD) & libc::FD_CLOEXEC, 0);
rb_close_before_exec(
plain,
plain + 1,
Value::from(RubySpecialConsts::Nil as crate::types::InternalValue),
);
assert_ne!(libc::fcntl(plain, libc::F_GETFD) & libc::FD_CLOEXEC, 0);
let mut stat: libc::stat = std::mem::zeroed();
assert_eq!(libc::fstat(plain, &mut stat), 0);
let stat = rb_stat_new(&stat as *const libc::stat as *const c_void);
assert!(vm::call_method(stat, "pipe?", &[]).is_true());
for fd in [plain, fds[0], fds[1]] {
libc::close(fd);
}
});
}
#[test]
fn test_separator_globals() {
crate::on_ruby_thread(|| unsafe {
assert_eq!(rb_rs.value, vm::eval_string("$/").value);
assert_eq!(rb_default_rs.value, vm::eval_string("$/").value);
assert!(rb_fs.is_nil() && rb_output_fs.is_nil() && rb_output_rs.is_nil());
});
}
}