All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jruby.RubyIO Maven / Gradle / Ivy

/*
 **** BEGIN LICENSE BLOCK *****
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/epl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001-2004 Jan Arne Petersen 
 * Copyright (C) 2002 Benoit Cerrina 
 * Copyright (C) 2002-2004 Anders Bengtsson 
 * Copyright (C) 2002-2006 Thomas E Enebo 
 * Copyright (C) 2004-2006 Charles O Nutter 
 * Copyright (C) 2004 Stefan Matthias Aust 
 * Copyright (C) 2006 Evan Buswell 
 * Copyright (C) 2007 Miguel Covarrubias 
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import jnr.constants.platform.Errno;
import jnr.constants.platform.OpenFlags;
import jnr.enxio.channels.NativeDeviceChannel;
import jnr.enxio.channels.NativeSelectableChannel;
import jnr.posix.POSIX;
import org.jcodings.transcode.EConvFlags;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.JavaSites.IOSites;
import org.jruby.runtime.callsite.RespondToCallSite;
import org.jruby.util.StringSupport;
import org.jruby.util.io.ChannelFD;
import org.jruby.util.io.EncodingUtils;
import static org.jruby.util.io.EncodingUtils.vmodeVperm;
import static org.jruby.util.io.EncodingUtils.vperm;

import org.jruby.util.io.FilenoUtil;
import org.jruby.util.io.ModeFlags;
import org.jruby.util.io.POSIXProcess;
import org.jruby.util.io.PopenExecutor;
import org.jruby.util.io.PosixShim;
import jnr.constants.platform.Fcntl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.jcodings.Encoding;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.fcntl.FcntlLibrary;
import org.jruby.internal.runtime.ThreadedRunnable;
import org.jruby.platform.Platform;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.encoding.EncodingService;
import org.jruby.util.ByteList;
import org.jruby.util.ShellLauncher.POpenProcess;
import org.jruby.util.SafePropertyAccessor;
import org.jruby.util.ShellLauncher;
import org.jruby.util.TypeConverter;
import org.jruby.util.io.IOEncodable;
import org.jruby.util.io.IOOptions;
import org.jruby.util.io.InvalidValueException;
import org.jruby.util.io.OpenFile;
import org.jruby.util.io.SelectExecutor;
import org.jruby.util.io.STDIO;

import static org.jruby.RubyEnumerator.enumeratorize;
import static org.jruby.runtime.Visibility.*;
import static org.jruby.util.io.ChannelHelper.*;

/**
 *
 * @author jpetersen
 */
@JRubyClass(name="IO", include="Enumerable")
public class RubyIO extends RubyObject implements IOEncodable {
    // We use a highly uncommon string to represent the paragraph delimiter (100% soln not worth it)
    public static final ByteList PARAGRAPH_DELIMETER = ByteList.create("PARAGRPH_DELIM_MRK_ER");
    public static final ByteList PARAGRAPH_SEPARATOR = ByteList.create("\n\n");
    public static final String CLOSED_STREAM_MSG = "closed stream";

    // This should only be called by this and RubyFile.
    // It allows this object to be created without a IOHandler.
    public RubyIO(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }

    public RubyIO(Ruby runtime, OutputStream outputStream) {
        this(runtime, outputStream, true);
    }

    public RubyIO(Ruby runtime, OutputStream outputStream, boolean autoclose) {
        super(runtime, runtime.getIO());

        // We only want IO objects with valid streams (better to error now).
        if (outputStream == null) {
            throw runtime.newRuntimeError("Opening null stream");
        }

        openFile = MakeOpenFile();
        openFile.setFD(new ChannelFD(writableChannel(outputStream), runtime.getPosix(), runtime.getFilenoUtil()));
        openFile.setMode(OpenFile.WRITABLE | OpenFile.APPEND);
        openFile.setAutoclose(autoclose);
    }

    public RubyIO(Ruby runtime, InputStream inputStream) {
        super(runtime, runtime.getIO());

        if (inputStream == null) {
            throw runtime.newRuntimeError("Opening null stream");
        }

        openFile = MakeOpenFile();
        openFile.setFD(new ChannelFD(readableChannel(inputStream), runtime.getPosix(), runtime.getFilenoUtil()));
        openFile.setMode(OpenFile.READABLE);
    }

    public RubyIO(Ruby runtime, Channel channel) {
        this(runtime, runtime.getIO(), channel);
    }

    public RubyIO(Ruby runtime, RubyClass klass, Channel channel) {
        super(runtime, klass);

        // We only want IO objects with valid streams (better to error now).
        if (channel == null) {
            throw runtime.newRuntimeError("Opening null channel");
        }

        ThreadContext context = runtime.getCurrentContext();
        initializeCommon(context, new ChannelFD(channel, runtime.getPosix(), runtime.getFilenoUtil()), runtime.newFixnum(ModeFlags.oflagsFrom(runtime.getPosix(), channel)), context.nil);
    }

    public RubyIO(Ruby runtime, ShellLauncher.POpenProcess process, IOOptions ioOptions) {
        super(runtime, runtime.getIO());

        ioOptions = updateIOOptionsFromOptions(runtime.getCurrentContext(), null, ioOptions);

        openFile = MakeOpenFile();

        setupPopen(ioOptions.getModeFlags(), process);
    }

    // MRI: prep_stdio
    public static RubyIO prepStdio(Ruby runtime, InputStream f, Channel c, int fmode, RubyClass klass, String path) {
        OpenFile fptr;
        RubyIO io = prepIO(runtime, c, fmode | OpenFile.PREP | EncodingUtils.DEFAULT_TEXTMODE, klass, path);

        fptr = io.getOpenFileChecked();

        // If we can't use native IO, always force stdio to expected fileno.
        if (!runtime.getPosix().isNative() || Platform.IS_WINDOWS) {
            // Use standard stdio filenos if we're using System.in et al.
            if (f == System.in) {
                fptr.fd().realFileno = 0;
            }
        }

        prepStdioEcflags(fptr, fmode);
        fptr.stdio_file = f;

        // We checkTTY again here because we're using stdout/stdin to indicate this is stdio
        return recheckTTY(runtime, fptr, io);
    }

    // MRI: prep_stdio
    public static RubyIO prepStdio(Ruby runtime, OutputStream f, Channel c, int fmode, RubyClass klass, String path) {
        OpenFile fptr;
        RubyIO io = prepIO(runtime, c, fmode | OpenFile.PREP | EncodingUtils.DEFAULT_TEXTMODE, klass, path);

        fptr = io.getOpenFileChecked();

        // If we can't use native IO, always force stdio to expected fileno.
        if (!runtime.getPosix().isNative() || Platform.IS_WINDOWS) {
            // Use standard stdio filenos if we're using System.in et al.
            if (f == System.out) {
                fptr.fd().realFileno = 1;
            } else if (f == System.err) {
                fptr.fd().realFileno = 2;
            }
        }

        prepStdioEcflags(fptr, fmode);
        fptr.stdio_file = f;

        return recheckTTY(runtime, fptr, io);
    }

    private static RubyIO recheckTTY(Ruby runtime, OpenFile fptr, RubyIO io) {
        // We checkTTY again here because we're using stdout/stdin to indicate this is stdio
        fptr.checkTTY();

        return io;
    }

    // MRI: part of prep_stdio
    private static void prepStdioEcflags(OpenFile fptr, int fmode) {
        boolean locked = fptr.lock();
        try {
            fptr.encs.ecflags |= EncodingUtils.ECONV_DEFAULT_NEWLINE_DECORATOR;
            if (EncodingUtils.TEXTMODE_NEWLINE_DECORATOR_ON_WRITE != 0) {
                fptr.encs.ecflags |= EncodingUtils.TEXTMODE_NEWLINE_DECORATOR_ON_WRITE;
                if ((fmode & OpenFile.READABLE) != 0) {
                    fptr.encs.ecflags |= EConvFlags.UNIVERSAL_NEWLINE_DECORATOR;
                }
            }
        } finally {
            if (locked) fptr.unlock();
        }
    }

    // MRI: prep_io
    private static RubyIO prepIO(Ruby runtime, Channel fd, int fmode, RubyClass klass, String path) {
        OpenFile fp;
        RubyIO io = (RubyIO)klass.allocate();

        fp = io.MakeOpenFile();
        fp.setChannel(fd);
        // Can we determine this?
//        if (Platform.IS_CYGWIN) {
//            if (!runtime.getPosix().isatty(fd)) {
//                fmode |= OpenFile.BINMODE;
                // TODO: setmode O_BINARY means what via NIO?
//                setmode(fd, OpenFlags.O_BINARY);
//            }
//        }
        fp.setMode(fmode);
        fp.checkTTY();
        if (path != null) fp.setPath(path);
//        rb_update_max_fd(fd);

        return io;
    }

    public static RubyIO newIO(Ruby runtime, Channel channel) {
        return new RubyIO(runtime, channel);
    }

    public OpenFile getOpenFile() {
        return openFile;
    }

    public OpenFile getOpenFileChecked() {
        checkInitialized();
        openFile.checkClosed();
        return openFile;
    }

    // MRI: rb_io_get_fptr
    public OpenFile getOpenFileInitialized() {
        checkInitialized();
        return openFile;
    }

    private static ObjectAllocator IO_ALLOCATOR = new ObjectAllocator() {
        @Override
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyIO(runtime, klass);
        }
    };

    /*
     * We use FILE versus IO to match T_FILE in MRI.
     */
    @Override
    public ClassIndex getNativeClassIndex() {
        return ClassIndex.FILE;
    }

    public static RubyClass createIOClass(Ruby runtime) {
        RubyClass ioClass = runtime.defineClass("IO", runtime.getObject(), IO_ALLOCATOR);

        ioClass.setClassIndex(ClassIndex.IO);
        ioClass.setReifiedClass(RubyIO.class);

        ioClass.kindOf = new RubyModule.JavaClassKindOf(RubyIO.class);

        ioClass.includeModule(runtime.getEnumerable());

        ioClass.defineAnnotatedMethods(RubyIO.class);

        // Constants for seek
        ioClass.setConstant("SEEK_SET", runtime.newFixnum(PosixShim.SEEK_SET));
        ioClass.setConstant("SEEK_CUR", runtime.newFixnum(PosixShim.SEEK_CUR));
        ioClass.setConstant("SEEK_END", runtime.newFixnum(PosixShim.SEEK_END));

        ioClass.defineModuleUnder("WaitReadable");
        ioClass.defineModuleUnder("WaitWritable");

        return ioClass;
    }

    public OutputStream getOutStream() {
        // FIXME: Could be faster by caching bytelist or string rather than creating for every call
        return new OutputStream() {
            final Ruby runtime = getRuntime();

            @Override
            public void write(int b) throws IOException {
                putc(runtime.getCurrentContext(), runtime.newFixnum(b));
            }

            @Override
            public void write(byte[] b) throws IOException {
                RubyIO.this.write(runtime.getCurrentContext(), RubyString.newStringNoCopy(runtime, b));
            }

            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                RubyIO.this.write(runtime.getCurrentContext(), RubyString.newStringNoCopy(runtime, b, off, len));
            }

            @Override
            public void flush() throws IOException {
                RubyIO.this.flush(runtime.getCurrentContext());
            }

            @Override
            public void close() throws IOException {
                RubyIO.this.close();
            }
        };
    }

    public InputStream getInStream() {
        // FIXME: Could be faster by caching bytelist or string rather than creating for every call
        return new InputStream() {
            final Ruby runtime = getRuntime();

            @Override
            public int read() throws IOException {
                return getByte(runtime.getCurrentContext());
            }

            @Override
            public int read(byte[] b) throws IOException {
                return read(b, 0, b.length);
            }

            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                RubyFixnum c = runtime.newFixnum(len);
                RubyString s = RubyString.newStringNoCopy(runtime, b, off, len);
                IRubyObject i = RubyIO.this.read(runtime.getCurrentContext(), c, s);
                if (i.isNil()) return -1;
                return s.size();
            }

            @Override
            public long skip(long n) throws IOException {
                return seek(runtime.getCurrentContext(), runtime.newFixnum(PosixShim.SEEK_CUR), runtime.newFixnum(n)).getLongValue();
            }

            @Override
            public int available() throws IOException {
                if (RubyIO.this instanceof RubyFile) {
                    long size = ((RubyFixnum)((RubyFile)RubyIO.this).size(runtime.getCurrentContext())).getLongValue();
                    if (size == 0) return 0;
                    if (size >= 0) return (int)(size - pos(runtime.getCurrentContext()).getLongValue());
                }
                return 0;
            }

            @Override
            public void close() throws IOException {
                RubyIO.this.close();
            }
        };
    }

    /**
     * Get the underlying channel from this IO object. Note that IO buffers data internally, so the channel returned
     * here may have been read into those buffers. If the channel and the IO are both being used at the same time, the
     * stream will get out of sync.
     *
     * @return the underlying channel for this IO
     */
    public Channel getChannel() {
        // FIXME: Do we want to make a faux channel that is backed by IO's buffering? Or turn buffering off?
        return getOpenFileChecked().channel();
    }

    // io_reopen
    protected RubyIO reopenIO(ThreadContext context, RubyIO nfile) {
        Ruby runtime = context.runtime;
        OpenFile fptr, orig;
        ChannelFD fd, fd2;
        long pos = 0;

        nfile = TypeConverter.ioGetIO(runtime, nfile);
        fptr = getOpenFileChecked();
        orig = nfile.getOpenFileChecked();

        if (fptr == orig) return this;
        if (fptr.IS_PREP_STDIO()) {
            if ((fptr.stdio_file == System.in && !orig.isReadable()) ||
                    (fptr.stdio_file == System.out && !orig.isWritable()) ||
                    (fptr.stdio_file == System.err && !orig.isWritable())) {
                throw runtime.newArgumentError(fptr.PREP_STDIO_NAME() + " can't change access mode from \"" + fptr.getModeAsString(runtime) + "\" to \"" + orig.getModeAsString(runtime) + "\"");
            }
        }
        // FIXME: three lock acquires...trying to reduce risk of deadlock, but not sure it's possible.

        boolean locked = fptr.lock();
        try {
            if (fptr.isWritable()) {
                if (fptr.io_fflush(context) < 0)
                    throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());
            } else {
                fptr.tell(context);
            }
        } finally {
            if (locked) fptr.unlock();
        }

        locked = orig.lock();
        try {
            if (orig.isReadable()) {
                pos = orig.tell(context);
            }
            if (orig.isWritable()) {
                if (orig.io_fflush(context) < 0)
                    throw runtime.newErrnoFromErrno(orig.errno(), fptr.getPath());
            }
        } finally {
            if (locked) orig.unlock();
        }

        /* copy rb_io_t structure */
        // NOTE: MRI does not copy sync here, but I can find no way to make stdout/stderr stay sync through a reopen
        locked = fptr.lock();
        boolean locked2 = orig.lock(); // TODO: This WILL deadlock if two threads try to reopen the same IOs in opposite directions. Fix?
        try {
            fptr.setMode(orig.getMode() | (fptr.getMode() & (OpenFile.PREP | OpenFile.SYNC)));
            fptr.setProcess(orig.getProcess());
            fptr.setLineNumber(orig.getLineNumber());
            if (orig.getPath() != null) fptr.setPath(orig.getPath());
            else if (!fptr.IS_PREP_STDIO()) fptr.setPath(null);
            fptr.setFinalizer(orig.getFinalizer());

            // TODO: unsure what to do here
            //        #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 (fptr.IS_PREP_STDIO() || fd.bestFileno() <= 2 || fptr.stdio_file == null) {
                    /* need to keep FILE objects of stdin, stdout and stderr */
                    checkReopenCloexecDup2(runtime, orig, fd2, fd);
                    //                rb_update_max_fd(fd);
                    fptr.setFD(fd);

                    //                // MRI does not do this, but we seem to need to set some types of channels to sync if they
                    //                // are reopened as stdout/stderr.
                    //                if (fptr.stdio_file == System.out || fptr.stdio_file == System.err) {
                    //                    fd.chFile.force();
                    //                }
                } else {
                    if (fptr.stdio_file != null) try {
                        fptr.stdio_file.close();
                    } catch (IOException ioe) {
                    }
                    fptr.clearStdio();
                    fptr.setFD(null);
                    checkReopenCloexecDup2(runtime, orig, fd2, fd);
                    //                rb_update_max_fd(fd);
                    fptr.setFD(fd);
                }
                // TODO: clear interrupts waiting on this IO?
                //            rb_thread_fd_close(fd);
                if (orig.isReadable() && pos >= 0) {
                    fptr.checkReopenSeek(context, runtime, pos);
                    orig.checkReopenSeek(context, runtime, pos);
                }
            }

            if (fptr.isBinmode()) {
                setBinmode();
            }
        } finally {
            if (locked2) orig.unlock();
            if (locked) fptr.unlock();
        }

        // We simply can't do this and still have real concrete types under RubyIO
//        setMetaClass(nfile.getMetaClass());
        return this;
    }

    private void checkReopenCloexecDup2(Ruby runtime, OpenFile orig, ChannelFD oldfd, ChannelFD newfd) {
        OpenFile.cloexecDup2(new PosixShim(runtime), oldfd, newfd);
    }

    // rb_io_binmode
    private void setBinmode() {
        OpenFile fptr;

        fptr = getOpenFileChecked();
        boolean locked = fptr.lock();
        try {
            if (fptr.readconv != null)
                fptr.readconv.binmode();
            if (fptr.writeconv != null)
                fptr.writeconv.binmode();
            fptr.setBinmode();
            fptr.clearTextMode();
            fptr.writeconvPreEcflags &= ~EConvFlags.NEWLINE_DECORATOR_MASK;
            if (OpenFlags.O_BINARY.defined()) {
                // TODO: Windows
                //            if (fptr.readconv == null) {
                //                SET_BINARY_MODE_WITH_SEEK_CUR(fptr);
                //            }
                //            else {
                // TODO: setmode O_BINARY means what via NIO?
                //                setmode(fptr->fd, O_BINARY);
                //            }
            }
        } finally {
            if (locked) fptr.unlock();
        }
        return;
    }

    // MRI: rb_io_reopen
    @JRubyMethod(name = "reopen", required = 1, optional = 1)
    public IRubyObject reopen(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.runtime;
        RubyIO file = this;
        IRubyObject fname = context.nil, nmode = context.nil, opt = context.nil;
        int[] oflags_p = {0};
        OpenFile fptr;

        switch (args.length) {
            case 3:
                opt = TypeConverter.checkHashType(runtime, args[2]);
                if (opt.isNil()) throw getRuntime().newArgumentError(3, 2);
            case 2:
                if (opt.isNil()) {
                    opt = TypeConverter.checkHashType(runtime, args[1]);
                    if (opt.isNil()) {
                        nmode = args[1];
                        opt = context.nil;
                    }
                } else {
                    nmode = args[1];
                }
            case 1:
                fname = args[0];
        }
        if (args.length == 1) {
            IRubyObject tmp = TypeConverter.ioCheckIO(runtime, fname);
            if (!tmp.isNil()) {
                return file.reopenIO(context, (RubyIO)tmp);
            }
        }

        fname = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, fname));
        // Not implemented
//        fname.checkTaint();
        fptr = file.openFile;
        if (fptr == null) {
            fptr = file.openFile = MakeOpenFile();
        }

        boolean locked = fptr.lock();
        try {
            if (!nmode.isNil() || !opt.isNil()) {
                ConvConfig convconfig = new ConvConfig();
                Object vmode_vperm = vmodeVperm(nmode, null);
                int[] fmode_p = {0};

                EncodingUtils.extractModeEncoding(context, convconfig, vmode_vperm, opt, oflags_p, fmode_p);
                if (fptr.IS_PREP_STDIO() &&
                        ((fptr.getMode() & OpenFile.READWRITE) & (fmode_p[0] & OpenFile.READWRITE)) !=
                                (fptr.getMode() & OpenFile.READWRITE)) {
                    throw runtime.newArgumentError(fptr.PREP_STDIO_NAME() + " can't change access mode from \"" + fptr.getModeAsString(runtime) + "\" to \"" + OpenFile.getStringFromMode(fmode_p[0]));
                }
                fptr.setMode(fmode_p[0]);
                fptr.encs = convconfig;
            } else {
                oflags_p[0] = OpenFile.getModeFlagsAsIntFrom(fptr.getMode());
            }

            fptr.setPath(fname.toString());
            if (fptr.fd() == null) {
                fptr.setFD(sysopen(runtime, fptr.getPath(), oflags_p[0], 0666));
                fptr.clearStdio();
                return file;
            }

            if (fptr.isWritable()) {
                if (fptr.io_fflush(context) < 0)
                    throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());
            }
            fptr.rbuf.off = fptr.rbuf.len = 0;

            if (fptr.isStdio()) {
                // Logic here reopens the stdio FILE* with a new path and mode. For our purposes, we skip this
                // since we do not want to damage the stdio streams
                //            if (freopen(RSTRING_PTR(fptr.pathv), rb_io_oflags_modestr(oflags), fptr.stdio_file) == 0) {
                //                rb_sys_fail_path(fptr.pathv);
                //            }
                fptr.setFD(sysopen(runtime, fptr.getPath(), oflags_p[0], 0666));

                // This logic fixes the original stdio file descriptor by clearing any CLOEXEC that might have
                // come across with the newly opened file. Since we do not yet support CLOEXEC, we skip this.
                //            fptr.fd = fileno(fptr.stdio_file);
                //            rb_fd_fix_cloexec(fptr.fd);

                // This logic configures buffering (none, line, full) and buffer size to match the original stdio
                // stream associated with this IO. I don't believe we can do this.
                //                #ifdef USE_SETVBUF
                //                if (setvbuf(fptr.stdio_file, NULL, _IOFBF, 0) != 0)
                //                    rb_warn("setvbuf() can't be honoured for %"PRIsVALUE, 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 %"PRIsVALUE, 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 %"PRIsVALUE, fptr.pathv);
                //                }
            } else {
                ChannelFD tmpfd = sysopen(runtime, fptr.getPath(), oflags_p[0], 0666);
                Errno err = null;
                if (OpenFile.cloexecDup2(fptr.posix, tmpfd, fptr.fd()) < 0)
                    err = fptr.errno();

                if (err != null) {
                    throw runtime.newErrnoFromErrno(err, fptr.getPath());
                }
                fptr.setFD(tmpfd);
            }
        } finally {
            if (locked) fptr.unlock();
        }

        return file;
    }

    public IRubyObject getline(ThreadContext context, IRubyObject separator) {
        return getline(context, separator, -1, null);
    }

    /**
     * getline using logic of gets.  If limit is -1 then read unlimited amount.
     *
     */
    public IRubyObject getline(ThreadContext context, IRubyObject separator, long limit) {
        return getline(context, separator, limit, null);
    }

    private IRubyObject getline(ThreadContext context, IRubyObject separator, long limit, ByteListCache cache) {
        return getlineInner(context, separator, (int)limit, cache);
    }

    /**
     * getline using logic of gets.  If limit is -1 then read unlimited amount.
     * mri: rb_io_getline_1 (mostly)
     */
    private IRubyObject getlineInner(ThreadContext context, IRubyObject rs, int _limit, ByteListCache cache) {
        Ruby runtime = context.runtime;
        IRubyObject str = context.nil;
        boolean noLimit = false;
        Encoding enc;

        OpenFile fptr = getOpenFileChecked();

        boolean locked = fptr.lock();
        try {
            fptr.checkCharReadable(context);

            if (rs.isNil() && _limit < 0) {
                str = fptr.readAll(context, 0, context.nil);
                if (((RubyString) str).size() == 0) return context.nil;
            } else if (_limit == 0) {
                return RubyString.newEmptyString(runtime, fptr.readEncoding(runtime));
            } else if (
                    rs == runtime.getGlobalVariables().getDefaultSeparator()
                            && _limit < 0
                            && !fptr.needsReadConversion()
                            && (enc = fptr.readEncoding(runtime)).isAsciiCompatible()) {
                fptr.NEED_NEWLINE_DECORATOR_ON_READ_CHECK();
                return fptr.getlineFast(context, enc, this);
            }

            // slow path logic
            int c, newline = -1;
            byte[] rsptrBytes = null;
            int rsptr = 0;
            int rslen = 0;
            boolean rspara = false;
            int extraLimit = 16;

            fptr.SET_BINARY_MODE();
            enc = getReadEncoding();

            if (!rs.isNil()) {
                RubyString rsStr = (RubyString) rs;
                ByteList rsByteList = rsStr.getByteList();
                rslen = rsByteList.getRealSize();
                if (rslen == 0) {
                    rsptrBytes = PARAGRAPH_SEPARATOR.unsafeBytes();
                    rsptr = PARAGRAPH_SEPARATOR.getBegin();
                    rslen = 2;
                    rspara = true;
                    fptr.swallow(context, '\n');
                    if (!enc.isAsciiCompatible()) {
                        rs = RubyString.newUsAsciiStringShared(runtime, rsptrBytes, rsptr, rslen);
                        rs = EncodingUtils.rbStrEncode(context, rs, runtime.getEncodingService().convertEncodingToRubyEncoding(enc), 0, context.nil);
                        rs.setFrozen(true);
                        rsStr = (RubyString) rs;
                        rsByteList = rsStr.getByteList();
                        rsptrBytes = rsByteList.getUnsafeBytes();
                        rsptr = rsByteList.getBegin();
                        rslen = rsByteList.getRealSize();
                    }
                } else {
                    rsptrBytes = rsByteList.unsafeBytes();
                    rsptr = rsByteList.getBegin();
                }
                newline = rsptrBytes[rsptr + rslen - 1] & 0xFF;
            }

            ByteList buf = cache != null ? cache.allocate(0) : new ByteList(0);
            try {
                boolean bufferString = str instanceof RubyString;
                ByteList[] strPtr = { bufferString ? ((RubyString) str).getByteList() : null };

                int[] limit_p = {_limit};
                while ((c = fptr.appendline(context, newline, strPtr, limit_p)) != OpenFile.EOF) {
                    int s, p, pp, e;

                    if (c == newline) {
                        if (strPtr[0].getRealSize() < rslen) continue;
                        s = strPtr[0].getBegin();
                        e = s + strPtr[0].getRealSize();
                        p = e - rslen;
                        pp = enc.leftAdjustCharHead(strPtr[0].getUnsafeBytes(), s, p, e);
                        if (pp != p) continue;
                        if (ByteList.memcmp(strPtr[0].getUnsafeBytes(), p, rsptrBytes, rsptr, rslen) == 0) break;
                    }
                    if (limit_p[0] == 0) {
                        s = strPtr[0].getBegin();
                        p = s + strPtr[0].getRealSize();
                        pp = enc.leftAdjustCharHead(strPtr[0].getUnsafeBytes(), s, p - 1, p);
                        if (extraLimit != 0 &&
                                StringSupport.MBCLEN_NEEDMORE_P(StringSupport.preciseLength(enc, strPtr[0].getUnsafeBytes(), pp, p))) {
                            limit_p[0] = 1;
                            extraLimit--;
                        } else {
                            noLimit = true;
                            break;
                        }
                    }
                }
                _limit = limit_p[0];
                if (strPtr[0] != null) {
                    if (bufferString) {
                        if (strPtr[0] != ((RubyString) str).getByteList()) {
                            ((RubyString) str).setValue(strPtr[0]);
                        } else {
                            // same BL as before
                        }
                    } else {
                        // create string
                        str = runtime.newString(strPtr[0]);
                    }
                }

                if (rspara && c != OpenFile.EOF) {
                    // FIXME: This may block more often than it should, to clean up extraneous newlines
                    fptr.swallow(context, '\n');
                }
                if (!str.isNil()) {
                    str = EncodingUtils.ioEncStr(runtime, str, fptr);
                }
            } finally {
                if (cache != null) cache.release(buf);
            }

            if (!str.isNil() && !noLimit) {
                fptr.incrementLineno(runtime, this);
            }
        } finally {
            if (locked) fptr.unlock();
        }

        return str;
    }

    // fptr->enc and codeconv->enc
    public Encoding getEnc() {
        return openFile.encs.enc;
    }

    // mri: io_read_encoding
    public Encoding getReadEncoding() {
        return openFile.readEncoding(getRuntime());
    }

    // fptr->enc2 and codeconv->enc2
    public Encoding getEnc2() {
        return openFile.encs.enc2;
    }

    // mri: io_input_encoding
    public Encoding getInputEncoding() {
        return openFile.inputEncoding(getRuntime());
    }

    private static final String VENDOR;
    static { String v = SafePropertyAccessor.getProperty("java.VENDOR") ; VENDOR = (v == null) ? "" : v; };
    private static final String msgEINTR = "Interrupted system call";

    // FIXME: We needed to use this to raise an appropriate error somewhere...find where...I think IRB related when suspending process?
    public static boolean restartSystemCall(Exception e) {
        return VENDOR.startsWith("Apple") && e.getMessage().equals(msgEINTR);
    }

    // IO class methods.

    @JRubyMethod(name = "new", rest = true, meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyClass klass = (RubyClass)recv;

        if (block.isGiven()) {
            String className = klass.getName();
            context.runtime.getWarnings().warn(
                    ID.BLOCK_NOT_ACCEPTED,
                    className + "::new() does not take block; use " + className + "::open() instead");
        }

        return klass.newInstance(context, args, block);
    }

    @JRubyMethod(rest = true, meta = true)
    public static IRubyObject for_fd(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyClass klass = (RubyClass)recv;

        return klass.newInstance(context, args, block);
    }

    private IRubyObject initializeCommon(ThreadContext context, int fileno, IRubyObject vmodeArg, IRubyObject opt) {
        Ruby runtime = context.runtime;

        ChannelFD fd;

        if (!FilenoUtil.isFake(fileno)) {
            // try using existing ChannelFD, then fall back on creating a new one
            fd = runtime.getFilenoUtil().getWrapperFromFileno(fileno);

            if (fd == null) {
                if (Platform.IS_WINDOWS) {
                    // Native channels don't work quite right on Windows yet. See jruby/jruby#3625
                    switch (fileno) {
                        case 0:
                            fd = new ChannelFD(Channels.newChannel(runtime.getIn()), runtime.getPosix(), runtime.getFilenoUtil());
                            break;
                        case 1:
                            fd = new ChannelFD(Channels.newChannel(runtime.getOut()), runtime.getPosix(), runtime.getFilenoUtil());
                            break;
                        case 2:
                            fd = new ChannelFD(Channels.newChannel(runtime.getErr()), runtime.getPosix(), runtime.getFilenoUtil());
                            break;
                        default:
                            throw runtime.newErrnoEBADFError("Windows does not support wrapping native file descriptor: " + fileno);
                    }
                } else {
                    fd = new ChannelFD(new NativeDeviceChannel(fileno), runtime.getPosix(), runtime.getFilenoUtil());
                }
            }
        } else {
            ChannelFD descriptor = runtime.getFilenoUtil().getWrapperFromFileno(fileno);

            if (descriptor == null) throw runtime.newErrnoEBADFError();

            fd = descriptor;
        }

        if (!fd.ch.isOpen()) {
            throw runtime.newErrnoEBADFError();
        }

        return initializeCommon(context, fd, vmodeArg, opt);
    }

    private IRubyObject initializeCommon(ThreadContext context, ChannelFD fd, IRubyObject vmodeArg, IRubyObject opt) {
        Ruby runtime = context.runtime;

        int ofmode;
        int[] oflags_p = {ModeFlags.RDONLY};

        if(opt != null && !opt.isNil() && !(opt instanceof RubyHash) && !(sites(context).respond_to_to_hash.respondsTo(context, opt, opt))) {
            throw runtime.newArgumentError("last argument must be a hash!");
        }

        if (opt != null && !opt.isNil()) {
            opt = opt.convertToHash();
        }

        if (!fd.ch.isOpen()) {
            throw runtime.newErrnoEBADFError();
        }

        Object pm = EncodingUtils.vmodeVperm(vmodeArg, runtime.newFixnum(0));
        int[] fmode_p = {0};
        ConvConfig convconfig = new ConvConfig();
        EncodingUtils.extractModeEncoding(context, convconfig, pm, opt, oflags_p, fmode_p);

        { // normally done with fcntl...which we *could* do too...but this is just checking read/write
            oflags_p[0] = ModeFlags.oflagsFrom(runtime.getPosix(), fd.ch);

            ofmode = ModeFlags.getOpenFileFlagsFor(oflags_p[0]);
            if (EncodingUtils.vmode(pm) == null || EncodingUtils.vmode(pm).isNil()) {
                fmode_p[0] = ofmode;
            } else if (((~ofmode & fmode_p[0]) & OpenFile.READWRITE) != 0) {
                throw runtime.newErrnoEINVALError();
            }
        }

        if (opt != null && !opt.isNil() && ((RubyHash)opt).op_aref(context, runtime.newSymbol("autoclose")) == runtime.getFalse()) {
            fmode_p[0] |= OpenFile.PREP;
        }

        // JRUBY-4650: Make sure we clean up the old data, if it's present.
        MakeOpenFile();

        openFile.setFD(fd);
        openFile.setMode(fmode_p[0]);
        openFile.encs = convconfig;
        openFile.clearCodeConversion();

        openFile.checkTTY();
        switch (fd.bestFileno()) {
            case 0:
                openFile.stdio_file = System.in;
                break;
            case 1:
                openFile.stdio_file = System.out;
                break;
            case 2:
                openFile.stdio_file = System.err;
                break;
        }

        if (openFile.isBOM()) {
            EncodingUtils.ioSetEncodingByBOM(context, this);
        }

        return this;
    }

    @JRubyMethod(name = "initialize", visibility = PRIVATE)
    public IRubyObject initialize(ThreadContext context, IRubyObject fileNumber, Block unused) {
        return initializeCommon(context, RubyNumeric.fix2int(fileNumber), null, context.nil);
    }

    @JRubyMethod(name = "initialize", visibility = PRIVATE)
    public IRubyObject initialize(ThreadContext context, IRubyObject fileNumber, IRubyObject second, Block unused) {
        int fileno = RubyNumeric.fix2int(fileNumber);
        IRubyObject vmode = null;
        IRubyObject options;
        IRubyObject hashTest = TypeConverter.checkHashType(context.runtime, second);
        if (hashTest instanceof RubyHash) {
            options = hashTest;
        } else {
            options = context.nil;
            vmode = second;
        }

        return initializeCommon(context, fileno, vmode, options);
    }

    @JRubyMethod(name = "initialize", visibility = PRIVATE)
    public IRubyObject initialize(ThreadContext context, IRubyObject fileNumber, IRubyObject modeValue, IRubyObject options, Block unused) {
        int fileno = RubyNumeric.fix2int(fileNumber);

        return initializeCommon(context, fileno, modeValue, options);
    }

    // Encoding processing
    protected IOOptions parseIOOptions(IRubyObject arg) {
        Ruby runtime = getRuntime();

        if (arg instanceof RubyFixnum) return newIOOptions(runtime, (int) RubyFixnum.fix2long(arg));

        String modeString = arg.convertToString().toString();
        try {
            return new IOOptions(runtime, modeString);
        } catch (InvalidValueException ive) {
            throw runtime.newArgumentError("invalid access mode " + modeString);
        }
    }

    @JRubyMethod
    public IRubyObject external_encoding(ThreadContext context) {
        EncodingService encodingService = context.runtime.getEncodingService();

        if (openFile.encs.enc2 != null) return encodingService.getEncoding(openFile.encs.enc2);

        if (openFile.isWritable()) {
            return openFile.encs.enc == null ? context.runtime.getNil() : encodingService.getEncoding(openFile.encs.enc);
        }

        return encodingService.getEncoding(getReadEncoding());
    }

    @JRubyMethod
    public IRubyObject internal_encoding(ThreadContext context) {
        if (openFile.encs.enc2 == null) return context.nil;

        return context.runtime.getEncodingService().getEncoding(getReadEncoding());
    }

    @JRubyMethod
    public IRubyObject set_encoding(ThreadContext context, IRubyObject encodingObj) {
        setEncoding(context, encodingObj, context.nil, context.nil);

        return context.nil;
    }

    @JRubyMethod
    public IRubyObject set_encoding(ThreadContext context, IRubyObject encodingString, IRubyObject internalEncoding) {
        IRubyObject opt = TypeConverter.checkHashType(context.runtime, internalEncoding);
        if (!opt.isNil()) {
            setEncoding(context, encodingString, context.nil, opt);
        } else {
            setEncoding(context, encodingString, internalEncoding, context.nil);
        }

        return context.nil;
    }

    @JRubyMethod
    public IRubyObject set_encoding(ThreadContext context, IRubyObject encodingString, IRubyObject internalEncoding, IRubyObject options) {
        setEncoding(context, encodingString, internalEncoding, options);

        return context.nil;
    }

    // mri: io_encoding_set
    public void setEncoding(ThreadContext context, IRubyObject v1, IRubyObject v2, IRubyObject opt) {
        IOEncodable.ConvConfig holder = new IOEncodable.ConvConfig();
        int ecflags = openFile.encs.ecflags;
        IRubyObject[] ecopts_p = {context.nil};
        IRubyObject tmp;

        if (!v2.isNil()) {
            holder.enc2 = EncodingUtils.rbToEncoding(context, v1);
            tmp = v2.checkStringType19();

            if (!tmp.isNil()) {
                RubyString internalAsString = (RubyString)tmp;

                // No encoding '-'
                if (internalAsString.size() == 1 && internalAsString.asJavaString().equals("-")) {
                    /* Special case - "-" => no transcoding */
                    holder.enc = holder.enc2;
                    holder.enc2 = null;
                } else {
                    holder.enc = EncodingUtils.rbToEncoding(context, internalAsString);
                }

                if (holder.enc == holder.enc2) {
                    /* Special case - "-" => no transcoding */
                    holder.enc2 = null;
                }
            } else {
                holder.enc = EncodingUtils.rbToEncoding(context, v2);

                if (holder.enc == holder.enc2) {
                    /* Special case - "-" => no transcoding */
                    holder.enc2 = null;
                }
            }
            EncodingUtils.SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(holder.getEnc2(), ecflags);
            ecflags = EncodingUtils.econvPrepareOptions(context, opt, ecopts_p, ecflags);
        } else {
            if (v1.isNil()) {
                EncodingUtils.ioExtIntToEncs(context, holder, null, null, 0);
                EncodingUtils.SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(holder.getEnc2(), ecflags);
                ecopts_p[0] = context.nil;
            } else {
                tmp = v1.checkStringType19();
                if (!tmp.isNil() && EncodingUtils.encAsciicompat(EncodingUtils.encGet(context, tmp))) {
                    EncodingUtils.parseModeEncoding(context, holder, tmp.asJavaString(), null);
                    EncodingUtils.SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(holder.getEnc2(), ecflags);
                    ecflags = EncodingUtils.econvPrepareOptions(context, opt, ecopts_p, ecflags);
                } else {
                    EncodingUtils.ioExtIntToEncs(context, holder, EncodingUtils.rbToEncoding(context, v1), null, 0);
                    EncodingUtils.SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(holder.getEnc2(), ecflags);
                }
            }
            // enc, enc2 should be set by now
        }

        int[] fmode_p = {openFile.getMode()};
        EncodingUtils.validateEncodingBinmode(context, fmode_p, ecflags, holder);
        openFile.setMode(fmode_p[0]);

        openFile.encs.enc = holder.enc;
        openFile.encs.enc2 = holder.enc2;
        openFile.encs.ecflags = ecflags;
        openFile.encs.ecopts = ecopts_p[0];

        openFile.clearCodeConversion();
    }

    // rb_io_s_open, 2014/5/16
    @JRubyMethod(required = 1, rest = true, meta = true)
    public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        IRubyObject io = ((RubyClass)recv).newInstance(context, args, Block.NULL_BLOCK);

        return ensureYieldClose(context, io, block);
    }

    public static IRubyObject ensureYieldClose(ThreadContext context, IRubyObject port, Block block) {
        if (block.isGiven()) {
            Ruby runtime = context.runtime;
            try {
                return block.yield(context, port);
            } finally {
                ioClose(runtime, port);
            }
        }
        return port;
    }

    public static IRubyObject sysopen(IRubyObject recv, IRubyObject[] args, Block block) {
        return sysopen19(recv.getRuntime().getCurrentContext(), recv, args, block);
    }

    // rb_io_s_sysopen
    @JRubyMethod(name = "sysopen", required = 1, optional = 2, meta = true)
    public static IRubyObject sysopen19(ThreadContext context, IRubyObject recv, IRubyObject[] argv, Block block) {
        Ruby runtime = context.runtime;
        IRubyObject fname, vmode, vperm;
        fname = vmode = vperm = context.nil;
        IRubyObject intmode;
        int oflags;
        ChannelFD fd;
        int perm;

        switch (argv.length) {
            case 3:
                vperm = argv[2];
            case 2:
                vmode = argv[1];
            case 1:
                fname = argv[0];
        }
        fname = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, fname));

        if (vmode.isNil())
            oflags = OpenFlags.O_RDONLY.intValue();
        else if (!(intmode = TypeConverter.checkIntegerType(context, vmode)).isNil())
            oflags = RubyNumeric.num2int(intmode);
        else {
            vmode = vmode.convertToString();
            oflags = OpenFile.ioModestrOflags(runtime, vmode.toString());
        }
        if (vperm.isNil()) perm = 0666;
        else              perm = RubyNumeric.num2int(vperm);

        StringSupport.checkStringSafety(context.runtime, fname);
        fname = ((RubyString)fname).dupFrozen();
        fd = sysopen(runtime, fname.toString(), oflags, perm);
        return runtime.newFixnum(fd.bestFileno());
    }

    private static class Sysopen {
        String fname;
        int oflags;
        int perm;
        Errno errno;
    }

    // rb_sysopen
    protected static ChannelFD sysopen(Ruby runtime, String fname, int oflags, int perm) {
        ChannelFD fd;
        Sysopen data = new Sysopen();

        data.fname = fname;
        data.oflags = oflags;
        data.perm = perm;

        fd = sysopenInternal(runtime, data);
        if (fd == null) {
            if (data.errno != null) {
                throw runtime.newErrnoFromErrno(data.errno, fname.toString());
            } else {
                throw runtime.newSystemCallError(fname.toString());
            }
        }
        return fd;
    }

    // rb_sysopen_internal
    private static ChannelFD sysopenInternal(Ruby runtime, Sysopen data) {
        ChannelFD fd;
        // TODO: thread eventing as in MRI
        fd = sysopenFunc(runtime, data);
//        if (0 <= fd)
//            rb_update_max_fd(fd);
        return fd;
    }

    // sysopen_func
    private static ChannelFD sysopenFunc(Ruby runtime, Sysopen data) {
        return cloexecOpen(runtime, data);
    }

    // rb_cloexec_open
    private static ChannelFD cloexecOpen(Ruby runtime, Sysopen data)
    {
        Channel ret = null;
//        #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
        PosixShim shim = new PosixShim(runtime);
        ret = shim.open(runtime.getCurrentDirectory(), data.fname, ModeFlags.createModeFlags(data.oflags), data.perm);
        if (ret == null) {
            data.errno = shim.errno;
            return null;
        }
        // TODO, if we need it?
//        rb_maygvl_fd_fix_cloexec(ret);
        return new ChannelFD(ret, runtime.getPosix(), runtime.getFilenoUtil());
    }

    // MRI: rb_io_autoclose_p
    public boolean isAutoclose() {
        OpenFile fptr;
        fptr = getOpenFileChecked();
        return fptr.isAutoclose();
    }

    // MRI: rb_io_set_autoclose
    public void setAutoclose(boolean autoclose) {
        OpenFile fptr;
        fptr = getOpenFileChecked();
        fptr.setAutoclose(autoclose);
    }

    @JRubyMethod(name = "autoclose?")
    public IRubyObject autoclose(ThreadContext context) {
        return context.runtime.newBoolean(isAutoclose());
    }

    @JRubyMethod(name = "autoclose=")
    public IRubyObject autoclose_set(ThreadContext context, IRubyObject autoclose) {
        setAutoclose(autoclose.isTrue());
        return context.nil;
    }

    // MRI: rb_io_binmode_m
    @JRubyMethod(name = "binmode")
    public IRubyObject binmode() {
        setAscii8bitBinmode();

        RubyIO write_io = GetWriteIO();
        if (write_io != this)
             write_io.setAscii8bitBinmode();

        return this;
    }

    // MRI: rb_io_binmode_p
    @JRubyMethod(name = "binmode?")
    public IRubyObject op_binmode(ThreadContext context) {
        return RubyBoolean.newBoolean(context.runtime, getOpenFileChecked().isBinmode());
    }

    // rb_io_syswrite
    @JRubyMethod(name = "syswrite", required = 1)
    public IRubyObject syswrite(ThreadContext context, IRubyObject str) {
       Ruby runtime = context.runtime;
        OpenFile fptr;
        long n;

        if (!(str instanceof RubyString))
            str = str.asString();

        RubyIO io = GetWriteIO();
        fptr = io.getOpenFileChecked();

        boolean locked = fptr.lock();
        try {
            fptr.checkWritable(context);

            str = str.convertToString().dupFrozen();

            if (fptr.wbuf.len != 0) {
                runtime.getWarnings().warn("syswrite for buffered IO");
            }

            ByteList strByteList = ((RubyString) str).getByteList();
            n = OpenFile.writeInternal(context, fptr, fptr.fd(), strByteList.unsafeBytes(), strByteList.begin(), strByteList.getRealSize());

            if (n == -1) throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());
        } finally {
            if (locked) fptr.unlock();
        }

        return runtime.newFixnum(n);
    }

    // MRI: rb_io_write_nonblock
    @JRubyMethod(name = "write_nonblock", required = 1, optional = 1)
    public IRubyObject write_nonblock(ThreadContext context, IRubyObject[] argv) {
        Ruby runtime = context.runtime;
        IRubyObject str;
        IRubyObject opts = context.nil;

        boolean exception = ArgsUtil.extractKeywordArg(context, "exception", argv) != runtime.getFalse();

        str = argv[0];

        return ioWriteNonblock(context, runtime, str, !exception);
    }

    // MRI: io_write_nonblock
    private IRubyObject ioWriteNonblock(ThreadContext context, Ruby runtime, IRubyObject str, boolean no_exception) {
        OpenFile fptr;
        long n;

        if (!(str instanceof RubyString))
            str = str.asString();

        RubyIO io = GetWriteIO();
        fptr = io.getOpenFileChecked();

        boolean locked = fptr.lock();
        try {
            fptr.checkWritable(context);

            if (fptr.io_fflush(context) < 0)
                throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());

            fptr.setNonblock(runtime);

            ByteList strByteList = ((RubyString) str).getByteList();
            n = fptr.posix.write(fptr.fd(), strByteList.unsafeBytes(), strByteList.begin(), strByteList.getRealSize(), true);

            if (n == -1) {
                if (fptr.posix.errno == Errno.EWOULDBLOCK || fptr.posix.errno == Errno.EAGAIN) {
                    if (no_exception) {
                        return runtime.newSymbol("wait_writable");
                    } else {
                        throw runtime.newErrnoEAGAINWritableError("write would block");
                    }
                }
                throw runtime.newErrnoFromErrno(fptr.posix.errno, fptr.getPath());
            }
        } finally {
            if (locked) fptr.unlock();
        }

        return runtime.newFixnum(n);
    }

    public RubyIO GetWriteIO() {
        RubyIO writeIO;
        checkInitialized();
        writeIO = openFile.tiedIOForWriting;
        if (writeIO != null) {
            return writeIO;
        }
        return this;
    }

    private void checkInitialized() {
        if (openFile == null) {
            throw getRuntime().newIOError("uninitialized stream");
        }
    }

    /** io_write_m
     *
     */
    @JRubyMethod(name = "write", required = 1)
    public IRubyObject write(ThreadContext context, IRubyObject str) {
        return write(context, str, false);
    }

    // io_write
    public IRubyObject write(ThreadContext context, IRubyObject str, boolean nosync) {
        Ruby runtime = context.runtime;
        OpenFile fptr;
        long n;
        IRubyObject tmp;

        RubyIO io = GetWriteIO();

        str = str.asString();
        tmp = TypeConverter.ioCheckIO(runtime, io);
        if (tmp.isNil()) {
	        /* port is not IO, call write method for it. */
            return sites(context).write.call(context, io, io, str);
        }
        io = (RubyIO)tmp;
        if (((RubyString)str).size() == 0) return RubyFixnum.zero(runtime);

        fptr = io.getOpenFileChecked();

        boolean locked = fptr.lock();
        try {
            fptr = io.getOpenFileChecked();
            fptr.checkWritable(context);

            n = fptr.fwrite(context, str, nosync);
            if (n == -1) throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath());
        } finally {
            if (locked) fptr.unlock();
        }

        return RubyFixnum.newFixnum(runtime, n);
    }

    /** rb_io_addstr
     *
     */
    @JRubyMethod(name = "<<", required = 1)
    public IRubyObject op_append(ThreadContext context, IRubyObject anObject) {
        // Claims conversion is done via 'to_s' in docs.
        sites(context).write.call(context, this, this, anObject);

        return this;
    }

    @JRubyMethod(name = "fileno", alias = "to_i")
    public RubyFixnum fileno(ThreadContext context) {
        return context.runtime.newFixnum(getOpenFileChecked().getFileno());
    }

    /** Returns the current line number.
     *
     * @return the current line number.
     */
    @JRubyMethod(name = "lineno")
    public RubyFixnum lineno(ThreadContext context) {
        return context.runtime.newFixnum(getOpenFileChecked().getLineNumber());
    }

    /** Sets the current line number.
     *
     * @param newLineNumber The new line number.
     */
    @JRubyMethod(name = "lineno=", required = 1)
    public RubyFixnum lineno_set(ThreadContext context, IRubyObject newLineNumber) {
        getOpenFileChecked().setLineNumber(RubyNumeric.fix2int(newLineNumber));

        return context.runtime.newFixnum(getOpenFileChecked().getLineNumber());
    }

    /** Returns the current sync mode.
     *
     * MRI: rb_io_sync
     *
     * @return the current sync mode.
     */
    @JRubyMethod
    public RubyBoolean sync(ThreadContext context) {
        Ruby runtime = context.runtime;
        OpenFile fptr;

        RubyIO io = GetWriteIO();
        fptr = io.getOpenFileChecked();
        fptr.lock();
        try {
            return (fptr.getMode() & OpenFile.SYNC) != 0 ? runtime.getTrue() : runtime.getFalse();
        } finally {
            fptr.unlock();
        }
    }

    /**
     * 

Return the process id (pid) of the process this IO object * spawned. If no process exists (popen was not called), then * nil is returned. This is not how it appears to be defined * but ruby 1.8 works this way.

* * @return the pid or nil */ @JRubyMethod public IRubyObject pid(ThreadContext context) { OpenFile myOpenFile = getOpenFileChecked(); if (myOpenFile.getProcess() == null) { return context.runtime.getNil(); } // Of course this isn't particularly useful. long pid = myOpenFile.getPid(); return context.runtime.newFixnum(pid); } // rb_io_pos @JRubyMethod(name = {"pos", "tell"}) public RubyFixnum pos(ThreadContext context) { OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { long pos = fptr.tell(context); if (pos == -1 && fptr.errno() != null) throw context.runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath()); pos -= fptr.rbuf.len; return context.runtime.newFixnum(pos); } finally { if (locked) fptr.unlock(); } } // rb_io_set_pos @JRubyMethod(name = "pos=", required = 1) public RubyFixnum pos_set(ThreadContext context, IRubyObject offset) { OpenFile fptr; long pos; pos = offset.convertToInteger().getLongValue(); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { pos = fptr.seek(context, pos, PosixShim.SEEK_SET); if (pos == -1 && fptr.errno() != null) throw context.runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath()); } finally { if (locked) fptr.unlock(); } return context.runtime.newFixnum(pos); } /** Print some objects to the stream. * */ @JRubyMethod(rest = true, reads = FrameField.LASTLINE) public IRubyObject print(ThreadContext context, IRubyObject[] args) { return print(context, this, args); } /** * Print some objects to the stream. * * MRI: rb_io_print */ public static IRubyObject print(ThreadContext context, IRubyObject out, IRubyObject[] args) { Ruby runtime = context.runtime; int i; IRubyObject line; int argc = args.length; /* if no argument given, print `$_' */ if (argc == 0) { argc = 1; line = context.getLastLine(); args = new IRubyObject[]{line}; } for (i=0; i0) { write(context, out, outputFS); } write(context, out, args[i]); } IRubyObject outputRS = runtime.getGlobalVariables().get("$\\"); if (argc > 0 && !outputRS.isNil()) { write(context, out, outputRS); } return context.nil; } @JRubyMethod(required = 1, rest = true) public IRubyObject printf(ThreadContext context, IRubyObject[] args) { write(context, this, RubyKernel.sprintf(context, this, args)); return context.nil; } @JRubyMethod(required = 1) public IRubyObject putc(ThreadContext context, IRubyObject ch) { IRubyObject str; if (ch instanceof RubyString) { str = ((RubyString)ch).substr(context.runtime, 0, 1); } else { str = RubyString.newStringShared(context.runtime, RubyFixnum.SINGLE_CHAR_BYTELISTS19[RubyNumeric.num2chr(ch) & 0xFF]); } sites(context).write.call(context, this, this, str); return ch; } public static IRubyObject putc(ThreadContext context, IRubyObject maybeIO, IRubyObject object) { if (maybeIO instanceof RubyIO) { ((RubyIO)maybeIO).putc(context, object); } else { byte c = RubyNumeric.num2chr(object); IRubyObject str = RubyString.newStringShared(context.runtime, RubyFixnum.SINGLE_CHAR_BYTELISTS19[c & 0xFF]); sites(context).write.call(context, maybeIO, maybeIO, str); } return object; } public RubyFixnum seek(ThreadContext context, IRubyObject[] args) { int whence = PosixShim.SEEK_SET; if (args.length > 1) { whence = interpretSeekWhence(args[1]); } return doSeek(context, args[0], whence); } @JRubyMethod public RubyFixnum seek(ThreadContext context, IRubyObject arg0) { int whence = PosixShim.SEEK_SET; return doSeek(context, arg0, whence); } @JRubyMethod public RubyFixnum seek(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { int whence = interpretSeekWhence(arg1); return doSeek(context, arg0, whence); } // rb_io_seek private RubyFixnum doSeek(ThreadContext context, IRubyObject offset, int whence) { OpenFile fptr; long pos; pos = RubyNumeric.num2long(offset); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { pos = fptr.seek(context, pos, whence); if (pos < 0 && fptr.errno() != null) throw getRuntime().newErrnoFromErrno(fptr.errno(), fptr.getPath()); } finally { if (locked) fptr.unlock(); } return RubyFixnum.zero(context.runtime); } // This was a getOpt with one mandatory arg, but it did not work // so I am parsing it for now. @JRubyMethod(required = 1, optional = 1) public RubyFixnum sysseek(ThreadContext context, IRubyObject[] args) { final Ruby runtime = context.runtime; IRubyObject offset = context.nil; int whence = PosixShim.SEEK_SET; OpenFile fptr; long pos; switch (args.length) { case 2: IRubyObject ptrname = args[1]; whence = interpretSeekWhence(ptrname); case 1: offset = args[0]; } pos = offset.convertToInteger().getLongValue(); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { if ((fptr.isReadable()) && (fptr.READ_DATA_BUFFERED() || fptr.READ_CHAR_PENDING())) { throw runtime.newIOError("sysseek for buffered IO"); } if (fptr.isWritable() && fptr.wbuf.len != 0) { runtime.getWarnings().warn("sysseek for buffered IO"); } fptr.errno(null); pos = fptr.posix.lseek(fptr.fd(), pos, whence); if (pos == -1 && fptr.errno() != null) throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath()); } finally { if (locked) fptr.unlock(); } return RubyFixnum.newFixnum(runtime, pos); } private static int interpretSeekWhence(IRubyObject vwhence) { if (vwhence instanceof RubySymbol) { if (vwhence.toString() == "SET") return PosixShim.SEEK_SET; if (vwhence.toString() == "CUR") return PosixShim.SEEK_CUR; if (vwhence.toString() == "END") return PosixShim.SEEK_END; } return (int)vwhence.convertToInteger().getLongValue(); } // rb_io_rewind @JRubyMethod public RubyFixnum rewind(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr; fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { if (fptr.seek(context, 0L, 0) == -1 && fptr.errno() != null) { throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath()); } if (RubyArgsFile.ArgsFileData.getArgsFileData(runtime).isCurrentFile(this)) { runtime.setCurrentLine(runtime.getCurrentLine() - fptr.getLineNumber()); } fptr.setLineNumber(0); if (fptr.readconv != null) { fptr.clearReadConversion(); } } finally { if (locked) fptr.unlock(); } return RubyFixnum.zero(runtime); } // rb_io_fsync @JRubyMethod public RubyFixnum fsync(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr; RubyIO io = GetWriteIO(); fptr = io.getOpenFileChecked(); if (fptr.io_fflush(context) < 0) throw runtime.newSystemCallError(""); if (!Platform.IS_WINDOWS) { /* already called in io_fflush() */ try { if (fptr.fileChannel() != null) fptr.fileChannel().force(true); if (fptr.fd().chNative != null) { int ret = runtime.getPosix().fsync(fptr.fd().chNative.getFD()); if (ret < 0) throw runtime.newErrnoFromInt(runtime.getPosix().errno()); } } catch (IOException ioe) { throw runtime.newIOErrorFromException(ioe); } } return RubyFixnum.zero(runtime); } /** Sets the current sync mode. * * MRI: rb_io_set_sync * * @param sync The new sync mode. */ @JRubyMethod(name = "sync=", required = 1) public IRubyObject sync_set(IRubyObject sync) { setSync(sync.isTrue()); return sync; } public void setSync(boolean sync) { RubyIO io = GetWriteIO(); OpenFile fptr = io.getOpenFileChecked(); fptr.setSync(sync); } public boolean getSync() { RubyIO io = GetWriteIO(); OpenFile fptr = io.getOpenFileChecked(); return fptr.isSync(); } // rb_io_eof @JRubyMethod(name = {"eof?", "eof"}) public RubyBoolean eof_p(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr; fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); if (fptr.READ_CHAR_PENDING()) return runtime.getFalse(); if (fptr.READ_DATA_PENDING()) return runtime.getFalse(); fptr.READ_CHECK(context); // #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) // if (!NEED_READCONV(fptr) && NEED_NEWLINE_DECORATOR_ON_READ(fptr)) { // return eof(fptr->fd) ? Qtrue : Qfalse; // } // #endif if (fptr.fillbuf(context) < 0) { return runtime.getTrue(); } } finally { if (locked) fptr.unlock(); } return runtime.getFalse(); } @JRubyMethod(name = {"tty?", "isatty"}) public RubyBoolean tty_p(ThreadContext context) { Ruby runtime = context.runtime; POSIX posix = runtime.getPosix(); OpenFile fptr; fptr = getOpenFileChecked(); fptr.lock(); try { if (posix.isNative() && fptr.fd().realFileno != -1) { return posix.libc().isatty(fptr.getFileno()) == 0 ? runtime.getFalse() : runtime.getTrue(); } else if (fptr.isStdio()) { // This is a bit of a hack for platforms where we can't do native stdio return runtime.getTrue(); } } finally { fptr.unlock(); } return runtime.getFalse(); } // rb_io_init_copy @JRubyMethod(required = 1, visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(IRubyObject _io){ RubyIO dest = this; Ruby runtime = getRuntime(); ThreadContext context = runtime.getCurrentContext(); OpenFile fptr, orig; ChannelFD fd; RubyIO write_io; long pos; RubyIO io = TypeConverter.ioGetIO(runtime, _io); if (!OBJ_INIT_COPY(dest, io)) return dest; orig = io.getOpenFileChecked(); fptr = dest.MakeOpenFile(); // orig is the visible one here but we lock both anyway boolean locked1 = orig.lock(); boolean locked2 = fptr.lock(); try { io.flush(context); /* copy rb_io_t structure */ fptr.setMode(orig.getMode() & ~OpenFile.PREP); fptr.encs = orig.encs; fptr.setProcess(orig.getProcess()); fptr.setLineNumber(orig.getLineNumber()); if (orig.getPath() != null) fptr.setPath(orig.getPath()); fptr.setFinalizer(orig.getFinalizer()); // TODO: not using pipe_finalize yet // #if defined (__CYGWIN__) || !defined(HAVE_FORK) // if (fptr.finalize == pipe_finalize) // pipe_add_fptr(fptr); // #endif fd = orig.fd().dup(); fptr.setFD(fd); pos = orig.tell(context); if (pos == -1) fptr.seek(context, pos, PosixShim.SEEK_SET); } finally { if (locked2) fptr.unlock(); if (locked1) orig.unlock(); } if (fptr.isBinmode()) { dest.setBinmode(); } write_io = io.GetWriteIO(); if (io != write_io) { write_io = (RubyIO)write_io.dup(); fptr.tiedIOForWriting = write_io; dest.getInstanceVariables().setInstanceVariable("@tied_io_for_writing", write_io); } return dest; } @JRubyMethod(name = "closed?") public RubyBoolean closed_p(ThreadContext context) { return context.runtime.newBoolean(isClosed()); } /** * Is this IO closed * * MRI: rb_io_closed * * @return true if closed */ public boolean isClosed() { OpenFile fptr; RubyIO write_io; OpenFile write_fptr; write_io = GetWriteIO(); if (this != write_io) { write_fptr = write_io.openFile; if (write_fptr != null && write_fptr.fd() != null) { return false; } } fptr = openFile; checkInitialized(); return fptr.fd() == null; } /** *

Closes all open resources for the IO. It also removes * it from our magical all open file descriptor pool.

* * @return The IO. Returns nil if the IO was already closed. * * MRI: rb_io_close_m */ @JRubyMethod public IRubyObject close() { Ruby runtime = getRuntime(); if (isClosed()) { return runtime.getNil(); } return rbIoClose(runtime); } // io_close protected static IRubyObject ioClose(Ruby runtime, IRubyObject io) { ThreadContext context = runtime.getCurrentContext(); IOSites sites = sites(context); IRubyObject closed = io.checkCallMethod(context, sites.closed_checked); if (closed != null && closed.isTrue()) return io; IRubyObject oldExc = runtime.getGlobalVariables().get("$!"); // Save $! try { closed = io.checkCallMethod(context, sites.close_checked); return runtime.newBoolean(closed != null && closed.isTrue()); } catch (RaiseException re) { if (re.getMessage().contains(CLOSED_STREAM_MSG)) { // ignore runtime.getGlobalVariables().set("$!", oldExc); // Restore $! return context.nil; } else { throw re; } } } // rb_io_close protected IRubyObject rbIoClose(Ruby runtime) { ThreadContext context = runtime.getCurrentContext(); OpenFile fptr; RubyIO write_io; OpenFile write_fptr; write_io = GetWriteIO(); if (this != write_io) { write_fptr = write_io.openFile; boolean locked = write_fptr.lock(); try { if (write_fptr != null && write_fptr.fd() != null) { write_fptr.cleanup(runtime, true); } } finally { if (locked) write_fptr.unlock(); } } fptr = openFile; boolean locked = fptr.lock(); try { if (fptr == null) return runtime.getNil(); if (fptr.fd() == null) return runtime.getNil(); // interrupt waiting threads fptr.interruptBlockingThreads(); fptr.cleanup(runtime, false); if (fptr.getProcess() != null) { context.setLastExitStatus(context.nil); if (runtime.getPosix().isNative() && fptr.getProcess() instanceof POSIXProcess) { // We do not need to nuke native-launched child process, since we now have full control // over child process pipes. IRubyObject processResult = RubyProcess.RubyStatus.newProcessStatus(runtime, ((POSIXProcess) fptr.getProcess()).status(), fptr.getPid()); context.setLastExitStatus(processResult); } else { // If this is not a popen3/popen4 stream and it has a process, attempt to shut down that process if (!popenSpecial) { obliterateProcess(fptr.getProcess()); // RubyStatus uses real native status now, so we unshift Java's shifted exit status IRubyObject processResult = RubyProcess.RubyStatus.newProcessStatus(runtime, fptr.getProcess().exitValue() << 8, fptr.getPid()); context.setLastExitStatus(processResult); } } fptr.setProcess(null); } } finally { if (locked) fptr.unlock(); } return runtime.getNil(); } // MRI: rb_io_close_write @JRubyMethod public IRubyObject close_write(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr; RubyIO write_io; write_io = GetWriteIO(); fptr = write_io.getOpenFileInitialized(); if (!fptr.isOpen()) return context.nil; boolean locked = fptr.lock(); try { if (fptr.socketChannel() != null) { try { fptr.socketChannel().shutdownOutput(); } catch (IOException ioe) { throw runtime.newErrnoFromErrno(Helpers.errnoFromException(ioe), fptr.getPath()); } fptr.setMode(fptr.getMode() & ~OpenFile.WRITABLE); if (!fptr.isReadable()) return write_io.rbIoClose(runtime); return context.nil; } if (fptr.isReadable() && !fptr.isDuplex()) { throw runtime.newIOError("closing non-duplex IO for writing"); } } finally { if (locked) fptr.unlock(); } if (this != write_io) { fptr = getOpenFileInitialized(); locked = fptr.lock(); try { fptr.tiedIOForWriting = null; } finally { if (locked) fptr.unlock(); } } write_io.rbIoClose(runtime); return context.nil; } @JRubyMethod public IRubyObject close_read(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr; RubyIO write_io; fptr = getOpenFileInitialized(); if (!fptr.isOpen()) return context.nil; boolean locked = fptr.lock(); try { if (fptr.socketChannel() != null) { try { fptr.socketChannel().socket().shutdownInput(); } catch (IOException ioe) { throw runtime.newErrnoFromErrno(Helpers.errnoFromException(ioe), fptr.getPath()); } fptr.setMode(fptr.getMode() & ~OpenFile.READABLE); if (!fptr.isWritable()) return rbIoClose(runtime); return context.nil; } write_io = GetWriteIO(); if (this != write_io) { OpenFile wfptr; wfptr = write_io.getOpenFileInitialized(); boolean locked2 = wfptr.lock(); try { wfptr.setProcess(fptr.getProcess()); wfptr.setPid(fptr.getPid()); fptr.setProcess(null); fptr.setPid(-1); this.openFile = wfptr; /* bind to write_io temporarily to get rid of memory/fd leak */ fptr.tiedIOForWriting = null; write_io.openFile = fptr; fptr.cleanup(runtime, false); /* should not finalize fptr because another thread may be reading it */ return context.nil; } finally { if (locked2) wfptr.unlock(); } } if (fptr.isWritable() && !fptr.isDuplex()) { throw runtime.newIOError("closing non-duplex IO for reading"); } } finally { if (locked) fptr.unlock(); } return rbIoClose(runtime); } @JRubyMethod(name = "close_on_exec=", notImplemented = true) public IRubyObject close_on_exec_set(ThreadContext context, IRubyObject arg) { // TODO: rb_io_set_close_on_exec throw context.runtime.newNotImplementedError("close_on_exec="); } @JRubyMethod(name = "close_on_exec?", notImplemented = true) public IRubyObject close_on_exec_p(ThreadContext context) { // TODO: rb_io_close_on_exec_p throw context.runtime.newNotImplementedError("close_on_exec="); } /** Flushes the IO output stream. * * MRI: rb_io_flush * * @return The IO. */ @JRubyMethod public RubyIO flush(ThreadContext context) { return flushRaw(context, true); } // rb_io_flush_raw protected RubyIO flushRaw(ThreadContext context, boolean sync) { OpenFile fptr; // not possible here // if (!RB_TYPE_P(io, T_FILE)) { // return rb_funcall(io, id_flush, 0); // } RubyIO io = GetWriteIO(); fptr = io.getOpenFileChecked(); boolean locked = fptr.lock(); try { if ((fptr.getMode() & OpenFile.WRITABLE) != 0) { if (fptr.io_fflush(context) < 0) throw context.runtime.newErrnoFromErrno(fptr.errno(), ""); // #ifdef _WIN32 // if (sync && GetFileType((HANDLE)rb_w32_get_osfhandle(fptr->fd)) == FILE_TYPE_DISK) { // rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd); // } // #endif } if ((fptr.getMode() & OpenFile.READABLE) != 0) { fptr.unread(context); } } finally { if (locked) fptr.unlock(); } return io; } /** Read a line. * */ // rb_io_gets_m @JRubyMethod(name = "gets", writes = FrameField.LASTLINE) public IRubyObject gets(ThreadContext context) { IRubyObject separator = prepareGetsSeparator(context, null, null); IRubyObject result = getline(context, separator); if (!result.isNil()) context.setLastLine(result); return result; } // rb_io_gets_m @JRubyMethod(name = "gets", writes = FrameField.LASTLINE) public IRubyObject gets(ThreadContext context, IRubyObject arg) { IRubyObject separator = prepareGetsSeparator(context, arg, null); long limit = prepareGetsLimit(context, arg, null); IRubyObject result = getline(context, separator, limit); if (!result.isNil()) context.setLastLine(result); return result; } // rb_io_gets_m @JRubyMethod(name = "gets", writes = FrameField.LASTLINE) public IRubyObject gets(ThreadContext context, IRubyObject rs, IRubyObject limit_arg) { rs = prepareGetsSeparator(context, rs, limit_arg); long limit = prepareGetsLimit(context, rs, limit_arg); IRubyObject result = getline(context, rs, limit); if (!result.isNil()) context.setLastLine(result); return result; } private IRubyObject prepareGetsSeparator(ThreadContext context, IRubyObject[] args) { switch (args.length) { case 0: return prepareGetsSeparator(context, null, null); case 1: return prepareGetsSeparator(context, args[0], null); case 2: return prepareGetsSeparator(context, args[0], args[1]); } throw new RuntimeException("invalid size for gets args: " + args.length); } // MRI: prepare_getline_args, separator logic private IRubyObject prepareGetsSeparator(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { Ruby runtime = context.runtime; IRubyObject rs = runtime.getRecordSeparatorVar().get(); if (arg0 != null && arg1 == null) { // argc == 1 IRubyObject tmp = context.nil; if (arg0.isNil() || !(tmp = TypeConverter.checkStringType(runtime, arg0)).isNil()) { rs = tmp; } } else if (arg0 != null && arg1 != null) { // argc >= 2 rs = arg0; if (!rs.isNil()) { rs = rs.convertToString(); } } if (!rs.isNil()) { Encoding enc_rs, enc_io; OpenFile fptr = getOpenFileChecked(); enc_rs = ((RubyString)rs).getEncoding(); enc_io = fptr.readEncoding(runtime); if (enc_io != enc_rs && (((RubyString)rs).scanForCodeRange() != StringSupport.CR_7BIT || (((RubyString)rs).size() > 0 && !enc_io.isAsciiCompatible()))) { if (rs == runtime.getGlobalVariables().getDefaultSeparator()) { rs = RubyString.newStringLight(runtime, 0, enc_io); ((RubyString)rs).catAscii(NEWLINE_BYTES, 0, 1); } else { throw runtime.newArgumentError("encoding mismatch: " + enc_io + " IO with " + enc_rs + " RS"); } } } return rs; } private long prepareGetsLimit(ThreadContext context, IRubyObject[] args) { switch (args.length) { case 0: return prepareGetsLimit(context, null, null); case 1: return prepareGetsLimit(context, args[0], null); case 2: return prepareGetsLimit(context, args[0], args[1]); } throw new RuntimeException("invalid size for gets args: " + args.length); } // MRI: prepare_getline_args, limit logic private long prepareGetsLimit(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { Ruby runtime = context.runtime; IRubyObject lim = context.nil; if (arg0 != null && arg1 == null) { // argc == 1 IRubyObject tmp = context.nil; if (arg0.isNil() || !(tmp = TypeConverter.checkStringType(runtime, arg0)).isNil()) { // only separator logic } else { lim = arg0; } } else if (arg0 != null && arg1 != null) { // argc >= 2 lim = arg1; } return lim.isNil() ? -1 : lim.convertToInteger().getLongValue(); } private IRubyObject gets(ThreadContext context, IRubyObject[] args) { switch (args.length) { case 0: return gets(context); case 1: return gets(context, args[0]); case 2: return gets(context, args[0], args[1]); default: Arity.raiseArgumentError(context, args.length, 0, 2); return null; // not reached } } public boolean getBlocking() { return openFile.isBlocking(); } public void setBlocking(boolean blocking) { openFile.setBlocking(getRuntime(), blocking); } @JRubyMethod(name = "fcntl") public IRubyObject fcntl(ThreadContext context, IRubyObject cmd) { // TODO: This version differs from ioctl by checking whether fcntl exists // and raising notimplemented if it doesn't; perhaps no difference for us? return ctl(context.runtime, cmd, null); } @JRubyMethod(name = "fcntl") public IRubyObject fcntl(ThreadContext context, IRubyObject cmd, IRubyObject arg) { // TODO: This version differs from ioctl by checking whether fcntl exists // and raising notimplemented if it doesn't; perhaps no difference for us? return ctl(context.runtime, cmd, arg); } @JRubyMethod(name = "ioctl", required = 1, optional = 1) public IRubyObject ioctl(ThreadContext context, IRubyObject[] args) { IRubyObject cmd = args[0]; IRubyObject arg; if (args.length == 2) { arg = args[1]; } else { arg = context.runtime.getNil(); } return ctl(context.runtime, cmd, arg); } public IRubyObject ctl(Ruby runtime, IRubyObject cmd, IRubyObject arg) { long realCmd = cmd.convertToInteger().getLongValue(); long nArg = 0; if (realCmd == Fcntl.F_GETFL.intValue()) { OpenFile myOpenFile = getOpenFileChecked(); return runtime.newFixnum(OpenFile.ioFmodeOflags(myOpenFile.getMode())); } // FIXME: Arg may also be true, false, and nil and still be valid. Strangely enough, // protocol conversion is not happening in Ruby on this arg? if (arg == null || arg.isNil() || arg == runtime.getFalse()) { nArg = 0; } else if (arg instanceof RubyFixnum) { nArg = RubyFixnum.fix2long(arg); } else if (arg == runtime.getTrue()) { nArg = 1; } else { throw runtime.newNotImplementedError("JRuby does not support string for second fcntl/ioctl argument yet"); } OpenFile fptr = getOpenFileChecked(); // Fixme: Only F_SETFL and F_GETFL is current supported // FIXME: Only NONBLOCK flag is supported // FIXME: F_SETFL and F_SETFD are treated as the same thing here. For the case of dup(fd) we // should actually have F_SETFL only affect one (it is unclear how well we do, but this TODO // is here to at least document that we might need to do more work here. Mostly SETFL is // for mode changes which should persist across fork() boundaries. Since JVM has no fork // this is not a problem for us. if (realCmd == FcntlLibrary.FD_CLOEXEC) { // Do nothing. FD_CLOEXEC has no meaning in JVM since we cannot really exec. // And why the hell does webrick pass this in as a first argument!!!!! } else if (realCmd == Fcntl.F_SETFL.intValue() || realCmd == Fcntl.F_SETFD.intValue()) { if ((nArg & FcntlLibrary.FD_CLOEXEC) == FcntlLibrary.FD_CLOEXEC) { // Do nothing. FD_CLOEXEC has no meaning in JVM since we cannot really exec. } else { boolean block = (nArg & ModeFlags.NONBLOCK) != ModeFlags.NONBLOCK; fptr.setBlocking(runtime, block); } } else if (realCmd == Fcntl.F_GETFL.intValue()) { return fptr.isBlocking() ? RubyFixnum.zero(runtime) : RubyFixnum.newFixnum(runtime, ModeFlags.NONBLOCK); } else { throw runtime.newNotImplementedError("JRuby only supports F_SETFL and F_GETFL with NONBLOCK for fcntl/ioctl"); } return runtime.newFixnum(0); } @JRubyMethod(name = "puts") public IRubyObject puts(ThreadContext context) { return puts0(context, this); } @JRubyMethod(name = "puts") public IRubyObject puts(ThreadContext context, IRubyObject arg0) { return puts1(context, this, arg0); } @JRubyMethod(name = "puts") public IRubyObject puts(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { return puts2(context, this, arg0, arg1); } @JRubyMethod(name = "puts") public IRubyObject puts(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { return puts3(context, this, arg0, arg1, arg2); } @JRubyMethod(name = "puts", rest = true) public IRubyObject puts(ThreadContext context, IRubyObject[] args) { return puts(context, this, args); } public static IRubyObject puts0(ThreadContext context, IRubyObject maybeIO) { return writeSeparator(context, maybeIO); } public static IRubyObject puts1(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0) { Ruby runtime = context.runtime; assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString; RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator(); putsSingle(context, runtime, maybeIO, arg0, separator); return context.nil; } public static IRubyObject puts2(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0, IRubyObject arg1) { Ruby runtime = context.runtime; assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString; RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator(); putsSingle(context, runtime, maybeIO, arg0, separator); putsSingle(context, runtime, maybeIO, arg1, separator); return context.nil; } public static IRubyObject puts3(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { Ruby runtime = context.runtime; assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString; RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator(); putsSingle(context, runtime, maybeIO, arg0, separator); putsSingle(context, runtime, maybeIO, arg1, separator); putsSingle(context, runtime, maybeIO, arg2, separator); return context.nil; } public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject... args) { if (args.length == 0) { return writeSeparator(context, maybeIO); } return putsArray(context, maybeIO, args); } private static IRubyObject writeSeparator(ThreadContext context, IRubyObject maybeIO) { Ruby runtime = context.runtime; assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString; RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator(); write(context, maybeIO, separator); return runtime.getNil(); } private static IRubyObject putsArray(ThreadContext context, IRubyObject maybeIO, IRubyObject[] args) { Ruby runtime = context.runtime; assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString; RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator(); for (int i = 0; i < args.length; i++) { putsSingle(context, runtime, maybeIO, args[i], separator); } return runtime.getNil(); } private static final ByteList RECURSIVE_BYTELIST = ByteList.create("[...]"); private static void putsSingle(ThreadContext context, Ruby runtime, IRubyObject maybeIO, IRubyObject arg, RubyString separator) { ByteList line; RubyString string; if (arg.isNil()) { line = getNilByteList(runtime); string = null; } else if (runtime.isInspecting(arg)) { line = RECURSIVE_BYTELIST; string = null; } else if (arg instanceof RubyArray) { inspectPuts(context, maybeIO, (RubyArray) arg); return; } else { string = arg.asString(); line = string.getByteList(); } if (string != null) { write(context, maybeIO, string); } else { write(context, maybeIO, line); } if (line.length() == 0 || !line.endsWith(separator.getByteList())) { write(context, maybeIO, separator); } } private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeIO, RubyArray array) { try { context.runtime.registerInspecting(array); return putsArray(context, maybeIO, array.toJavaArray()); } finally { context.runtime.unregisterInspecting(array); } } protected IRubyObject write(ThreadContext context, ByteList byteList) { return sites(context).write.call(context, this, this, RubyString.newStringShared(context.runtime, byteList)); } protected static IRubyObject write(ThreadContext context, IRubyObject maybeIO, ByteList byteList) { return sites(context).write.call(context, maybeIO, maybeIO, RubyString.newStringShared(context.runtime, byteList)); } public static IRubyObject write(ThreadContext context, IRubyObject maybeIO, IRubyObject str) { return sites(context).write.call(context, maybeIO, maybeIO, str); } @JRubyMethod @Override public IRubyObject inspect() { Ruby runtime = getRuntime(); if (openFile == null) return super.inspect(); String className = getMetaClass().getRealClass().getName(); String path = openFile.getPath(); String status = ""; if (path == null || path == "") { if (openFile.fd() == null) { path = ""; status = "(closed)"; } else { path = "fd " + openFile.fd().bestFileno(); } } else if (!openFile.isOpen()) { status = " (closed)"; } String inspectStr = "#<" + className + ":" + path + status + ">"; return runtime.newString(inspectStr); } /** Read a line. * */ @JRubyMethod(name = "readline", writes = FrameField.LASTLINE) public IRubyObject readline(ThreadContext context) { IRubyObject line = gets(context); if (line.isNil()) { throw context.runtime.newEOFError(); } return line; } @JRubyMethod(name = "readline", writes = FrameField.LASTLINE) public IRubyObject readline(ThreadContext context, IRubyObject separator) { IRubyObject line = gets(context, separator); if (line.isNil()) { throw context.runtime.newEOFError(); } return line; } /** Read a byte. On EOF returns nil. * */ public IRubyObject getc() { return getbyte(getRuntime().getCurrentContext()); } // rb_io_readchar @JRubyMethod public IRubyObject readchar(ThreadContext context) { IRubyObject c = getc19(context); if (c.isNil()) { throw context.runtime.newEOFError(); } return c; } // rb_io_getbyte @JRubyMethod public IRubyObject getbyte(ThreadContext context) { int c = getByte(context); if (c == -1) return context.nil; return RubyNumeric.int2fix(context.runtime, c & 0xff); } // rb_io_getbyte public int getByte(ThreadContext context) { int c; OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkByteReadable(context); fptr.READ_CHECK(context); // TODO: tty flushing // 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 (fptr.fillbuf(context) < 0) { return -1; } fptr.rbuf.off++; fptr.rbuf.len--; return fptr.rbuf.ptr[fptr.rbuf.off - 1] & 0xFF; } finally { if (locked) fptr.unlock(); } } // rb_io_readbyte @JRubyMethod public IRubyObject readbyte(ThreadContext context) { IRubyObject c = getbyte(context); if (c.isNil()) { throw getRuntime().newEOFError(); } return c; } // rb_io_getc @JRubyMethod(name = "getc") public IRubyObject getc19(ThreadContext context) { Ruby runtime = context.runtime; Encoding enc; OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); enc = fptr.inputEncoding(runtime); fptr.READ_CHECK(context); return fptr.getc(context, enc); } finally { if (locked) fptr.unlock(); } } // rb_io_ungetbyte @JRubyMethod public IRubyObject ungetbyte(ThreadContext context, IRubyObject b) { OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkByteReadable(context); if (b.isNil()) return context.nil; if (b instanceof RubyFixnum) { byte cc = (byte) RubyNumeric.fix2int(b); b = RubyString.newStringNoCopy(context.runtime, new byte[]{cc}); } else { b = b.convertToString(); } fptr.ungetbyte(context, b); } finally { if (locked) fptr.unlock(); } return context.nil; } // MRI: rb_io_ungetc @JRubyMethod public IRubyObject ungetc(ThreadContext context, IRubyObject c) { Ruby runtime = context.runtime; final OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); if (c.isNil()) return c; if (c instanceof RubyFixnum) { c = EncodingUtils.encUintChr(context, (int) ((RubyFixnum) c).getLongValue(), fptr.readEncoding(runtime)); } else if (c instanceof RubyBignum) { c = EncodingUtils.encUintChr(context, (int) ((RubyBignum) c).getLongValue(), fptr.readEncoding(runtime)); } else { c = c.convertToString(); } if (fptr.needsReadConversion()) { fptr.SET_BINARY_MODE(); final int len = ((RubyString) c).size(); // #if SIZEOF_LONG > SIZEOF_INT // if (len > INT_MAX) // rb_raise(rb_eIOError, "ungetc failed"); // #endif fptr.makeReadConversion(context, len); if (fptr.cbuf.capa - fptr.cbuf.len < len) throw runtime.newIOError("ungetc failed"); // shift cbuf back to 0 if (fptr.cbuf.off < len) { System.arraycopy( fptr.cbuf.ptr, fptr.cbuf.off, fptr.cbuf.ptr, fptr.cbuf.capa - fptr.cbuf.len, // this should be 0 fptr.cbuf.len); fptr.cbuf.off = fptr.cbuf.capa - fptr.cbuf.len; // this should be 0 too } fptr.cbuf.off -= len; fptr.cbuf.len += len; ByteList cByteList = ((RubyString) c).getByteList(); System.arraycopy(cByteList.unsafeBytes(), cByteList.begin(), fptr.cbuf.ptr, fptr.cbuf.off, len); } else { fptr.NEED_NEWLINE_DECORATOR_ON_READ_CHECK(); fptr.ungetbyte(context, c); } } finally { if (locked) fptr.unlock(); } return context.nil; } @JRubyMethod(name = "read_nonblock", required = 1, optional = 2) public IRubyObject read_nonblock(ThreadContext context, IRubyObject[] args) { return doReadNonblock(context, args, true); } // MRI: io_read_nonblock public IRubyObject doReadNonblock(ThreadContext context, IRubyObject[] args, boolean useException) { final Ruby runtime = context.runtime; boolean exception = ArgsUtil.extractKeywordArg(context, "exception", args) != runtime.getFalse(); IRubyObject ret = getPartial(context, args, true, !exception); return ret.isNil() ? nonblockEOF(runtime, !exception) : ret; } // MRI: io_nonblock_eof(VALUE opts) static IRubyObject nonblockEOF(final Ruby runtime, final boolean noException) { if ( noException ) return runtime.getNil(); throw runtime.newEOFError(); } @JRubyMethod(name = "readpartial", required = 1, optional = 1) public IRubyObject readpartial(ThreadContext context, IRubyObject[] args) { // ruby bug 11885 if (args.length == 2) { args[1] = args[1].convertToString(); } IRubyObject value = getPartial(context, args, false, false); if (value.isNil()) { throw context.runtime.newEOFError(); } return value; } // MRI: io_getpartial IRubyObject getPartial(ThreadContext context, IRubyObject[] args, boolean nonblock, boolean noException) { Ruby runtime = context.runtime; OpenFile fptr; IRubyObject length, str; switch (args.length) { case 3: length = args[0]; str = args[1]; args[2].convertToHash(); break; case 2: length = args[0]; str = TypeConverter.checkHashType(runtime, args[1]); str = str.isNil() ? args[1] : context.nil; break; case 1: length = args[0]; str = context.nil; break; default: length = context.nil; str = context.nil; } final int len; if ( ( len = RubyNumeric.num2int(length) ) < 0 ) { throw runtime.newArgumentError("negative length " + len + " given"); } str = EncodingUtils.setStrBuf(runtime, str, len); str.setTaint(true); fptr = getOpenFileChecked(); final boolean locked = fptr.lock(); int n; try { fptr.checkByteReadable(context); if ( len == 0 ) return str; if ( ! nonblock ) fptr.READ_CHECK(context); ByteList strByteList = ((RubyString) str).getByteList(); n = fptr.readBufferedData(strByteList.unsafeBytes(), strByteList.begin(), len); if (n <= 0) { again: while (true) { if (nonblock) { fptr.setNonblock(runtime); } str = EncodingUtils.setStrBuf(runtime, str, len); strByteList = ((RubyString) str).getByteList(); // arg.fd = fptr->fd; // arg.str_ptr = RSTRING_PTR(str); // arg.len = len; // rb_str_locktmp_ensure(str, read_internal_call, (VALUE)&arg); // n = arg.len; n = OpenFile.readInternal(context, fptr, fptr.fd(), strByteList.unsafeBytes(), strByteList.begin(), len); if (n < 0) { Errno e = fptr.errno(); if (!nonblock && fptr.waitReadable(context)) continue again; if (nonblock && (e == Errno.EWOULDBLOCK || e == Errno.EAGAIN)) { if (noException) return runtime.newSymbol("wait_readable"); throw runtime.newErrnoEAGAINReadableError("read would block"); } throw runtime.newEOFError(fptr.getPath()); } break; } } } finally { if ( locked ) fptr.unlock(); } ((RubyString) str).setReadLength(n); return n == 0 ? context.nil : str; } // MRI: rb_io_sysread @JRubyMethod(name = "sysread", required = 1, optional = 1) public IRubyObject sysread(ThreadContext context, IRubyObject[] args) { final Ruby runtime = context.runtime; final int length = RubyNumeric.num2int(args.length >= 1 ? args[0] : context.nil); RubyString str = EncodingUtils.setStrBuf(runtime, args.length >= 2 ? args[1] : context.nil, length); if (length == 0) return str; final OpenFile fptr = getOpenFileChecked(); final int n; boolean locked = fptr.lock(); try { fptr.checkByteReadable(context); if (fptr.READ_DATA_BUFFERED()) { throw runtime.newIOError("sysread for buffered IO"); } /* * MRI COMMENT: * FIXME: removing rb_thread_wait_fd() here changes sysread semantics * on non-blocking IOs. However, it's still currently possible * for sysread to raise Errno::EAGAIN if another thread read()s * the IO after we return from rb_thread_wait_fd() but before * we call read() */ context.getThread().select(fptr.channel(), fptr, SelectionKey.OP_READ); fptr.checkClosed(); ByteList strByteList = str.getByteList(); n = OpenFile.readInternal(context, fptr, fptr.fd(), strByteList.unsafeBytes(), strByteList.begin(), length); if (n == -1) { throw runtime.newErrnoFromErrno(fptr.errno(), fptr.getPath()); } } finally { if (locked) fptr.unlock(); } if (n == 0 && length > 0) throw runtime.newEOFError(); str.setReadLength(n); str.setTaint(true); return str; } // io_read public IRubyObject read(IRubyObject[] args) { ThreadContext context = getRuntime().getCurrentContext(); switch (args.length) { case 0: return read(context); case 1: return read(context, args[0]); case 2: return read(context, args[0], args[1]); default: throw getRuntime().newArgumentError(args.length, 2); } } // io_read @JRubyMethod(name = "read") public IRubyObject read(ThreadContext context) { return read(context, context.nil, context.nil); } // io_read @JRubyMethod(name = "read") public IRubyObject read(ThreadContext context, IRubyObject arg0) { return read(context, arg0, context.nil); } // io_read @JRubyMethod(name = "read") public IRubyObject read(ThreadContext context, IRubyObject length, IRubyObject str) { Ruby runtime = context.runtime; OpenFile fptr; int n, len; if (length.isNil()) { fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); return fptr.readAll(context, fptr.remainSize(), str); } finally { if (locked) fptr.unlock(); } } len = RubyNumeric.num2int(length); if (len < 0) { throw runtime.newArgumentError("negative length " + len + " given"); } str = EncodingUtils.setStrBuf(runtime, str, len); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkByteReadable(context); if (len == 0) { ((RubyString)str).setReadLength(0); return str; } fptr.READ_CHECK(context); // #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) // previous_mode = set_binary_mode_with_seek_cur(fptr); // #endif n = fptr.fread(context, str, 0, len); } finally { if (locked) fptr.unlock(); } ((RubyString)str).setReadLength(n); // #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) // if (previous_mode == O_TEXT) { // setmode(fptr->fd, O_TEXT); // } // #endif if (n == 0) return context.nil; str.setTaint(true); return str; } /** Read a byte. On EOF throw EOFError. * */ public IRubyObject readchar() { return readchar(getRuntime().getCurrentContext()); } @JRubyMethod public IRubyObject stat(ThreadContext context) { Ruby runtime = context.runtime; OpenFile fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { int realFileno; fptr.checkClosed(); if (runtime.getPosix().isNative() && (realFileno = fptr.fd().realFileno) != -1) { return RubyFileStat.newFileStat(runtime, realFileno); } else { // no real fd, stat the path return context.runtime.newFileStat(fptr.getPath(), false); } } finally { if (locked) fptr.unlock(); } } /** *

Invoke a block for each byte.

* * MRI: rb_io_each_byte */ public IRubyObject each_byteInternal(ThreadContext context, Block block) { Ruby runtime = context.runtime; OpenFile fptr; if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_byte"); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { do { while (fptr.rbuf.len > 0) { byte[] pBytes = fptr.rbuf.ptr; int p = fptr.rbuf.off++; fptr.rbuf.len--; block.yield(context, runtime.newFixnum(pBytes[p] & 0xFF)); fptr.errno(null); } fptr.checkByteReadable(context); fptr.READ_CHECK(context); } while (fptr.fillbuf(context) >= 0); } finally { if (locked) fptr.unlock(); } return this; } @JRubyMethod public IRubyObject each_byte(ThreadContext context, Block block) { return block.isGiven() ? each_byteInternal(context, block) : enumeratorize(context.runtime, this, "each_byte"); } // rb_io_bytes @JRubyMethod(name = "bytes") public IRubyObject bytes(ThreadContext context, Block block) { context.runtime.getWarnings().warn("IO#bytes is deprecated; use #each_byte instead"); return each_byte(context, block); } // rb_io_each_char public IRubyObject each_charInternal(ThreadContext context, Block block) { Ruby runtime = context.runtime; OpenFile fptr; Encoding enc; IRubyObject c; if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_char"); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); enc = fptr.inputEncoding(runtime); fptr.READ_CHECK(context); while (!(c = fptr.getc(context, enc)).isNil()) { block.yield(context, c); } } finally { if (locked) fptr.unlock(); } return this; } @JRubyMethod(name = "each_char") public IRubyObject each_char(ThreadContext context, Block block) { return each_charInternal(context, block); } @JRubyMethod(name = "chars") public IRubyObject chars(ThreadContext context, Block block) { context.runtime.getWarnings().warn("IO#chars is deprecated; use #each_char instead"); return each_charInternal(context, block); } @JRubyMethod public IRubyObject codepoints(ThreadContext context, Block block) { context.runtime.getWarnings().warn("IO#codepoints is deprecated; use #each_codepoint instead"); return eachCodePointCommon(context, block, "each_codepoint"); } @JRubyMethod public IRubyObject each_codepoint(ThreadContext context, Block block) { return eachCodePointCommon(context, block, "each_codepoint"); } // rb_io_each_codepoint private IRubyObject eachCodePointCommon(ThreadContext context, Block block, String methodName) { Ruby runtime = context.runtime; OpenFile fptr; Encoding enc; int c; int r, n; if (!block.isGiven()) return enumeratorize(context.runtime, this, methodName); fptr = getOpenFileChecked(); boolean locked = fptr.lock(); try { fptr.checkCharReadable(context); fptr.READ_CHECK(context); if (fptr.needsReadConversion()) { fptr.SET_BINARY_MODE(); r = 1; /* no invalid char yet */ for (;;) { fptr.makeReadConversion(context); for (;;) { if (fptr.cbuf.len != 0) { if (fptr.encs.enc != null) r = StringSupport.preciseLength(fptr.encs.enc, fptr.cbuf.ptr, fptr.cbuf.off, fptr.cbuf.off + fptr.cbuf.len); else r = StringSupport.CONSTRUCT_MBCLEN_CHARFOUND(1); if (!StringSupport.MBCLEN_NEEDMORE_P(r)) break; if (fptr.cbuf.len == fptr.cbuf.capa) { throw runtime.newIOError("too long character"); } } if (fptr.moreChar(context) == OpenFile.MORE_CHAR_FINISHED) { fptr.clearReadConversion(); if (!StringSupport.MBCLEN_CHARFOUND_P(r)) { enc = fptr.encs.enc; throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); } return this; } } if (StringSupport.MBCLEN_INVALID_P(r)) { enc = fptr.encs.enc; throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); } n = StringSupport.MBCLEN_CHARFOUND_LEN(r); if (fptr.encs.enc != null) { c = StringSupport.codePoint(runtime, fptr.encs.enc, fptr.cbuf.ptr, fptr.cbuf.off, fptr.cbuf.off + fptr.cbuf.len); } else { c = fptr.cbuf.ptr[fptr.cbuf.off] & 0xFF; } fptr.cbuf.off += n; fptr.cbuf.len -= n; block.yield(context, runtime.newFixnum(c & 0xFFFFFFFF)); } } fptr.NEED_NEWLINE_DECORATOR_ON_READ_CHECK(); enc = fptr.inputEncoding(runtime); while (fptr.fillbuf(context) >= 0) { r = StringSupport.preciseLength(enc, fptr.rbuf.ptr, fptr.rbuf.off, fptr.rbuf.off + fptr.rbuf.len); if (StringSupport.MBCLEN_CHARFOUND_P(r) && (n = StringSupport.MBCLEN_CHARFOUND_LEN(r)) <= fptr.rbuf.len) { c = StringSupport.codePoint(runtime, fptr.encs.enc, fptr.rbuf.ptr, fptr.rbuf.off, fptr.rbuf.off + fptr.rbuf.len); fptr.rbuf.off += n; fptr.rbuf.len -= n; block.yield(context, runtime.newFixnum(c & 0xFFFFFFFF)); } else if (StringSupport.MBCLEN_INVALID_P(r)) { throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); } else if (StringSupport.MBCLEN_NEEDMORE_P(r)) { byte[] cbuf = new byte[8]; int p = 0; int more = StringSupport.MBCLEN_NEEDMORE_LEN(r); if (more > cbuf.length) throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); more += n = fptr.rbuf.len; if (more > cbuf.length) throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); while ((n = (int)fptr.readBufferedData(cbuf, p, more)) > 0) { p += n; if ((more -= n) <= 0) break; if (fptr.fillbuf(context) < 0) throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); if ((n = fptr.rbuf.len) > more) n = more; } r = enc.length(cbuf, 0, p); if (!StringSupport.MBCLEN_CHARFOUND_P(r)) throw runtime.newArgumentError("invalid byte sequence in " + enc.toString()); c = enc.mbcToCode(cbuf, 0, p); block.yield(context, runtime.newFixnum(c)); } else { continue; } } } finally { if (locked) fptr.unlock(); } return this; } /** *

Invoke a block for each line.

* * MRI: rb_io_each_line */ private IRubyObject each_lineInternal(ThreadContext context, IRubyObject[] args, Block block, String name) { if (!block.isGiven()) return enumeratorize(context.runtime, this, name, args); IRubyObject separator = prepareGetsSeparator(context, args); ByteListCache cache = new ByteListCache(); for (IRubyObject line = getline(context, separator); !line.isNil(); line = getline(context, separator, -1, cache)) { block.yield(context, line); } return this; } @JRubyMethod(optional = 1) public IRubyObject each(final ThreadContext context, IRubyObject[]args, final Block block) { return each_lineInternal(context, args, block, "each"); } @JRubyMethod(optional = 1) public IRubyObject each_line(final ThreadContext context, IRubyObject[]args, final Block block) { return each_lineInternal(context, args, block, "each_line"); } @JRubyMethod(name = "lines") public IRubyObject lines(final ThreadContext context, Block block) { context.runtime.getWarnings().warn("IO#lines is deprecated; use #each_line instead"); return each_lineInternal(context, NULL_ARRAY, block, "each_line"); } @JRubyMethod(name = "readlines", optional = 2) public RubyArray readlines(ThreadContext context, IRubyObject[] args) { return readlinesCommon(context, args); } private RubyArray readlinesCommon(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; long limit = prepareGetsLimit(context, args); IRubyObject separator = prepareGetsSeparator(context, args); RubyArray result = runtime.newArray(); IRubyObject line; while (! (line = getline(context, separator, limit, null)).isNil()) { result.append(line); } return result; } @JRubyMethod(name = "to_io") public RubyIO to_io() { return this; } @Override public String toString() { return inspect().toString(); } /* class methods for IO */ // rb_io_s_foreach private static IRubyObject foreachInternal19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { Ruby runtime = context.runtime; IRubyObject opt = ArgsUtil.getOptionsArg(context.runtime, args); IRubyObject io = openKeyArgs(context, recv, args, opt); if (io.isNil()) return io; // io_s_foreach IRubyObject[] methodArguments = processReadlinesMethodArguments(context, args); try { IRubyObject str; while (!(str = ((RubyIO)io).gets(context, methodArguments)).isNil()) { block.yield(context, str); } } finally { ((RubyIO)io).close(); runtime.getGlobalVariables().clear("$_"); } return context.nil; } @JRubyMethod(name = "foreach", required = 1, optional = 3, meta = true) public static IRubyObject foreach(final ThreadContext context, IRubyObject recv, IRubyObject[] args, final Block block) { if (!block.isGiven()) return enumeratorize(context.runtime, recv, "foreach", args); return foreachInternal19(context, recv, args, block); } public static RubyIO convertToIO(ThreadContext context, IRubyObject obj) { return TypeConverter.ioGetIO(context.runtime, obj); } @JRubyMethod(name = "select", required = 1, optional = 3, meta = true) public static IRubyObject select(ThreadContext context, IRubyObject recv, IRubyObject[] argv) { IRubyObject read, write, except, _timeout; read = write = except = _timeout = context.nil; switch (argv.length) { case 4: _timeout = argv[3]; case 3: except = argv[2]; case 2: write = argv[1]; case 1: read = argv[0]; } final Long timeout; if (_timeout.isNil()) { timeout = null; } else { try { // MRI calls to_f even if not respond_to? (or respond_to_missing?) :to_f _timeout = sites(context).to_f.call(context, _timeout, _timeout); } catch (RaiseException e) { TypeConverter.handleUncoercibleObject(true, _timeout, context.runtime.getFloat()); throw e; // won't happen } final double t = _timeout.convertToFloat().getDoubleValue(); if ( t < 0 ) throw context.runtime.newArgumentError("negative timeout"); timeout = (long) (t * 1000); // ms } SelectExecutor args = new SelectExecutor(read, write, except, timeout); return args.go(context); } // MRI: rb_io_advise @JRubyMethod(required = 1, optional = 2) public IRubyObject advise(ThreadContext context, IRubyObject[] argv) { IRubyObject advice, offset, len; advice = offset = len = context.nil; OpenFile fptr; switch (argv.length) { case 3: len = argv[2]; case 2: offset = argv[1]; case 1: advice = argv[0]; } adviceArgCheck(context, advice); RubyIO io = GetWriteIO(); fptr = io.getOpenFileChecked(); boolean locked = fptr.lock(); try { int off = offset.isNil() ? 0 : offset.convertToInteger().getIntValue(); int l = len.isNil() ? 0 : len.convertToInteger().getIntValue(); // TODO: implement advise // #ifdef HAVE_POSIX_FADVISE // return do_io_advise(fptr, advice, off, l); // #else // ((void)off, (void)l); /* Ignore all hint */ return context.nil; // #endif } finally { if (locked) fptr.unlock(); } } // MRI: advice_arg_check static void adviceArgCheck(ThreadContext context, IRubyObject advice) { if (!(advice instanceof RubySymbol)) throw context.runtime.newTypeError("advise must be a symbol"); String adviceStr = advice.asJavaString(); switch (adviceStr) { default: throw context.runtime.newNotImplementedError(rbInspect(context, advice).toString()); case "normal": case "sequential": case "random": case "willneed": case "dontneed": case "noreuse": // ok } } public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args) { return read19(context, recv, args, Block.NULL_BLOCK); } public static void failIfDirectory(Ruby runtime, RubyString pathStr) { if (RubyFileTest.directory_p(runtime, pathStr).isTrue()) { if (Platform.IS_WINDOWS) { throw runtime.newErrnoEACCESError(pathStr.asJavaString()); } else { throw runtime.newErrnoEISDirError(pathStr.asJavaString()); } } } // open_key_args private static IRubyObject openKeyArgs(ThreadContext context, IRubyObject recv, IRubyObject[] argv, IRubyObject opt) { final Ruby runtime = context.runtime; IRubyObject path, v; path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, argv[0])); failIfDirectory(runtime, (RubyString) path); // only in JRuby // MRI increments args past 0 now, so remaining uses of args only see non-path args if (opt.isNil()) { return ioOpen(context, path, runtime.newFixnum(ModeFlags.RDONLY), runtime.newFixnum(0666), opt); } v = ((RubyHash) opt).op_aref(context, runtime.newSymbol("open_args")); if ( ! v.isNil() ) { v = v.convertToArray(); RubyArray args = runtime.newArray( ((RubyArray) v).size() + 1 ); args.push(path); args.concat(v); return RubyKernel.open19(context, recv, args.toJavaArray(), Block.NULL_BLOCK); } return ioOpen(context, path, context.nil, context.nil, opt); } // rb_io_open public static IRubyObject ioOpen(ThreadContext context, IRubyObject filename, IRubyObject vmode, IRubyObject vperm, IRubyObject opt) { final Ruby runtime = context.runtime; int[] oflags_p = {0}, fmode_p = {0}; int perm; IRubyObject cmd; if ((filename instanceof RubyString) && ((RubyString) filename).isEmpty()) { throw runtime.newErrnoENOENTError(); } Object pm = EncodingUtils.vmodeVperm(vmode, vperm); IOEncodable convconfig = new IOEncodable.ConvConfig(); EncodingUtils.extractModeEncoding(context, convconfig, pm, opt, oflags_p, fmode_p); perm = (vperm(pm) == null || vperm(pm).isNil()) ? 0666 : RubyNumeric.num2int(vperm(pm)); if ( ! ( cmd = PopenExecutor.checkPipeCommand(context, filename) ).isNil() ) { return PopenExecutor.pipeOpen(context, cmd, OpenFile.ioOflagsModestr(runtime, oflags_p[0]), fmode_p[0], convconfig); } return ((RubyFile) runtime.getFile().allocate()).fileOpenGeneric(context, filename, oflags_p[0], fmode_p[0], convconfig, perm); } /** * binread is just like read, except it doesn't take options and it forces * mode to be "rb:ASCII-8BIT" * * @param context the current ThreadContext * @param recv the target of the call (IO or a subclass) * @param args arguments; path [, length [, offset]] * @return the binary contents of the given file, at specified length and offset */ @JRubyMethod(meta = true, required = 1, optional = 2) public static IRubyObject binread(ThreadContext context, IRubyObject recv, IRubyObject[] args) { Ruby runtime = context.runtime; IRubyObject nil = runtime.getNil(); IRubyObject path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, args[0])); IRubyObject length = nil; IRubyObject offset = nil; if (args.length > 2) { offset = args[2]; length = args[1]; } else if (args.length > 1) { length = args[1]; } RubyClass File = runtime.getFile(); RubyIO file = (RubyIO) sites(context).new_.call(context, File, File, path, runtime.newString("rb:ASCII-8BIT")); try { if (!offset.isNil()) { file.seek(context, offset); } return file.read(context, length); } finally { file.close(); } } // Enebo: annotation processing forced me to do pangea method here... @JRubyMethod(name = "read", meta = true, required = 1, optional = 3) public static IRubyObject read19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) { Ruby runtime = context.runtime; IRubyObject nil = runtime.getNil(); IRubyObject path = args[0]; IRubyObject length = nil; IRubyObject offset = nil; IRubyObject options = nil; { // rb_scan_args logic, basically if (args.length > 3) { if (!(args[3] instanceof RubyHash)) throw runtime.newTypeError("Must be a hash"); options = (RubyHash) args[3]; offset = args[2]; length = args[1]; } else if (args.length > 2) { if (args[2] instanceof RubyHash) { options = (RubyHash) args[2]; } else { offset = args[2]; } length = args[1]; } else if (args.length > 1) { if (args[1] instanceof RubyHash) { options = (RubyHash) args[1]; } else { length = args[1]; } } if (options == null) { options = RubyHash.newHash(runtime); } } RubyIO file = (RubyIO)openKeyArgs(context, recv, new IRubyObject[]{path, length, offset}, options); try { if (!offset.isNil()) { // protect logic around here in MRI? file.seek(context, offset); } return file.read(context, length); } finally { file.close(); } } // rb_io_s_binwrite @JRubyMethod(meta = true, required = 2, optional = 2) public static IRubyObject binwrite(ThreadContext context, IRubyObject recv, IRubyObject[] args) { return ioStaticWrite(context, recv, args, true); } // MRI: rb_io_s_write @JRubyMethod(name = "write", meta = true, required = 2, optional = 2) public static IRubyObject write(ThreadContext context, IRubyObject recv, IRubyObject[] argv) { return (ioStaticWrite(context, recv, argv, false)); } // MRI: io_s_write public static IRubyObject ioStaticWrite(ThreadContext context, IRubyObject recv, IRubyObject[] argv, boolean binary) { final Ruby runtime = context.runtime; IRubyObject string, offset, opt; string = offset = opt = context.nil; switch (argv.length) { case 4: opt = argv[3].convertToHash(); offset = argv[2]; string = argv[1]; break; case 3: opt = TypeConverter.checkHashType(runtime, argv[2]); if (opt.isNil()) offset = argv[2]; string = argv[1]; break; case 2: string = argv[1]; break; default: Arity.raiseArgumentError(runtime, argv.length, 2, 4); } final RubyHash optHash; optHash = opt.isNil() ? RubyHash.newHash(runtime) : ((RubyHash) opt).dupFast(context); final RubySymbol modeSym = runtime.newSymbol("mode"); if ( optHash.op_aref(context, modeSym).isNil() ) { int mode = OpenFlags.O_WRONLY.intValue() | OpenFlags.O_CREAT.intValue(); if ( OpenFlags.O_BINARY.defined() ) { if ( binary ) mode |= OpenFlags.O_BINARY.intValue(); } if ( offset.isNil() ) mode |= OpenFlags.O_TRUNC.intValue(); optHash.op_aset(context, modeSym, runtime.newFixnum(mode)); } IRubyObject _io = openKeyArgs(context, recv, argv, optHash); if ( _io.isNil() ) return context.nil; final RubyIO io = (RubyIO) _io; if ( ! OpenFlags.O_BINARY.defined() ) { if ( binary ) io.binmode(); } if ( ! offset.isNil() ) { seekBeforeAccess(context, io, offset, PosixShim.SEEK_SET); } try { return io.write(context, string, false); } finally { ioClose(runtime, io); } } static IRubyObject seekBeforeAccess(ThreadContext context, RubyIO io, IRubyObject offset, int mode) { io.setBinmode(); return io.doSeek(context, offset, mode); } public static IRubyObject readlines(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) { return readlines19(context, recv, args, unusedBlock); } // rb_io_s_readlines @JRubyMethod(name = "readlines", required = 1, optional = 3, meta = true) public static IRubyObject readlines19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) { IRubyObject opt = ArgsUtil.getOptionsArg(context.runtime, args); IRubyObject io = openKeyArgs(context, recv, args, opt); if (io.isNil()) return io; IRubyObject[] methodArguments = processReadlinesMethodArguments(context, args); return readlinesCommon19(context, (RubyIO)io, methodArguments); } private static IRubyObject[] processReadlinesMethodArguments(ThreadContext context, IRubyObject[] args) { int count = args.length; IRubyObject[] methodArguments = IRubyObject.NULL_ARRAY; RespondToCallSite respond_to_to_int = sites(context).respond_to_to_int; if(count >= 3 && (args[2] instanceof RubyFixnum || respond_to_to_int.respondsTo(context, args[2], args[2]))) { methodArguments = new IRubyObject[]{args[1], args[2]}; } else if (count >= 2 && (args[1] instanceof RubyFixnum || respond_to_to_int.respondsTo(context, args[1], args[1]))) { methodArguments = new IRubyObject[]{args[1]}; } else if (count >= 2 && !(args[1] instanceof RubyHash)) { methodArguments = new IRubyObject[]{args[1]}; } return methodArguments; } private static RubyArray readlinesCommon19(ThreadContext context, RubyIO file, IRubyObject[] newArguments) { try { return file.readlines(context, newArguments); } finally { file.close(); } } private void setupPopen(ModeFlags modes, POpenProcess process) throws RaiseException { Ruby runtime = getRuntime(); openFile.setMode(modes.getOpenFileFlags() | OpenFile.SYNC); openFile.setProcess(process); if (openFile.isReadable()) { Channel inChannel; if (process.getInput() != null) { // NIO-based inChannel = process.getInput(); } else { // Stream-based inChannel = Channels.newChannel(process.getInputStream()); } ChannelFD main = new ChannelFD(inChannel, runtime.getPosix(), runtime.getFilenoUtil()); openFile.setFD(main); } if (openFile.isWritable() && process.hasOutput()) { Channel outChannel; if (process.getOutput() != null) { // NIO-based outChannel = process.getOutput(); } else { outChannel = Channels.newChannel(process.getOutputStream()); } ChannelFD pipe = new ChannelFD(outChannel, runtime.getPosix(), runtime.getFilenoUtil()); // if also readable, attach as tied IO; otherwise, primary IO if (openFile.isReadable()) { RubyIO writeIO = new RubyIO(runtime, runtime.getIO()); writeIO.initializeCommon(runtime.getCurrentContext(), pipe, runtime.newFixnum(OpenFlags.O_WRONLY), runtime.getNil()); openFile.tiedIOForWriting = writeIO; setInstanceVariable("@tied_io_for_writing", writeIO); } else { openFile.setFD(pipe); } } } private static class Ruby19POpen { final RubyString cmd; final IRubyObject[] cmdPlusArgs; final RubyHash env; Ruby19POpen(Ruby runtime, IRubyObject[] args) { IRubyObject[] _cmdPlusArgs; IRubyObject _env; IRubyObject _cmd; int firstArg = 0; int argc = args.length; if (argc > 0 && !(_env = TypeConverter.checkHashType(runtime, args[0])).isNil()) { if (argc < 2) throw runtime.newArgumentError(1, 2); firstArg++; argc--; } else { _env = null; } IRubyObject arg0 = args[firstArg].checkArrayType(); if (arg0.isNil()) { if ((arg0 = TypeConverter.checkStringType(runtime, args[firstArg])).isNil()) { throw runtime.newTypeError(args[firstArg], runtime.getString()); } _cmdPlusArgs = null; _cmd = arg0; } else { RubyArray arg0Ary = (RubyArray) arg0; if (arg0Ary.isEmpty()) throw runtime.newArgumentError("wrong number of arguments"); if (arg0Ary.eltOk(0) instanceof RubyHash) { // leading hash, use for env _env = arg0Ary.delete_at(0); } if (arg0Ary.isEmpty()) throw runtime.newArgumentError("wrong number of arguments"); if (arg0Ary.size() > 1 && arg0Ary.eltOk(arg0Ary.size() - 1) instanceof RubyHash) { // trailing hash, use for opts _env = arg0Ary.eltOk(arg0Ary.size() - 1); } _cmdPlusArgs = arg0Ary.toJavaArray(); _cmd = _cmdPlusArgs[0]; } if (Platform.IS_WINDOWS) { String commandString = _cmd.convertToString().toString().replace('/', '\\'); _cmd = runtime.newString(commandString); if (_cmdPlusArgs != null) _cmdPlusArgs[0] = _cmd; } else { _cmd = _cmd.convertToString(); if (_cmdPlusArgs != null) _cmdPlusArgs[0] = _cmd; } this.cmd = (RubyString)_cmd; this.cmdPlusArgs = _cmdPlusArgs; this.env = (RubyHash)_env; } } @JRubyMethod(name = "popen", required = 1, optional = 2, meta = true) public static IRubyObject popen(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { Ruby runtime = context.runtime; if (runtime.getPosix().isNative() && !Platform.IS_WINDOWS) { // new native popen logic return PopenExecutor.popen(context, args, (RubyClass)recv, block); } // old JDK popen logic IRubyObject pmode = null; RubyHash options = null; IRubyObject tmp; int firstArg = 0; int argc = args.length; if (argc > 0 && !TypeConverter.checkHashType(runtime, args[0]).isNil()) { firstArg++; argc--; } if (argc > 0 && !(tmp = TypeConverter.checkHashType(runtime, args[args.length - 1])).isNil()) { options = (RubyHash)tmp; argc--; } if (argc > 1) { pmode = args[firstArg + 1]; } RubyIO io = new RubyIO(runtime, (RubyClass) recv); io.MakeOpenFile(); Object pm = vmodeVperm(pmode, runtime.newFixnum(0)); int[] oflags_p = {0}, fmode_p = {0}; EncodingUtils.extractModeEncoding(context, io, pm, options, oflags_p, fmode_p); ModeFlags modes = ModeFlags.createModeFlags(oflags_p[0]); // FIXME: Reprocessing logic twice for now... // for 1.9 mode, strip off the trailing options hash, if there if (args.length > 1 && args[args.length - 1] instanceof RubyHash) { options = (RubyHash)args[args.length - 1]; IRubyObject[] newArgs = new IRubyObject[args.length - 1]; System.arraycopy(args, 0, newArgs, 0, args.length - 1); args = newArgs; } Ruby19POpen r19Popen = new Ruby19POpen(runtime, args); if ("-".equals(r19Popen.cmd.toString())) { throw runtime.newNotImplementedError("popen(\"-\") is unimplemented"); } try { ShellLauncher.POpenProcess process; if (r19Popen.cmdPlusArgs == null) { process = ShellLauncher.popen(runtime, r19Popen.cmd, modes); } else { process = ShellLauncher.popen(runtime, r19Popen.cmdPlusArgs, r19Popen.env, modes); } checkPopenOptions(options); io.setupPopen(modes, process); if (block.isGiven()) { ensureYieldClose(context, io, block); // RubyStatus uses real native status now, so we unshift Java's shifted exit status context.setLastExitStatus(RubyProcess.RubyStatus.newProcessStatus(runtime, process.waitFor() << 8, ShellLauncher.getPidFromProcess(process))); } return io; } catch (IOException e) { throw runtime.newIOErrorFromException(e); } catch (InterruptedException e) { throw runtime.newThreadError("unexpected interrupt"); } } public static IRubyObject pipe(ThreadContext context, IRubyObject recv) { return pipe19(context, recv); } public static IRubyObject pipe19(ThreadContext context, IRubyObject recv) { return pipe19(context, recv, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); } public static IRubyObject pipe19(ThreadContext context, IRubyObject recv, IRubyObject modes) { return pipe19(context, recv, new IRubyObject[] {modes}, Block.NULL_BLOCK); } @JRubyMethod(name = "pipe", optional = 3, meta = true) public static IRubyObject pipe19(ThreadContext context, IRubyObject klass, IRubyObject[] argv, Block block) { Ruby runtime = context.runtime; int state; RubyIO r, w; // IRubyObject args[] = new IRubyObject[3] IRubyObject v1, v2; IRubyObject opt; v1 = v2 = opt = context.nil; OpenFile fptr, fptr2; int[] fmode_p = {0}; IRubyObject ret; int argc = argv.length; switch (argc) { case 3: opt = argv[2].convertToHash(); argc--; v2 = argv[1]; v1 = argv[0]; break; case 2: opt = TypeConverter.checkHashType(runtime, argv[1]); if (!opt.isNil()) { argc--; } else { v2 = argv[1]; } v1 = argv[0]; break; case 1: opt = TypeConverter.checkHashType(runtime, argv[0]); if (!opt.isNil()) { argc--; } else { v1 = argv[0]; } } PosixShim posix = new PosixShim(runtime); Channel[] fds = posix.pipe(); if (fds == null) throw runtime.newErrnoFromErrno(posix.errno, "opening pipe"); // 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); // } r = new RubyIO(runtime, (RubyClass)klass); r.initializeCommon(context, new ChannelFD(fds[0], runtime.getPosix(), runtime.getFilenoUtil()), runtime.newFixnum(OpenFlags.O_RDONLY), context.nil); fptr = r.getOpenFileChecked(); r.setEncoding(context, 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); // } w = new RubyIO(runtime, (RubyClass)klass); w.initializeCommon(context, new ChannelFD(fds[1], runtime.getPosix(), runtime.getFilenoUtil()), runtime.newFixnum(OpenFlags.O_WRONLY), context.nil); fptr2 = w.getOpenFileChecked(); fptr2.setSync(true); EncodingUtils.extractBinmode(runtime, opt, fmode_p); if (EncodingUtils.DEFAULT_TEXTMODE != 0) { if ((fptr.getMode() & OpenFile.TEXTMODE) != 0 && (fmode_p[0] & OpenFile.BINMODE) != 0) { fptr.setMode(fptr.getMode() & ~OpenFile.TEXTMODE); // TODO: setmode O_BINARY means what via NIO? // setmode(fptr->fd, O_BINARY); } if (Platform.IS_WINDOWS) { // #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) if ((fptr.encs.ecflags & EncodingUtils.ECONV_DEFAULT_NEWLINE_DECORATOR) != 0) { fptr.encs.ecflags |= EConvFlags.UNIVERSAL_NEWLINE_DECORATOR; } } } fptr.setMode(fptr.getMode() | fmode_p[0]); if (EncodingUtils.DEFAULT_TEXTMODE != 0) { if ((fptr2.getMode() & OpenFile.TEXTMODE) != 0 && (fmode_p[0] & OpenFile.BINMODE) != 0) { fptr2.setMode(fptr2.getMode() & ~OpenFile.TEXTMODE); // TODO: setmode O_BINARY means what via NIO? // setmode(fptr2->fd, O_BINARY); } } fptr2.setMode(fptr2.getMode() | fmode_p[0]); ret = runtime.newArray(r, w); if (block.isGiven()) { return ensureYieldClosePipes(context, ret, r, w, block); } return ret; } // MRI: rb_ensure(... pipe_pair_close ...) public static IRubyObject ensureYieldClosePipes(ThreadContext context, IRubyObject obj, RubyIO r, RubyIO w, Block block) { try { return block.yield(context, obj); } finally { pipePairClose(context.runtime, r, w); } } // MRI: pipe_pair_close private static void pipePairClose(Ruby runtime, RubyIO r, RubyIO w) { try { ioClose(runtime, r); } finally { ioClose(runtime, w); } } @JRubyMethod(name = "copy_stream", required = 2, optional = 2, meta = true) public static IRubyObject copy_stream(ThreadContext context, IRubyObject recv, IRubyObject[] args) { Ruby runtime = context.runtime; IRubyObject arg1 = args[0]; IRubyObject arg2 = args[1]; RubyInteger length = null; RubyInteger offset = null; RubyIO io1 = null; RubyIO io2 = null; RubyString read = null; if (args.length >= 3) { length = args[2].convertToInteger(); if (args.length == 4) { offset = args[3].convertToInteger(); } } IOSites sites = sites(context); boolean close1 = false; boolean close2 = false; try { if (arg1 instanceof RubyString) { io1 = (RubyIO) RubyFile.open(context, runtime.getFile(), new IRubyObject[]{arg1}, Block.NULL_BLOCK); close1 = true; } else if (arg1 instanceof RubyIO) { io1 = (RubyIO) arg1; } else if (sites.to_path_checked1.respond_to_X.respondsTo(context, arg1, arg1)) { RubyString path = (RubyString) TypeConverter.convertToType19(context, arg1, runtime.getString(), sites.to_path_checked1); io1 = (RubyIO) RubyFile.open(context, runtime.getFile(), new IRubyObject[]{path}, Block.NULL_BLOCK); close1 = true; } else if (sites.respond_to_read.respondsTo(context, arg1, arg1, true)) { if (length == null) { read = sites.read.call(context, arg1, arg1, context.nil).convertToString(); } else { read = sites.read.call(context, arg1, arg1, length).convertToString(); } } else { throw runtime.newArgumentError("Should be String or IO"); } if (arg2 instanceof RubyString) { io2 = (RubyIO) RubyFile.open(context, runtime.getFile(), new IRubyObject[]{arg2, runtime.newString("w")}, Block.NULL_BLOCK); close2 = true; } else if (arg2 instanceof RubyIO) { io2 = (RubyIO) arg2; } else if (sites.to_path_checked2.respond_to_X.respondsTo(context, arg2, arg2)) { RubyString path = (RubyString) TypeConverter.convertToType19(context, arg2, runtime.getString(), sites.to_path_checked2); io2 = (RubyIO) RubyFile.open(context, runtime.getFile(), new IRubyObject[]{path, runtime.newString("w")}, Block.NULL_BLOCK); close2 = true; } else if (sites.respond_to_write.respondsTo(context, arg2, arg2, true)) { if (read == null) { if (length == null) { read = io1.read(context, runtime.getNil()).convertToString(); } else { read = io1.read(context, length).convertToString(); } } return sites.write.call(context, arg2, arg2, read); } else { throw runtime.newArgumentError("Should be String or IO"); } if (io1 == null) { IRubyObject size = io2.write(context, read); io2.flush(context); return size; } io2 = io2.GetWriteIO(); if (!io1.openFile.isReadable()) throw runtime.newIOError("from IO is not readable"); if (!io2.openFile.isWritable()) throw runtime.newIOError("to IO is not writable"); io2.flush(context); // attempt to preserve position of original OpenFile fptr = io1.getOpenFileChecked(); boolean locked = fptr.lock(); try { long pos = fptr.tell(context); long size = 0; try { if (io1.openFile.fileChannel() == null) { long remaining = length == null ? -1 : length.getLongValue(); long position = offset == null ? -1 : offset.getLongValue(); if (io2.openFile.fileChannel() == null) { ReadableByteChannel from = io1.openFile.readChannel(); WritableByteChannel to = io2.openFile.writeChannel(); size = transfer(context, from, to, remaining, position); } else { ReadableByteChannel from = io1.openFile.readChannel(); FileChannel to = io2.openFile.fileChannel(); size = transfer(context, from, to, remaining, position); } } else { FileChannel from = io1.openFile.fileChannel(); WritableByteChannel to = io2.openFile.writeChannel(); long remaining = length == null ? from.size() : length.getLongValue(); long position = offset == null ? from.position() : offset.getLongValue(); size = transfer(from, to, remaining, position); } return context.runtime.newFixnum(size); } catch (IOException ioe) { ioe.printStackTrace(); throw runtime.newIOErrorFromException(ioe); } finally { if (offset != null) { fptr.seek(context, pos, PosixShim.SEEK_SET); } else { fptr.seek(context, pos + size, PosixShim.SEEK_SET); } } } finally { if (locked) fptr.unlock(); } } finally { if (close1 && io1 != null) { try {io1.close();} catch (Exception e) {} } if (close2 && io2 != null) { try {io2.close();} catch (Exception e) {} } } } private static long transfer(ThreadContext context, ReadableByteChannel from, FileChannel to, long length, long position) throws IOException { // handle large files on 32-bit JVMs long chunkSize = 128 * 1024 * 1024; long transferred = 0; long bytes; long startPosition = to.position(); if (position != -1) { if (from instanceof NativeSelectableChannel) { int ret = context.runtime.getPosix().lseek(((NativeSelectableChannel)from).getFD(), position, PosixShim.SEEK_SET); if (ret == -1) { throw context.runtime.newErrnoFromErrno(Errno.valueOf(context.runtime.getPosix().errno()), from.toString()); } } } if (length > 0) { while ((bytes = to.transferFrom(from, startPosition+transferred, Math.min(chunkSize, length))) > 0) { transferred += bytes; length -= bytes; } } else { while ((bytes = to.transferFrom(from, startPosition+transferred, chunkSize)) > 0) { transferred += bytes; } } // transforFrom does not change position of target to.position(startPosition + transferred); return transferred; } private static long transfer(FileChannel from, WritableByteChannel to, long remaining, long position) throws IOException { // handle large files on 32-bit JVMs long chunkSize = 128 * 1024 * 1024; long transferred = 0; if (remaining < 0) remaining = from.size(); while (remaining > 0) { long count = Math.min(remaining, chunkSize); long n = from.transferTo(position, count, to); if (n == 0) { break; } position += n; remaining -= n; transferred += n; } return transferred; } private static long transfer(ThreadContext context, ReadableByteChannel from, WritableByteChannel to, long length, long position) throws IOException { int chunkSize = 8 * 1024; ByteBuffer buffer = ByteBuffer.allocateDirect(chunkSize); long transferred = 0; if (position != -1) { if (from instanceof NativeSelectableChannel) { int ret = context.runtime.getPosix().lseek(((NativeSelectableChannel)from).getFD(), position, PosixShim.SEEK_SET); if (ret == -1) { throw context.runtime.newErrnoFromErrno(Errno.valueOf(context.runtime.getPosix().errno()), from.toString()); } } } while (true) { context.pollThreadEvents(); if (length > 0 && length < chunkSize) { // last read should limit to remaining length buffer.limit((int)length); } long n = from.read(buffer); if (n == -1) break; buffer.flip(); to.write(buffer); buffer.clear(); transferred += n; if (length > 0) { length -= n; if (length <= 0) break; } if (!from.isOpen()) break; } return transferred; } @JRubyMethod(name = "try_convert", meta = true) public static IRubyObject tryConvert(ThreadContext context, IRubyObject recv, IRubyObject arg) { return ( arg instanceof RubyObject && sites(context).respond_to_to_io.respondsTo(context, arg, arg, true) ) ? convertToIO(context, arg) : context.nil; } private static ByteList getNilByteList(Ruby runtime) { return ByteList.EMPTY_BYTELIST; } /** * Add a thread to the list of blocking threads for this IO. * * @param thread A thread blocking on this IO */ public void addBlockingThread(RubyThread thread) { OpenFile fptr = openFile; if (openFile != null) openFile.addBlockingThread(thread); } /** * Remove a thread from the list of blocking threads for this IO. * * @param thread A thread blocking on this IO */ public void removeBlockingThread(RubyThread thread) { if (openFile != null) openFile.removeBlockingThread(thread); } /** * Fire an IOError in all threads blocking on this IO object */ protected void interruptBlockingThreads() { if (openFile != null) openFile.interruptBlockingThreads(); } /** * Caching reference to allocated byte-lists, allowing for internal byte[] to be * reused, rather than reallocated. * * Predominately used on {@link RubyIO#getline(Ruby, ByteList)} and variants. * * @author realjenius */ private static class ByteListCache { private byte[] buffer = ByteList.NULL_ARRAY; final void release(ByteList bytes) { buffer = bytes.getUnsafeBytes(); } final ByteList allocate(int size) { return new ByteList(buffer, 0, size, false); } } /** * See http://ruby-doc.org/core-1.9.3/IO.html#method-c-new for the format of modes in options */ protected IOOptions updateIOOptionsFromOptions(ThreadContext context, RubyHash options, IOOptions ioOptions) { if (options == null || options.isNil()) return ioOptions; final Ruby runtime = context.runtime; final RubySymbol mode = runtime.newSymbol("mode"); if (options.containsKey(mode)) { ioOptions = parseIOOptions(options.fastARef(mode)); } // This duplicates the non-error behavior of MRI 1.9: the // :binmode option is ORed in with other options. It does // not obliterate what came before. final RubySymbol binmode = runtime.newSymbol("binmode"); if (options.containsKey(binmode) && options.fastARef(binmode).isTrue()) { ioOptions = newIOOptions(runtime, ioOptions, ModeFlags.BINARY); } // This duplicates the non-error behavior of MRI 1.9: the // :binmode option is ORed in with other options. It does // not obliterate what came before. if (options.containsKey(binmode) && options.fastARef(binmode).isTrue()) { ioOptions = newIOOptions(runtime, ioOptions, ModeFlags.BINARY); } final RubySymbol textmode = runtime.newSymbol("textmode"); if (options.containsKey(textmode) && options.fastARef(textmode).isTrue()) { ioOptions = newIOOptions(runtime, ioOptions, ModeFlags.TEXT); } final RubySymbol open_args = runtime.newSymbol("open_args"); // TODO: Waaaay different than MRI. They uniformly have all opening logic // do a scan of args before anything opens. We do this logic in a less // consistent way. We should consider re-impling all IO/File construction // logic. if (options.containsKey(open_args)) { IRubyObject args = options.fastARef(open_args); RubyArray openArgs = args.convertToArray(); for (int i = 0; i < openArgs.size(); i++) { IRubyObject arg = openArgs.eltInternal(i); if (arg instanceof RubyString) { // Overrides all? ioOptions = newIOOptions(runtime, arg.asJavaString()); } else if (arg instanceof RubyFixnum) { ioOptions = newIOOptions(runtime, ((RubyFixnum) arg).getLongValue()); } else if (arg instanceof RubyHash) { ioOptions = updateIOOptionsFromOptions(context, (RubyHash) arg, ioOptions); } } } EncodingUtils.ioExtractEncodingOption(context, this, options, null); return ioOptions; } private static final Set UNSUPPORTED_SPAWN_OPTIONS = new HashSet(Arrays.asList(new String[] { "unsetenv_others", "prgroup", "new_pgroup", "rlimit_resourcename", "chdir", "umask", "in", "out", "err", "close_others" })); private static final Set ALL_SPAWN_OPTIONS = new HashSet(Arrays.asList(new String[] { "unsetenv_others", "prgroup", "new_pgroup", "rlimit_resourcename", "chdir", "umask", "in", "out", "err", "close_others" })); /** * Warn when using exec with unsupported options. * * @param options */ public static void checkExecOptions(IRubyObject options) { checkUnsupportedOptions(options, UNSUPPORTED_SPAWN_OPTIONS, "unsupported exec option"); checkValidOptions(options, ALL_SPAWN_OPTIONS); } /** * Warn when using spawn with unsupported options. * * @param options */ public static void checkSpawnOptions(IRubyObject options) { checkUnsupportedOptions(options, UNSUPPORTED_SPAWN_OPTIONS, "unsupported spawn option"); checkValidOptions(options, ALL_SPAWN_OPTIONS); } /** * Warn when using spawn with unsupported options. * * @param options */ public static void checkPopenOptions(IRubyObject options) { checkUnsupportedOptions(options, UNSUPPORTED_SPAWN_OPTIONS, "unsupported popen option"); } /** * Warn when using unsupported options. * * @param options */ private static void checkUnsupportedOptions(IRubyObject options, Set unsupported, String error) { if (options == null || options.isNil() || !(options instanceof RubyHash)) return; RubyHash optsHash = (RubyHash)options; Ruby runtime = optsHash.getRuntime(); for (String key : unsupported) { if (optsHash.containsKey(runtime.newSymbol(key))) { runtime.getWarnings().warn(error + ": " + key); } } } /** * Error when using unknown option. * * @param options */ private static void checkValidOptions(IRubyObject options, Set valid) { if (options == null || options.isNil() || !(options instanceof RubyHash)) return; RubyHash optsHash = (RubyHash)options; Ruby runtime = optsHash.getRuntime(); for (Object opt : optsHash.keySet()) { if (opt instanceof RubySymbol || opt instanceof RubyFixnum || opt instanceof RubyArray || valid.contains(opt.toString())) { continue; } throw runtime.newTypeError("wrong exec option: " + opt); } } // MRI: check_exec_env, w/ check_exec_env_i body in-line public static RubyArray checkExecEnv(ThreadContext context, RubyHash hash) { return PopenExecutor.checkExecEnv(context, hash); } /** * Try for around 1s to destroy the child process. This is to work around * issues on some JVMs where if you try to destroy the process too quickly * it may not be ready and may ignore the destroy. A subsequent waitFor * will then hang. This version tries to destroy and call exitValue * repeatedly for up to 1000 calls with 1ms delay between iterations, with * the intent that the target process ought to be "ready to die" fairly * quickly and we don't get stuck in a blocking waitFor call. * * @param process The process to obliterate */ public static void obliterateProcess(Process process) { int i = 0; Object waitLock = new Object(); while (true) { // only try 1000 times with a 1ms sleep between, so we don't hang // forever on processes that ignore SIGTERM. After that, not much // we can do... if (i >= 1000) { return; } // attempt to destroy (SIGTERM on UNIX, TerminateProcess on Windows) process.destroy(); try { // get the exit value; succeeds if it has terminated, throws // IllegalThreadStateException if not. process.exitValue(); } catch (IllegalThreadStateException itse) { // increment count and try again after a 1ms sleep i += 1; synchronized (waitLock) { try {waitLock.wait(1);} catch (InterruptedException ie) {} } continue; } // success! break; } } public static ModeFlags newModeFlags(Ruby runtime, long mode) { return newModeFlags(runtime, (int) mode); } public static ModeFlags newModeFlags(Ruby runtime, int mode) { try { return new ModeFlags(mode); } catch (InvalidValueException ive) { throw runtime.newErrnoEINVALError(); } } public static ModeFlags newModeFlags(Ruby runtime, String mode) { try { return new ModeFlags(mode); } catch (InvalidValueException ive) { // This is used by File and StringIO, which seem to want an ArgumentError instead of EINVAL throw runtime.newArgumentError("illegal access mode " + mode); } } public static IOOptions newIOOptions(Ruby runtime, ModeFlags modeFlags) { return new IOOptions(modeFlags); } public static IOOptions newIOOptions(Ruby runtime, long mode) { return newIOOptions(runtime, (int) mode); } public static IOOptions newIOOptions(Ruby runtime, int mode) { try { ModeFlags modeFlags = new ModeFlags(mode); return new IOOptions(modeFlags); } catch (InvalidValueException ive) { throw runtime.newErrnoEINVALError(); } } public static IOOptions newIOOptions(Ruby runtime, String mode) { try { return new IOOptions(runtime, mode); } catch (InvalidValueException ive) { // This is used by File and StringIO, which seem to want an ArgumentError instead of EINVAL throw runtime.newArgumentError("illegal access mode " + mode); } } public static IOOptions newIOOptions(Ruby runtime, IOOptions oldFlags, int orOflags) { try { return new IOOptions(new ModeFlags(oldFlags.getModeFlags().getFlags() | orOflags)); } catch (InvalidValueException ive) { throw runtime.newErrnoEINVALError(); } } @Deprecated public IRubyObject readline(ThreadContext context, IRubyObject[] args) { return args.length == 0 ? readline(context) : readline(context, args[0]); } @Override public void setEnc2(Encoding enc2) { openFile.encs.enc2 = enc2; } @Override public void setEnc(Encoding enc) { openFile.encs.enc = enc; } @Override public void setEcflags(int ecflags) { openFile.encs.ecflags = ecflags; } @Override public int getEcflags() { return openFile.encs.ecflags; } @Override public void setEcopts(IRubyObject ecopts) { openFile.encs.ecopts = ecopts; } @Override public IRubyObject getEcopts() { return openFile.encs.ecopts; } @Override public void setBOM(boolean bom) { openFile.setBOM(bom); } @Override public boolean getBOM() { return openFile.isBOM(); } // MRI: rb_io_ascii8bit_binmode protected RubyIO setAscii8bitBinmode() { OpenFile fptr; fptr = getOpenFileChecked(); fptr.ascii8bitBinmode(getRuntime()); return this; } public final OpenFile MakeOpenFile() { Ruby runtime = getRuntime(); if (openFile != null) { rbIoClose(runtime); rb_io_fptr_finalize(runtime, openFile); openFile = null; } openFile = new OpenFile(runtime.getNil()); runtime.addInternalFinalizer(openFile); return openFile; } private static int rb_io_fptr_finalize(Ruby runtime, OpenFile fptr) { if (fptr == null) return 0; fptr.setPath(null);; if (fptr.fd() != null) fptr.cleanup(runtime, true); fptr.write_lock = null; if (fptr.rbuf.ptr != null) { fptr.rbuf.ptr = null; } if (fptr.wbuf.ptr != null) { fptr.wbuf.ptr = null; } fptr.clearCodeConversion(); return 1; } private static final byte[] NEWLINE_BYTES = { (byte) '\n' }; private static IOSites sites(ThreadContext context) { return context.sites.IO; } @Deprecated public IRubyObject getline(Ruby runtime, ByteList separator) { return getline(runtime.getCurrentContext(), runtime.newString(separator), -1, null); } @Deprecated public IRubyObject getline(Ruby runtime, ByteList separator, long limit) { return getline(runtime.getCurrentContext(), runtime.newString(separator), limit, null); } @Deprecated public IRubyObject getline(ThreadContext context, ByteList separator) { return getline(context, RubyString.newString(context.runtime, separator), -1, null); } @Deprecated public IRubyObject getline(ThreadContext context, ByteList separator, long limit) { return getline(context, RubyString.newString(context.runtime, separator), limit, null); } @Deprecated public IRubyObject lines19(final ThreadContext context, Block block) { return lines(context, block); } @Deprecated public IRubyObject each_char19(final ThreadContext context, final Block block) { return each_char(context, block); } @Deprecated public IRubyObject chars19(final ThreadContext context, final Block block) { return chars(context, block); } @Deprecated public RubyArray readlines19(ThreadContext context, IRubyObject[] args) { return readlines(context, args); } @Deprecated public RubyIO(Ruby runtime, STDIO stdio) { super(runtime, runtime.getIO()); RubyIO tmp = null; switch (stdio) { case IN: tmp = prepStdio(runtime, runtime.getIn(), Channels.newChannel(runtime.getIn()), OpenFile.READABLE, runtime.getIO(), ""); break; case OUT: tmp = prepStdio(runtime, runtime.getOut(), Channels.newChannel(runtime.getOut()), OpenFile.WRITABLE, runtime.getIO(), ""); break; case ERR: tmp = prepStdio(runtime, runtime.getErr(), Channels.newChannel(runtime.getErr()), OpenFile.WRITABLE | OpenFile.SYNC, runtime.getIO(), ""); break; } this.openFile = tmp.openFile; tmp.openFile = null; } @Deprecated public RubyIO(Ruby runtime, RubyClass cls, ShellLauncher.POpenProcess process, RubyHash options, IOOptions ioOptions) { super(runtime, cls); ioOptions = updateIOOptionsFromOptions(runtime.getCurrentContext(), options, ioOptions); openFile = MakeOpenFile(); setupPopen(ioOptions.getModeFlags(), process); } @Deprecated public static ModeFlags getIOModes(Ruby runtime, String modesString) { return newModeFlags(runtime, modesString); } @Deprecated public static int getIOModesIntFromString(Ruby runtime, String modesString) { try { return ModeFlags.getOFlagsFromString(modesString); } catch (InvalidValueException ive) { throw runtime.newArgumentError("illegal access mode"); } } @Deprecated public static IRubyObject writeStatic(ThreadContext context, IRubyObject recv, IRubyObject[] argv, Block unusedBlock) { return write(context, recv, argv); } @Deprecated @JRubyMethod(name = "popen3", rest = true, meta = true) public static IRubyObject popen3(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { final Ruby runtime = context.runtime; // TODO: handle opts if (args.length > 0 && args[args.length - 1] instanceof RubyHash) { args = Arrays.copyOf(args, args.length - 1); } final POpenTuple tuple = popenSpecial(context, args); final long pid = ShellLauncher.getPidFromProcess(tuple.process); // array trick to be able to reference enclosing RubyThread final RubyThread[] waitThread = new RubyThread[1]; waitThread[0] = new RubyThread( runtime, (RubyClass) runtime.getClassFromPath("Process::WaitThread"), new ThreadedRunnable() { volatile Thread javaThread; @Override public Thread getJavaThread() { return javaThread; } @Override public void run() { javaThread = Thread.currentThread(); RubyThread rubyThread; // spin a bit until this happens; should almost never spin while ((rubyThread = waitThread[0]) == null) { Thread.yield(); } runtime.getThreadService().registerNewThread(rubyThread); rubyThread.op_aset(runtime.newSymbol("pid"), runtime.newFixnum(pid)); try { int exitValue = tuple.process.waitFor(); // RubyStatus uses real native status now, so we unshift Java's shifted exit status RubyProcess.RubyStatus status = RubyProcess.RubyStatus.newProcessStatus( runtime, exitValue << 8, pid); rubyThread.cleanTerminate(status); } catch (Throwable t) { rubyThread.exceptionRaised(t); } finally { rubyThread.dispose(); } } }); RubyArray yieldArgs = RubyArray.newArrayLight(runtime, tuple.output, tuple.input, tuple.error, waitThread[0]); if (block.isGiven()) { try { return block.yield(context, yieldArgs); } finally { cleanupPOpen(tuple); IRubyObject status = waitThread[0].join(IRubyObject.NULL_ARRAY); context.setLastExitStatus(status); } } return yieldArgs; } @Deprecated public static IRubyObject popen4(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { Ruby runtime = context.runtime; try { POpenTuple tuple = popenSpecial(context, args); RubyArray yieldArgs = RubyArray.newArrayLight(runtime, runtime.newFixnum(ShellLauncher.getPidFromProcess(tuple.process)), tuple.output, tuple.input, tuple.error); if (block.isGiven()) { try { return block.yield(context, yieldArgs); } finally { cleanupPOpen(tuple); // RubyStatus uses real native status now, so we unshift Java's shifted exit status context.setLastExitStatus(RubyProcess.RubyStatus.newProcessStatus(runtime, tuple.process.waitFor() << 8, ShellLauncher.getPidFromProcess(tuple.process))); } } return yieldArgs; } catch (InterruptedException e) { throw runtime.newThreadError("unexpected interrupt"); } } @Deprecated private static void cleanupPOpen(POpenTuple tuple) { if (tuple.input.openFile.isOpen()) { try { tuple.input.close(); } catch (RaiseException re) {} } if (tuple.output.openFile.isOpen()) { try { tuple.output.close(); } catch (RaiseException re) {} } if (tuple.error.openFile.isOpen()) { try { tuple.error.close(); } catch (RaiseException re) {} } } @Deprecated private static class POpenTuple { public POpenTuple(RubyIO i, RubyIO o, RubyIO e, Process p) { input = i; output = o; error = e; process = p; } public final RubyIO input; public final RubyIO output; public final RubyIO error; public final Process process; } @Deprecated public static POpenTuple popenSpecial(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; try { ShellLauncher.POpenProcess process = ShellLauncher.popen3(runtime, args, false); RubyIO input = process.getInput() != null ? new RubyIO(runtime, process.getInput()) : new RubyIO(runtime, process.getInputStream()); RubyIO output = process.getOutput() != null ? new RubyIO(runtime, process.getOutput()) : new RubyIO(runtime, process.getOutputStream()); RubyIO error = process.getError() != null ? new RubyIO(runtime, process.getError()) : new RubyIO(runtime, process.getErrorStream()); // ensure the OpenFile knows it's a process; see OpenFile#finalize input.getOpenFile().setProcess(process); output.getOpenFile().setProcess(process); error.getOpenFile().setProcess(process); // set all streams as popenSpecial streams, so we don't shut down process prematurely input.popenSpecial = true; output.popenSpecial = true; error.popenSpecial = true; // process streams are not seekable // input.getOpenFile().getMainStreamSafe().getDescriptor(). // setCanBeSeekable(false); // output.getOpenFile().getMainStreamSafe().getDescriptor(). // setCanBeSeekable(false); // error.getOpenFile().getMainStreamSafe().getDescriptor(). // setCanBeSeekable(false); return new POpenTuple(input, output, error, process); // } catch (BadDescriptorException e) { // throw runtime.newErrnoEBADFError(); } catch (IOException e) { throw runtime.newIOErrorFromException(e); } } @Deprecated public IRubyObject doWriteNonblock(ThreadContext context, IRubyObject[] argv, boolean useException) { return write_nonblock(context, argv); } @Deprecated public static IRubyObject select_static(ThreadContext context, Ruby runtime, IRubyObject[] args) { return select(context, runtime.getIO(), args); } protected OpenFile openFile; /** * If the stream is being used for popen, we don't want to destroy the process * when we close the stream. */ protected boolean popenSpecial; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy