Make sure syncing gets set on OpenFile too
[jruby.git] / src / org / jruby / RubyIO.java
blobd7e607f2ae8b6659bc87ed5d98751773b24c100a
1 /*
2 **** BEGIN LICENSE BLOCK *****
3 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Common Public
6 * License Version 1.0 (the "License"); you may not use this file
7 * except in compliance with the License. You may obtain a copy of
8 * the License at http://www.eclipse.org/legal/cpl-v10.html
10 * Software distributed under the License is distributed on an "AS
11 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
12 * implied. See the License for the specific language governing
13 * rights and limitations under the License.
15 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
16 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
17 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
18 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
19 * Copyright (C) 2004-2006 Charles O Nutter <headius@headius.com>
20 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
21 * Copyright (C) 2006 Evan Buswell <ebuswell@gmail.com>
22 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the CPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the CPL, the GPL or the LGPL.
35 ***** END LICENSE BLOCK *****/
36 package org.jruby;
38 import static java.lang.System.out;
39 import java.util.logging.Level;
40 import java.util.logging.Logger;
41 import org.jruby.util.io.FileExistsException;
42 import org.jruby.util.io.STDIO;
43 import org.jruby.util.io.OpenFile;
44 import org.jruby.util.io.ChannelDescriptor;
45 import java.io.EOFException;
46 import java.io.FileDescriptor;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.OutputStream;
50 import java.lang.ref.Reference;
51 import java.lang.ref.WeakReference;
52 import java.nio.channels.Channel;
53 import java.nio.channels.Channels;
54 import java.nio.channels.Pipe;
55 import java.nio.channels.SelectableChannel;
56 import java.nio.channels.SelectionKey;
57 import java.nio.channels.Selector;
58 import java.util.ArrayList;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Set;
63 import org.jruby.anno.JRubyMethod;
65 import org.jruby.common.IRubyWarnings.ID;
66 import org.jruby.exceptions.RaiseException;
67 import org.jruby.runtime.Block;
68 import org.jruby.runtime.CallType;
69 import org.jruby.runtime.CallbackFactory;
70 import org.jruby.runtime.MethodIndex;
71 import org.jruby.runtime.ObjectAllocator;
72 import org.jruby.runtime.ThreadContext;
73 import org.jruby.runtime.Visibility;
74 import org.jruby.runtime.builtin.IRubyObject;
75 import org.jruby.util.ByteList;
76 import org.jruby.util.io.Stream;
77 import org.jruby.util.io.ModeFlags;
78 import org.jruby.util.ShellLauncher;
79 import org.jruby.util.TypeConverter;
80 import org.jruby.util.io.BadDescriptorException;
81 import org.jruby.util.io.ChannelStream;
82 import org.jruby.util.io.InvalidValueException;
83 import org.jruby.util.io.PipeException;
85 /**
87 * @author jpetersen
89 public class RubyIO extends RubyObject {
90 protected OpenFile openFile;
92 public void registerDescriptor(ChannelDescriptor descriptor) {
93 getRuntime().getDescriptors().put(new Integer(descriptor.getFileno()), new WeakReference<ChannelDescriptor>(descriptor));
96 public void unregisterDescriptor(int aFileno) {
97 getRuntime().getDescriptors().remove(new Integer(aFileno));
100 public ChannelDescriptor getDescriptorByFileno(int aFileno) {
101 Reference<ChannelDescriptor> reference = getRuntime().getDescriptors().get(new Integer(aFileno));
102 if (reference == null) {
103 return null;
105 return (ChannelDescriptor) reference.get();
108 // FIXME can't use static; would interfere with other runtimes in the same JVM
109 protected static int filenoIndex = 2;
111 public static int getNewFileno() {
112 filenoIndex++;
114 return filenoIndex;
117 // This should only be called by this and RubyFile.
118 // It allows this object to be created without a IOHandler.
119 public RubyIO(Ruby runtime, RubyClass type) {
120 super(runtime, type);
122 openFile = new OpenFile();
125 public RubyIO(Ruby runtime, OutputStream outputStream) {
126 super(runtime, runtime.getIO());
128 // We only want IO objects with valid streams (better to error now).
129 if (outputStream == null) {
130 throw runtime.newIOError("Opening invalid stream");
133 openFile = new OpenFile();
135 try {
136 openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(Channels.newChannel(outputStream), getNewFileno(), new FileDescriptor())));
137 } catch (InvalidValueException e) {
138 throw getRuntime().newErrnoEINVALError();
141 openFile.setMode(OpenFile.WRITABLE | OpenFile.APPEND);
143 registerDescriptor(openFile.getMainStream().getDescriptor());
146 public RubyIO(Ruby runtime, InputStream inputStream) {
147 super(runtime, runtime.getIO());
149 if (inputStream == null) {
150 throw runtime.newIOError("Opening invalid stream");
153 openFile = new OpenFile();
155 try {
156 openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(Channels.newChannel(inputStream), getNewFileno(), new FileDescriptor())));
157 } catch (InvalidValueException e) {
158 throw getRuntime().newErrnoEINVALError();
161 openFile.setMode(OpenFile.READABLE);
163 registerDescriptor(openFile.getMainStream().getDescriptor());
166 public RubyIO(Ruby runtime, Channel channel) {
167 super(runtime, runtime.getIO());
169 // We only want IO objects with valid streams (better to error now).
170 if (channel == null) {
171 throw runtime.newIOError("Opening invalid stream");
174 openFile = new OpenFile();
176 try {
177 openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(channel, getNewFileno(), new FileDescriptor())));
178 } catch (InvalidValueException e) {
179 throw getRuntime().newErrnoEINVALError();
182 openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
184 registerDescriptor(openFile.getMainStream().getDescriptor());
187 public RubyIO(Ruby runtime, Process process, ModeFlags modes) {
188 super(runtime, runtime.getIO());
190 openFile = new OpenFile();
192 openFile.setMode(modes.getOpenFileFlags() | OpenFile.SYNC);
193 openFile.setProcess(process);
195 try {
196 InputStream pipeIn = process.getInputStream();
197 ChannelDescriptor main = new ChannelDescriptor(
198 Channels.newChannel(pipeIn),
199 getNewFileno(),
200 new FileDescriptor());
202 OutputStream pipeOut = process.getOutputStream();
203 ChannelDescriptor pipe = new ChannelDescriptor(
204 Channels.newChannel(pipeOut),
205 getNewFileno(),
206 new FileDescriptor());
208 if (!openFile.isReadable()) {
209 main.close();
210 pipeIn.close();
211 } else {
212 openFile.setMainStream(new ChannelStream(getRuntime(), main));
215 if (!openFile.isWritable()) {
216 pipe.close();
217 pipeOut.close();
218 } else {
219 if (openFile.getMainStream() != null) {
220 openFile.setPipeStream(new ChannelStream(getRuntime(), pipe));
221 } else {
222 openFile.setMainStream(new ChannelStream(getRuntime(), pipe));
226 registerDescriptor(main);
227 registerDescriptor(pipe);
228 } catch (BadDescriptorException ex) {
229 throw getRuntime().newErrnoEBADFError();
230 } catch (IOException ex) {
231 throw getRuntime().newIOErrorFromException(ex);
232 } catch (InvalidValueException e) {
233 throw getRuntime().newErrnoEINVALError();
237 public RubyIO(Ruby runtime, STDIO stdio) {
238 super(runtime, runtime.getIO());
240 openFile = new OpenFile();
242 try {
243 switch (stdio) {
244 case IN:
245 openFile.setMainStream(
246 new ChannelStream(
247 runtime,
248 // special constructor that accepts stream, not channel
249 new ChannelDescriptor(runtime.getIn(), 0, new ModeFlags(ModeFlags.RDONLY), FileDescriptor.in),
250 FileDescriptor.in));
251 break;
252 case OUT:
253 openFile.setMainStream(
254 new ChannelStream(
255 runtime,
256 new ChannelDescriptor(Channels.newChannel(runtime.getOut()), 1, new ModeFlags(ModeFlags.WRONLY | ModeFlags.APPEND), FileDescriptor.out),
257 FileDescriptor.out));
258 openFile.getMainStream().setSync(true);
259 break;
260 case ERR:
261 openFile.setMainStream(
262 new ChannelStream(
263 runtime,
264 new ChannelDescriptor(Channels.newChannel(runtime.getErr()), 2, new ModeFlags(ModeFlags.WRONLY | ModeFlags.APPEND), FileDescriptor.out),
265 FileDescriptor.err));
266 openFile.getMainStream().setSync(true);
267 break;
269 } catch (InvalidValueException ex) {
270 throw getRuntime().newErrnoEINVALError();
273 openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
275 registerDescriptor(openFile.getMainStream().getDescriptor());
278 public OpenFile getOpenFile() {
279 return openFile;
282 protected OpenFile getOpenFileChecked() {
283 openFile.checkClosed(getRuntime());
284 return openFile;
287 private static ObjectAllocator IO_ALLOCATOR = new ObjectAllocator() {
288 public IRubyObject allocate(Ruby runtime, RubyClass klass) {
289 return new RubyIO(runtime, klass);
293 public static RubyClass createIOClass(Ruby runtime) {
294 RubyClass ioClass = runtime.defineClass("IO", runtime.getObject(), IO_ALLOCATOR);
295 CallbackFactory callbackFactory = runtime.callbackFactory(RubyIO.class);
296 ioClass.kindOf = new RubyModule.KindOf() {
297 @Override
298 public boolean isKindOf(IRubyObject obj, RubyModule type) {
299 return obj instanceof RubyIO;
303 ioClass.includeModule(runtime.getEnumerable());
305 // TODO: Implement tty? and isatty. We have no real capability to
306 // determine this from java, but if we could set tty status, then
307 // we could invoke jruby differently to allow stdin to return true
308 // on this. This would allow things like cgi.rb to work properly.
310 ioClass.defineAnnotatedMethods(RubyIO.class);
312 // Constants for seek
313 ioClass.fastSetConstant("SEEK_SET", runtime.newFixnum(Stream.SEEK_SET));
314 ioClass.fastSetConstant("SEEK_CUR", runtime.newFixnum(Stream.SEEK_CUR));
315 ioClass.fastSetConstant("SEEK_END", runtime.newFixnum(Stream.SEEK_END));
317 ioClass.dispatcher = callbackFactory.createDispatcher(ioClass);
319 return ioClass;
322 public OutputStream getOutStream() {
323 return getOpenFileChecked().getMainStream().newOutputStream();
326 public InputStream getInStream() {
327 return getOpenFileChecked().getMainStream().newInputStream();
330 public Channel getChannel() {
331 if (getOpenFileChecked().getMainStream() instanceof ChannelStream) {
332 return ((ChannelStream) openFile.getMainStream()).getDescriptor().getChannel();
333 } else {
334 return null;
338 public Stream getHandler() {
339 return getOpenFileChecked().getMainStream();
342 @JRubyMethod(name = "reopen", required = 1, optional = 1)
343 public IRubyObject reopen(IRubyObject[] args) throws InvalidValueException {
344 if (args.length < 1) {
345 throw getRuntime().newArgumentError("wrong number of arguments");
348 IRubyObject tmp = TypeConverter.convertToTypeWithCheck(args[0],
349 getRuntime().getIO(), MethodIndex.getIndex("to_io"), "to_io");
350 if (!tmp.isNil()) {
351 try {
352 RubyIO ios = (RubyIO) tmp;
354 if (ios.openFile == this.openFile) {
355 return this;
358 OpenFile originalFile = ios.getOpenFileChecked();
359 OpenFile selfFile = getOpenFileChecked();
361 long pos = 0;
362 if (originalFile.isReadable()) {
363 pos = originalFile.getMainStream().fgetpos();
366 if (originalFile.getPipeStream() != null) {
367 originalFile.getPipeStream().fflush();
368 } else if (originalFile.isWritable()) {
369 originalFile.getMainStream().fflush();
372 if (selfFile.isWritable()) {
373 selfFile.getWriteStream().fflush();
376 selfFile.setMode(originalFile.getMode());
377 selfFile.setProcess(originalFile.getProcess());
378 selfFile.setLineNumber(originalFile.getLineNumber());
379 selfFile.setPath(originalFile.getPath());
380 selfFile.setFinalizer(originalFile.getFinalizer());
382 ChannelDescriptor selfDescriptor = selfFile.getMainStream().getDescriptor();
383 ChannelDescriptor originalDescriptor = originalFile.getMainStream().getDescriptor();
385 // confirm we're not reopening self's channel
386 if (selfDescriptor.getChannel() != originalDescriptor.getChannel()) {
387 // check if we're a stdio IO, and ensure we're not badly mutilated
388 if (selfDescriptor.getFileno() >=0 && selfDescriptor.getFileno() <= 2) {
389 selfFile.getMainStream().clearerr();
391 // dup2 new fd into self to preserve fileno and references to it
392 originalDescriptor.dup2(selfDescriptor);
394 // re-register, since fileno points at something new now
395 registerDescriptor(selfFile.getMainStream().getDescriptor());
396 } else {
397 Stream pipeFile = selfFile.getPipeStream();
398 int mode = selfFile.getMode();
399 selfFile.getMainStream().fclose();
400 selfFile.setPipeStream(null);
402 // TODO: turn off readable? am I reading this right?
403 // This only seems to be used while duping below, since modes gets
404 // reset to actual modes afterward
405 //fptr->mode &= (m & FMODE_READABLE) ? ~FMODE_READABLE : ~FMODE_WRITABLE;
407 if (pipeFile != null) {
408 selfFile.setMainStream(ChannelStream.fdopen(getRuntime(), originalDescriptor, new ModeFlags()));
409 selfFile.setPipeStream(pipeFile);
410 } else {
411 selfFile.setMainStream(
412 new ChannelStream(
413 getRuntime(),
414 originalDescriptor.dup2(selfDescriptor.getFileno())));
416 // re-register the descriptor
417 registerDescriptor(selfFile.getMainStream().getDescriptor());
419 // since we're not actually duping the incoming channel into our handler, we need to
420 // copy the original sync behavior from the other handler
421 selfFile.getMainStream().setSync(selfFile.getMainStream().isSync());
423 selfFile.setMode(mode);
425 // TODO: anything threads attached to original fd are notified of the close...
426 // see rb_thread_fd_close
428 if (originalFile.isReadable() && pos >= 0) {
429 selfFile.seek(pos, Stream.SEEK_SET);
430 originalFile.seek(pos, Stream.SEEK_SET);
434 // TODO: more pipe logic
435 // if (fptr->f2 && fd != fileno(fptr->f2)) {
436 // fd = fileno(fptr->f2);
437 // if (!orig->f2) {
438 // fclose(fptr->f2);
439 // rb_thread_fd_close(fd);
440 // fptr->f2 = 0;
441 // }
442 // else if (fd != (fd2 = fileno(orig->f2))) {
443 // fclose(fptr->f2);
444 // rb_thread_fd_close(fd);
445 // if (dup2(fd2, fd) < 0)
446 // rb_sys_fail(orig->path);
447 // fptr->f2 = rb_fdopen(fd, "w");
448 // }
449 // }
451 // TODO: restore binary mode
452 // if (fptr->mode & FMODE_BINMODE) {
453 // rb_io_binmode(io);
454 // }
456 // TODO: set our metaclass to target's class (i.e. scary!)
458 } catch (IOException ex) { // TODO: better error handling
459 throw getRuntime().newIOError("could not reopen: " + ex.getMessage());
460 } catch (BadDescriptorException ex) {
461 throw getRuntime().newIOError("could not reopen: " + ex.getMessage());
462 } catch (PipeException ex) {
463 throw getRuntime().newIOError("could not reopen: " + ex.getMessage());
465 } else {
466 IRubyObject pathString = args[0].convertToString();
468 // TODO: check safe, taint on incoming string
470 if (openFile == null) {
471 openFile = new OpenFile();
474 try {
475 ModeFlags modes;
476 if (args.length > 1) {
477 IRubyObject modeString = args[1].convertToString();
478 modes = getIOModes(getRuntime(), modeString.toString());
480 openFile.setMode(modes.getOpenFileFlags());
481 } else {
482 modes = getIOModes(getRuntime(), "r");
485 String path = pathString.toString();
487 // Ruby code frequently uses a platform check to choose "NUL:" on windows
488 // but since that check doesn't work well on JRuby, we help it out
490 openFile.setPath(path);
492 if (openFile.getMainStream() == null) {
493 try {
494 openFile.setMainStream(ChannelStream.fopen(getRuntime(), path, modes));
495 } catch (FileExistsException fee) {
496 throw getRuntime().newErrnoEEXISTError(path);
499 registerDescriptor(openFile.getMainStream().getDescriptor());
500 if (openFile.getPipeStream() != null) {
501 openFile.getPipeStream().fclose();
502 unregisterDescriptor(openFile.getPipeStream().getDescriptor().getFileno());
503 openFile.setPipeStream(null);
505 return this;
506 } else {
507 // TODO: This is an freopen in MRI, this is close, but not quite the same
508 openFile.getMainStream().freopen(path, getIOModes(getRuntime(), openFile.getModeAsString(getRuntime())));
510 // re-register
511 registerDescriptor(openFile.getMainStream().getDescriptor());
513 if (openFile.getPipeStream() != null) {
514 // TODO: pipe handler to be reopened with path and "w" mode
517 } catch (PipeException pe) {
518 throw getRuntime().newErrnoEPIPEError();
519 } catch (IOException ex) {
520 throw getRuntime().newIOErrorFromException(ex);
521 } catch (BadDescriptorException ex) {
522 throw getRuntime().newErrnoEBADFError();
523 } catch (InvalidValueException e) {
524 throw getRuntime().newErrnoEINVALError();
528 // A potentially previously close IO is being 'reopened'.
529 return this;
532 public static ModeFlags getIOModes(Ruby runtime, String modesString) throws InvalidValueException {
533 return new ModeFlags(getIOModesIntFromString(runtime, modesString));
536 public static int getIOModesIntFromString(Ruby runtime, String modesString) {
537 int modes = 0;
538 int length = modesString.length();
540 if (length == 0) {
541 throw runtime.newArgumentError("illegal access mode");
544 switch (modesString.charAt(0)) {
545 case 'r' :
546 modes |= ModeFlags.RDONLY;
547 break;
548 case 'a' :
549 modes |= ModeFlags.APPEND | ModeFlags.WRONLY | ModeFlags.CREAT;
550 break;
551 case 'w' :
552 modes |= ModeFlags.WRONLY | ModeFlags.TRUNC | ModeFlags.CREAT;
553 break;
554 default :
555 throw runtime.newArgumentError("illegal access mode " + modes);
558 for (int n = 1; n < length; n++) {
559 switch (modesString.charAt(n)) {
560 case 'b':
561 modes |= ModeFlags.BINARY;
562 break;
563 case '+':
564 modes = (modes & ~ModeFlags.ACCMODE) | ModeFlags.RDWR;
565 break;
566 default:
567 throw runtime.newArgumentError("illegal access mode " + modes);
571 return modes;
574 private static ByteList getSeparatorFromArgs(Ruby runtime, IRubyObject[] args, int idx) {
575 IRubyObject sepVal;
577 if (args.length > idx) {
578 sepVal = args[idx];
579 } else {
580 sepVal = runtime.getRecordSeparatorVar().get();
583 ByteList separator = sepVal.isNil() ? null : sepVal.convertToString().getByteList();
585 if (separator != null && separator.realSize == 0) {
586 separator = Stream.PARAGRAPH_DELIMETER;
589 return separator;
592 private ByteList getSeparatorForGets(IRubyObject[] args) {
593 return getSeparatorFromArgs(getRuntime(), args, 0);
596 public IRubyObject getline(ByteList separator) {
597 try {
598 OpenFile myOpenFile = getOpenFileChecked();
600 myOpenFile.checkReadable(getRuntime());
601 boolean isParagraph = separator == Stream.PARAGRAPH_DELIMETER;
602 separator = (separator == Stream.PARAGRAPH_DELIMETER) ?
603 Stream.PARAGRAPH_SEPARATOR : separator;
605 if (isParagraph) {
606 swallow('\n');
609 if (separator == null) {
610 IRubyObject str = readAll(null);
611 if (((RubyString)str).getByteList().length() == 0) {
612 return getRuntime().getNil();
614 incrementLineno(myOpenFile);
615 return str;
616 } else if (separator.length() == 1) {
617 return getlineFast(separator.get(0));
618 } else {
619 Stream readStream = myOpenFile.getMainStream();
620 int c = -1;
621 int newline = separator.get(separator.length() - 1) & 0xFF;
623 ByteList buf = new ByteList(1024);
624 boolean update = false;
626 while (true) {
627 do {
628 readCheck(readStream);
629 readStream.clearerr();
631 try {
632 c = readStream.fgetc();
633 } catch (EOFException e) {
634 c = -1;
637 if (c == -1) {
638 // TODO: clear error, wait for it to become readable
639 break;
642 buf.append(c);
644 update = true;
645 } while (c != newline); // loop until we see the nth separator char
647 // if we hit EOF, we're done
648 if (c == -1) {
649 break;
652 // if we've found the last char of the separator,
653 // and we've found at least as many characters as separator length,
654 // and the last n characters of our buffer match the separator, we're done
655 if (c == newline && buf.length() >= separator.length() &&
656 0 == ByteList.memcmp(buf.unsafeBytes(), buf.begin + buf.realSize - separator.length(), separator.unsafeBytes(), separator.begin, separator.realSize)) {
657 break;
661 if (isParagraph) {
662 if (c != -1) {
663 swallow('\n');
667 if (!update) {
668 return getRuntime().getNil();
669 } else {
670 incrementLineno(myOpenFile);
671 RubyString str = RubyString.newString(getRuntime(), buf);
672 str.setTaint(true);
674 return str;
677 } catch (PipeException ex) {
678 throw getRuntime().newErrnoEPIPEError();
679 } catch (InvalidValueException ex) {
680 throw getRuntime().newErrnoEINVALError();
681 } catch (EOFException e) {
682 return getRuntime().getNil();
683 } catch (BadDescriptorException e) {
684 throw getRuntime().newErrnoEBADFError();
685 } catch (IOException e) {
686 throw getRuntime().newIOError(e.getMessage());
690 private void incrementLineno(OpenFile myOpenFile) {
691 Ruby runtime = getRuntime();
692 int lineno = myOpenFile.getLineNumber() + 1;
693 myOpenFile.setLineNumber(lineno);
694 runtime.getGlobalVariables().set("$.", runtime.newFixnum(lineno));
695 // this is for a range check, near as I can tell
696 RubyNumeric.int2fix(getRuntime(), myOpenFile.getLineNumber());
699 protected boolean swallow(int term) throws IOException, BadDescriptorException {
700 Stream readStream = openFile.getMainStream();
701 int c;
703 do {
704 readCheck(readStream);
706 try {
707 c = readStream.fgetc();
708 } catch (EOFException e) {
709 c = -1;
712 if (c != term) {
713 readStream.ungetc(c);
714 return true;
716 } while (c != -1);
718 return false;
721 public IRubyObject getlineFast(int delim) throws IOException, BadDescriptorException {
722 Stream readStream = openFile.getMainStream();
723 int c = -1;
725 ByteList buf = new ByteList(1024);
726 boolean update = false;
727 do {
728 readCheck(readStream);
729 readStream.clearerr();
731 try {
732 c = readStream.fgetc();
733 } catch (EOFException e) {
734 c = -1;
737 if (c == -1) {
738 // TODO: clear error, wait for it to become readable
739 break;
742 buf.append(c);
744 update = true;
745 } while (c != delim);
747 if (!update) {
748 return getRuntime().getNil();
749 } else {
750 incrementLineno(openFile);
751 RubyString str = RubyString.newString(getRuntime(), buf);
752 str.setTaint(true);
754 return str;
757 // IO class methods.
759 @JRubyMethod(name = {"new", "for_fd"}, rest = true, frame = true, meta = true)
760 public static IRubyObject newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
761 RubyClass klass = (RubyClass)recv;
763 if (block.isGiven()) {
764 String className = klass.getName();
765 recv.getRuntime().getWarnings().warn(
766 ID.BLOCK_NOT_ACCEPTED,
767 className + "::new() does not take block; use " + className + "::open() instead",
768 className + "::open()");
771 return klass.newInstance(args, block);
774 @JRubyMethod(name = "initialize", required = 1, optional = 1, frame = true, visibility = Visibility.PRIVATE)
775 public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
776 int argCount = args.length;
777 ModeFlags modes;
779 int fileno = RubyNumeric.fix2int(args[0]);
781 try {
782 ChannelDescriptor descriptor = getDescriptorByFileno(fileno);
784 if (descriptor == null) {
785 throw getRuntime().newErrnoEBADFError();
788 descriptor.checkOpen();
790 if (argCount == 2) {
791 if (args[1] instanceof RubyFixnum) {
792 modes = new ModeFlags(RubyFixnum.fix2long(args[1]));
793 } else {
794 modes = getIOModes(getRuntime(), (args[1].convertToString().toString()));
796 } else {
797 // use original modes
798 modes = descriptor.getOriginalModes();
801 openFile.setMode(modes.getOpenFileFlags());
803 openFile.setMainStream(fdopen(descriptor, modes));
804 } catch (BadDescriptorException ex) {
805 throw getRuntime().newErrnoEBADFError();
806 } catch (InvalidValueException ive) {
807 throw getRuntime().newErrnoEINVALError();
810 return this;
813 protected Stream fdopen(ChannelDescriptor existingDescriptor, ModeFlags modes) throws InvalidValueException {
814 // See if we already have this descriptor open.
815 // If so then we can mostly share the handler (keep open
816 // file, but possibly change the mode).
818 if (existingDescriptor == null) {
819 // redundant, done above as well
821 // this seems unlikely to happen unless it's a totally bogus fileno
822 // ...so do we even need to bother trying to create one?
824 // IN FACT, we should probably raise an error, yes?
825 throw getRuntime().newErrnoEBADFError();
827 // if (mode == null) {
828 // mode = "r";
829 // }
831 // try {
832 // openFile.setMainStream(streamForFileno(getRuntime(), fileno));
833 // } catch (BadDescriptorException e) {
834 // throw getRuntime().newErrnoEBADFError();
835 // } catch (IOException e) {
836 // throw getRuntime().newErrnoEBADFError();
837 // }
838 // //modes = new IOModes(getRuntime(), mode);
840 // registerStream(openFile.getMainStream());
841 } else {
842 // We are creating a new IO object that shares the same
843 // IOHandler (and fileno).
844 return ChannelStream.fdopen(getRuntime(), existingDescriptor, modes);
848 @JRubyMethod(name = "open", required = 1, optional = 2, frame = true, meta = true)
849 public static IRubyObject open(IRubyObject recv, IRubyObject[] args, Block block) {
850 Ruby runtime = recv.getRuntime();
851 RubyClass klass = (RubyClass)recv;
853 RubyIO io = (RubyIO)klass.newInstance(args, block);
855 if (block.isGiven()) {
856 try {
857 return block.yield(runtime.getCurrentContext(), io);
858 } finally {
859 try {
860 io.getMetaClass().invoke(runtime.getCurrentContext(), io, "close", IRubyObject.NULL_ARRAY, CallType.FUNCTIONAL, Block.NULL_BLOCK);
861 } catch (RaiseException re) {
862 RubyException rubyEx = re.getException();
863 if (rubyEx.kind_of_p(runtime.getStandardError()).isTrue()) {
864 // MRI behavior: swallow StandardErorrs
865 } else {
866 throw re;
872 return io;
875 // This appears to be some windows-only mode. On a java platform this is a no-op
876 @JRubyMethod(name = "binmode")
877 public IRubyObject binmode() {
878 return this;
881 protected void checkInitialized() {
882 if (openFile == null) {
883 throw getRuntime().newIOError("uninitialized stream");
887 protected void checkClosed() {
888 if (openFile.getMainStream() == null && openFile.getPipeStream() == null) {
889 throw getRuntime().newIOError("closed stream");
893 @JRubyMethod(name = "syswrite", required = 1)
894 public IRubyObject syswrite(IRubyObject obj) {
895 try {
896 RubyString string = obj.asString();
898 OpenFile myOpenFile = getOpenFileChecked();
900 myOpenFile.checkWritable(getRuntime());
902 Stream writeStream = myOpenFile.getWriteStream();
904 if (myOpenFile.isWriteBuffered()) {
905 getRuntime().getWarnings().warn(
906 ID.SYSWRITE_BUFFERED_IO,
907 "syswrite for buffered IO");
910 if (!writeStream.getDescriptor().isWritable()) {
911 myOpenFile.checkClosed(getRuntime());
914 int read = writeStream.getDescriptor().write(string.getByteList());
916 if (read == -1) {
917 // TODO? I think this ends up propagating from normal Java exceptions
918 // sys_fail(openFile.getPath())
921 return getRuntime().newFixnum(read);
922 } catch (InvalidValueException ex) {
923 throw getRuntime().newErrnoEINVALError();
924 } catch (PipeException ex) {
925 throw getRuntime().newErrnoEPIPEError();
926 } catch (BadDescriptorException e) {
927 throw getRuntime().newErrnoEBADFError();
928 } catch (IOException e) {
929 e.printStackTrace();
930 throw getRuntime().newSystemCallError(e.getMessage());
934 @JRubyMethod(name = "write_nonblock", required = 1)
935 public IRubyObject write_nonblock(IRubyObject obj) {
936 // TODO: Obviously, we're not doing a non-blocking write here
937 return write(obj);
940 /** io_write
943 @JRubyMethod(name = "write", required = 1)
944 public IRubyObject write(IRubyObject obj) {
945 getRuntime().secure(4);
947 RubyString str = obj.asString();
949 // TODO: Ruby reuses this logic for other "write" behavior by checking if it's an IO and calling write again
951 if (str.getByteList().length() == 0) {
952 return getRuntime().newFixnum(0);
955 try {
956 OpenFile myOpenFile = getOpenFileChecked();
958 myOpenFile.checkWritable(getRuntime());
960 int written = fwrite(str.getByteList());
962 if (written == -1) {
963 // TODO: sys fail
966 // if not sync, we switch to write buffered mode
967 if (!myOpenFile.isSync()) {
968 myOpenFile.setWriteBuffered();
971 return getRuntime().newFixnum(written);
972 } catch (IOException ex) {
973 throw getRuntime().newIOErrorFromException(ex);
974 } catch (BadDescriptorException ex) {
975 throw getRuntime().newErrnoEBADFError();
976 } catch (InvalidValueException ex) {
977 throw getRuntime().newErrnoEINVALError();
978 } catch (PipeException ex) {
979 throw getRuntime().newErrnoEPIPEError();
983 protected int fwrite(ByteList buffer) {
984 int n, r, l, offset = 0;
985 Stream writeStream = openFile.getWriteStream();
987 int len = buffer.length();
989 // if ((n = len) <= 0) return n;
990 if (len == 0) return 0;
992 try {
993 if (openFile.isSync()) {
994 openFile.fflush(writeStream);
996 // TODO: why is this guarded?
997 // if (!rb_thread_fd_writable(fileno(f))) {
998 // rb_io_check_closed(fptr);
999 // }
1000 // TODO: loop until it's all written
1001 //while (offset < len) {
1002 writeStream.getDescriptor().write(buffer);
1004 return len;
1006 // TODO: all this stuff...some pipe logic, some async thread stuff
1007 // retry:
1008 // l = n;
1009 // if (PIPE_BUF < l &&
1010 // !rb_thread_critical &&
1011 // !rb_thread_alone() &&
1012 // wsplit_p(fptr)) {
1013 // l = PIPE_BUF;
1014 // }
1015 // TRAP_BEG;
1016 // r = write(fileno(f), RSTRING(str)->ptr+offset, l);
1017 // TRAP_END;
1018 // if (r == n) return len;
1019 // if (0 <= r) {
1020 // offset += r;
1021 // n -= r;
1022 // errno = EAGAIN;
1023 // }
1024 // if (rb_io_wait_writable(fileno(f))) {
1025 // rb_io_check_closed(fptr);
1026 // if (offset < RSTRING(str)->len)
1027 // goto retry;
1028 // }
1029 // return -1L;
1032 // TODO: handle errors in buffered write by retrying until finished or file is closed
1033 return writeStream.fwrite(buffer);
1034 // while (errno = 0, offset += (r = fwrite(RSTRING(str)->ptr+offset, 1, n, f)), (n -= r) > 0) {
1035 // if (ferror(f)
1036 // ) {
1037 // if (rb_io_wait_writable(fileno(f))) {
1038 // rb_io_check_closed(fptr);
1039 // clearerr(f);
1040 // if (offset < RSTRING(str)->len)
1041 // continue;
1042 // }
1043 // return -1L;
1044 // }
1045 // }
1047 // return len - n;
1048 } catch (IOException ex) {
1049 throw getRuntime().newIOErrorFromException(ex);
1050 } catch (BadDescriptorException ex) {
1051 throw getRuntime().newErrnoEBADFError();
1055 /** rb_io_addstr
1058 @JRubyMethod(name = "<<", required = 1)
1059 public IRubyObject op_append(IRubyObject anObject) {
1060 // Claims conversion is done via 'to_s' in docs.
1061 callMethod(getRuntime().getCurrentContext(), "write", anObject);
1063 return this;
1066 @JRubyMethod(name = "fileno", alias = "to_i")
1067 public RubyFixnum fileno() {
1068 return getRuntime().newFixnum(getOpenFileChecked().getMainStream().getDescriptor().getFileno());
1071 /** Returns the current line number.
1073 * @return the current line number.
1075 @JRubyMethod(name = "lineno")
1076 public RubyFixnum lineno() {
1077 return getRuntime().newFixnum(getOpenFileChecked().getLineNumber());
1080 /** Sets the current line number.
1082 * @param newLineNumber The new line number.
1084 @JRubyMethod(name = "lineno=", required = 1)
1085 public RubyFixnum lineno_set(IRubyObject newLineNumber) {
1086 getOpenFileChecked().setLineNumber(RubyNumeric.fix2int(newLineNumber));
1088 return getRuntime().newFixnum(getOpenFileChecked().getLineNumber());
1091 /** Returns the current sync mode.
1093 * @return the current sync mode.
1095 @JRubyMethod(name = "sync")
1096 public RubyBoolean sync() {
1097 return getRuntime().newBoolean(getOpenFileChecked().getMainStream().isSync());
1101 * <p>Return the process id (pid) of the process this IO object
1102 * spawned. If no process exists (popen was not called), then
1103 * nil is returned. This is not how it appears to be defined
1104 * but ruby 1.8 works this way.</p>
1106 * @return the pid or nil
1108 @JRubyMethod(name = "pid")
1109 public IRubyObject pid() {
1110 OpenFile myOpenFile = getOpenFileChecked();
1112 if (myOpenFile.getProcess() == null) {
1113 return getRuntime().getNil();
1116 // Of course this isn't particularly useful.
1117 int pid = myOpenFile.getProcess().hashCode();
1119 return getRuntime().newFixnum(pid);
1123 * @deprecated
1124 * @return
1126 public boolean writeDataBuffered() {
1127 return openFile.getMainStream().writeDataBuffered();
1130 @JRubyMethod(name = {"pos", "tell"})
1131 public RubyFixnum pos() {
1132 try {
1133 return getRuntime().newFixnum(getOpenFileChecked().getMainStream().fgetpos());
1134 } catch (InvalidValueException ex) {
1135 throw getRuntime().newErrnoEINVALError();
1136 } catch (BadDescriptorException bde) {
1137 throw getRuntime().newErrnoEBADFError();
1138 } catch (PipeException e) {
1139 throw getRuntime().newErrnoESPIPEError();
1140 } catch (IOException e) {
1141 throw getRuntime().newIOError(e.getMessage());
1145 @JRubyMethod(name = "pos=", required = 1)
1146 public RubyFixnum pos_set(IRubyObject newPosition) {
1147 long offset = RubyNumeric.num2long(newPosition);
1149 if (offset < 0) {
1150 throw getRuntime().newSystemCallError("Negative seek offset");
1153 OpenFile myOpenFile = getOpenFileChecked();
1155 try {
1156 myOpenFile.getMainStream().fseek(offset, Stream.SEEK_SET);
1157 } catch (BadDescriptorException e) {
1158 throw getRuntime().newErrnoEBADFError();
1159 } catch (InvalidValueException e) {
1160 throw getRuntime().newErrnoEINVALError();
1161 } catch (PipeException e) {
1162 throw getRuntime().newErrnoESPIPEError();
1163 } catch (IOException e) {
1164 throw getRuntime().newIOError(e.getMessage());
1167 myOpenFile.getMainStream().clearerr();
1169 return (RubyFixnum) newPosition;
1172 /** Print some objects to the stream.
1175 @JRubyMethod(name = "print", rest = true)
1176 public IRubyObject print(IRubyObject[] args) {
1177 if (args.length == 0) {
1178 args = new IRubyObject[] { getRuntime().getCurrentContext().getCurrentFrame().getLastLine() };
1181 IRubyObject fs = getRuntime().getGlobalVariables().get("$,");
1182 IRubyObject rs = getRuntime().getGlobalVariables().get("$\\");
1183 ThreadContext context = getRuntime().getCurrentContext();
1185 for (int i = 0; i < args.length; i++) {
1186 if (i > 0 && !fs.isNil()) {
1187 callMethod(context, "write", fs);
1189 if (args[i].isNil()) {
1190 callMethod(context, "write", getRuntime().newString("nil"));
1191 } else {
1192 callMethod(context, "write", args[i]);
1195 if (!rs.isNil()) {
1196 callMethod(context, "write", rs);
1199 return getRuntime().getNil();
1202 @JRubyMethod(name = "printf", required = 1, rest = true)
1203 public IRubyObject printf(IRubyObject[] args) {
1204 callMethod(getRuntime().getCurrentContext(), "write", RubyKernel.sprintf(this, args));
1205 return getRuntime().getNil();
1208 @JRubyMethod(name = "putc", required = 1)
1209 public IRubyObject putc(IRubyObject object) {
1210 int c;
1212 if (getRuntime().getString().isInstance(object)) {
1213 String value = ((RubyString) object).toString();
1215 if (value.length() > 0) {
1216 c = value.charAt(0);
1217 } else {
1218 throw getRuntime().newTypeError("Cannot convert String to Integer");
1220 } else if (getRuntime().getFixnum().isInstance(object)){
1221 c = RubyNumeric.fix2int(object);
1222 } else { // What case will this work for?
1223 c = RubyNumeric.fix2int(object.callMethod(getRuntime().getCurrentContext(), MethodIndex.TO_I, "to_i"));
1226 try {
1227 getOpenFileChecked().getMainStream().fputc(c);
1228 } catch (BadDescriptorException e) {
1229 return RubyFixnum.zero(getRuntime());
1230 } catch (IOException e) {
1231 return RubyFixnum.zero(getRuntime());
1234 return object;
1237 // This was a getOpt with one mandatory arg, but it did not work
1238 // so I am parsing it for now.
1239 @JRubyMethod(name = "seek", required = 1, optional = 1)
1240 public RubyFixnum seek(IRubyObject[] args) {
1241 long offset = RubyNumeric.num2long(args[0]);
1242 int whence = Stream.SEEK_SET;
1244 if (args.length > 1) {
1245 whence = RubyNumeric.fix2int(args[1].convertToInteger());
1248 OpenFile myOpenFile = getOpenFileChecked();
1250 try {
1251 myOpenFile.seek(offset, whence);
1252 } catch (BadDescriptorException ex) {
1253 throw getRuntime().newErrnoEBADFError();
1254 } catch (InvalidValueException e) {
1255 throw getRuntime().newErrnoEINVALError();
1256 } catch (PipeException e) {
1257 throw getRuntime().newErrnoESPIPEError();
1258 } catch (IOException e) {
1259 throw getRuntime().newIOError(e.getMessage());
1262 myOpenFile.getMainStream().clearerr();
1264 return RubyFixnum.zero(getRuntime());
1267 // This was a getOpt with one mandatory arg, but it did not work
1268 // so I am parsing it for now.
1269 @JRubyMethod(name = "sysseek", required = 1, optional = 1)
1270 public RubyFixnum sysseek(IRubyObject[] args) {
1271 long offset = RubyNumeric.num2long(args[0]);
1272 long pos;
1273 int whence = Stream.SEEK_SET;
1275 if (args.length > 1) {
1276 whence = RubyNumeric.fix2int(args[1].convertToInteger());
1279 OpenFile myOpenFile = getOpenFileChecked();
1281 try {
1283 if (myOpenFile.isReadable() && myOpenFile.isReadBuffered()) {
1284 throw getRuntime().newIOError("sysseek for buffered IO");
1286 if (myOpenFile.isWritable() && myOpenFile.isWriteBuffered()) {
1287 getRuntime().getWarnings().warn(
1288 ID.SYSSEEK_BUFFERED_IO,
1289 "sysseek for buffered IO");
1292 pos = myOpenFile.getMainStream().getDescriptor().lseek(offset, whence);
1293 } catch (BadDescriptorException ex) {
1294 throw getRuntime().newErrnoEBADFError();
1295 } catch (InvalidValueException e) {
1296 throw getRuntime().newErrnoEINVALError();
1297 } catch (PipeException e) {
1298 throw getRuntime().newErrnoESPIPEError();
1299 } catch (IOException e) {
1300 throw getRuntime().newIOError(e.getMessage());
1303 myOpenFile.getMainStream().clearerr();
1305 return getRuntime().newFixnum(pos);
1308 @JRubyMethod(name = "rewind")
1309 public RubyFixnum rewind() {
1310 OpenFile myOpenfile = getOpenFileChecked();
1312 try {
1313 myOpenfile.getMainStream().fseek(0L, Stream.SEEK_SET);
1314 myOpenfile.getMainStream().clearerr();
1316 // TODO: This is some goofy global file value from MRI..what to do?
1317 // if (io == current_file) {
1318 // gets_lineno -= fptr->lineno;
1319 // }
1320 } catch (BadDescriptorException e) {
1321 throw getRuntime().newErrnoEBADFError();
1322 } catch (InvalidValueException e) {
1323 throw getRuntime().newErrnoEINVALError();
1324 } catch (PipeException e) {
1325 throw getRuntime().newErrnoESPIPEError();
1326 } catch (IOException e) {
1327 throw getRuntime().newIOError(e.getMessage());
1330 // Must be back on first line on rewind.
1331 myOpenfile.setLineNumber(0);
1333 return RubyFixnum.zero(getRuntime());
1336 @JRubyMethod(name = "fsync")
1337 public RubyFixnum fsync() {
1338 try {
1339 OpenFile myOpenFile = getOpenFileChecked();
1341 myOpenFile.checkWritable(getRuntime());
1343 myOpenFile.getWriteStream().sync();
1344 } catch (InvalidValueException ex) {
1345 throw getRuntime().newErrnoEINVALError();
1346 } catch (PipeException ex) {
1347 throw getRuntime().newErrnoEPIPEError();
1348 } catch (IOException e) {
1349 throw getRuntime().newIOError(e.getMessage());
1350 } catch (BadDescriptorException e) {
1351 throw getRuntime().newErrnoEBADFError();
1354 return RubyFixnum.zero(getRuntime());
1357 /** Sets the current sync mode.
1359 * @param newSync The new sync mode.
1361 @JRubyMethod(name = "sync=", required = 1)
1362 public IRubyObject sync_set(IRubyObject newSync) {
1363 getOpenFileChecked().setSync(newSync.isTrue());
1364 getOpenFileChecked().getMainStream().setSync(newSync.isTrue());
1366 return this;
1369 @JRubyMethod(name = {"eof?", "eof"})
1370 public RubyBoolean eof_p() {
1371 try {
1372 OpenFile myOpenFile = getOpenFileChecked();
1374 myOpenFile.checkReadable(getRuntime());
1376 if (myOpenFile.getMainStream().feof()) {
1377 return getRuntime().getTrue();
1380 if (myOpenFile.getMainStream().readDataBuffered()) {
1381 return getRuntime().getFalse();
1384 readCheck(myOpenFile.getMainStream());
1386 myOpenFile.getMainStream().clearerr();
1388 int c = myOpenFile.getMainStream().fgetc();
1390 if (c != -1) {
1391 myOpenFile.getMainStream().ungetc(c);
1392 return getRuntime().getFalse();
1395 myOpenFile.checkClosed(getRuntime());
1397 myOpenFile.getMainStream().clearerr();
1399 return getRuntime().getTrue();
1400 } catch (PipeException ex) {
1401 throw getRuntime().newErrnoEPIPEError();
1402 } catch (InvalidValueException ex) {
1403 throw getRuntime().newErrnoEINVALError();
1404 } catch (BadDescriptorException e) {
1405 throw getRuntime().newErrnoEBADFError();
1406 } catch (IOException e) {
1407 throw getRuntime().newIOError(e.getMessage());
1411 @JRubyMethod(name = {"tty?", "isatty"})
1412 public RubyBoolean tty_p() {
1413 return getRuntime().newBoolean(getRuntime().getPosix().isatty(getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor()));
1416 @JRubyMethod(name = "initialize_copy", required = 1)
1417 @Override
1418 public IRubyObject initialize_copy(IRubyObject original){
1419 if (this == original) return this;
1421 RubyIO originalIO = (RubyIO) TypeConverter.convertToTypeWithCheck(original, getRuntime().getIO(), MethodIndex.TO_IO, "to_io");
1423 OpenFile originalFile = originalIO.getOpenFileChecked();
1424 OpenFile newFile = openFile;
1426 try {
1427 // TODO: I didn't see where MRI has this check, but it seems to be the right place
1428 originalFile.checkClosed(getRuntime());
1430 if (originalFile.getPipeStream() != null) {
1431 originalFile.getPipeStream().fflush();
1432 originalFile.getMainStream().fseek(0, Stream.SEEK_CUR);
1433 } else if (originalFile.isWritable()) {
1434 originalFile.getMainStream().fflush();
1435 } else {
1436 originalFile.getMainStream().fseek(0, Stream.SEEK_CUR);
1439 newFile.setMode(originalFile.getMode());
1440 newFile.setProcess(originalFile.getProcess());
1441 newFile.setLineNumber(originalFile.getLineNumber());
1442 newFile.setPath(originalFile.getPath());
1443 newFile.setFinalizer(originalFile.getFinalizer());
1445 ModeFlags modes;
1446 if (newFile.isReadable()) {
1447 if (newFile.isWritable()) {
1448 if (newFile.getPipeStream() != null) {
1449 modes = new ModeFlags(ModeFlags.RDONLY);
1450 } else {
1451 modes = new ModeFlags(ModeFlags.RDWR);
1453 } else {
1454 modes = new ModeFlags(ModeFlags.RDONLY);
1456 } else {
1457 if (newFile.isWritable()) {
1458 modes = new ModeFlags(ModeFlags.WRONLY);
1459 } else {
1460 modes = originalFile.getMainStream().getModes();
1464 ChannelDescriptor descriptor = originalFile.getMainStream().getDescriptor().dup();
1466 newFile.setMainStream(ChannelStream.fdopen(getRuntime(), descriptor, modes));
1468 // TODO: the rest of this...seeking to same position is unnecessary since we share a channel
1469 // but some of this may be needed?
1471 // fseeko(fptr->f, ftello(orig->f), SEEK_SET);
1472 // if (orig->f2) {
1473 // if (fileno(orig->f) != fileno(orig->f2)) {
1474 // fd = ruby_dup(fileno(orig->f2));
1475 // }
1476 // fptr->f2 = rb_fdopen(fd, "w");
1477 // fseeko(fptr->f2, ftello(orig->f2), SEEK_SET);
1478 // }
1479 // if (fptr->mode & FMODE_BINMODE) {
1480 // rb_io_binmode(dest);
1481 // }
1483 // Register the new descriptor
1484 registerDescriptor(newFile.getMainStream().getDescriptor());
1485 } catch (IOException ex) {
1486 throw getRuntime().newIOError("could not init copy: " + ex);
1487 } catch (BadDescriptorException ex) {
1488 throw getRuntime().newIOError("could not init copy: " + ex);
1489 } catch (PipeException ex) {
1490 throw getRuntime().newIOError("could not init copy: " + ex);
1491 } catch (InvalidValueException ex) {
1492 throw getRuntime().newIOError("could not init copy: " + ex);
1495 return this;
1498 /** Closes the IO.
1500 * @return The IO.
1502 @JRubyMethod(name = "closed?")
1503 public RubyBoolean closed_p() {
1504 if (openFile.getMainStream() == null && openFile.getPipeStream() == null) {
1505 return getRuntime().getTrue();
1506 } else {
1507 return getRuntime().getFalse();
1511 /**
1512 * <p>Closes all open resources for the IO. It also removes
1513 * it from our magical all open file descriptor pool.</p>
1515 * @return The IO.
1517 @JRubyMethod(name = "close")
1518 public IRubyObject close() {
1519 if (getRuntime().getSafeLevel() >= 4 && isTaint()) {
1520 throw getRuntime().newSecurityError("Insecure: can't close");
1523 openFile.checkClosed(getRuntime());
1524 return close2();
1527 protected IRubyObject close2() {
1528 if (openFile == null) {
1529 return getRuntime().getNil();
1532 // These would be used when we notify threads...if we notify threads
1533 ChannelDescriptor main, pipe;
1534 if (openFile.getPipeStream() != null) {
1535 pipe = openFile.getPipeStream().getDescriptor();
1536 } else {
1537 if (openFile.getMainStream() == null) {
1538 return getRuntime().getNil();
1540 pipe = null;
1543 main = openFile.getMainStream().getDescriptor();
1545 // cleanup, raising errors if any
1546 openFile.cleanup(getRuntime(), true);
1548 // TODO: notify threads waiting on descriptors/IO? probably not...
1550 if (openFile.getProcess() != null) {
1551 try {
1552 IRubyObject processResult = RubyProcess.RubyStatus.newProcessStatus(getRuntime(), openFile.getProcess().waitFor());
1553 getRuntime().getGlobalVariables().set("$?", processResult);
1554 } catch (InterruptedException ie) {
1555 // TODO: do something here?
1559 return getRuntime().getNil();
1562 @JRubyMethod(name = "close_write")
1563 public IRubyObject close_write() throws BadDescriptorException {
1564 try {
1565 if (getRuntime().getSafeLevel() >= 4 && isTaint()) {
1566 throw getRuntime().newSecurityError("Insecure: can't close");
1569 OpenFile myOpenFile = getOpenFileChecked();
1571 if (myOpenFile.getPipeStream() == null && myOpenFile.isReadable()) {
1572 throw getRuntime().newIOError("closing non-duplex IO for writing");
1575 if (myOpenFile.getPipeStream() == null) {
1576 close();
1577 } else{
1578 myOpenFile.getPipeStream().fclose();
1579 myOpenFile.setPipeStream(null);
1580 myOpenFile.setMode(myOpenFile.getMode() & ~OpenFile.WRITABLE);
1581 // TODO
1582 // n is result of fclose; but perhaps having a SysError below is enough?
1583 // if (n != 0) rb_sys_fail(fptr->path);
1585 } catch (IOException ioe) {
1586 // hmmmm
1588 return this;
1591 @JRubyMethod(name = "close_read")
1592 public IRubyObject close_read() throws BadDescriptorException {
1593 try {
1594 if (getRuntime().getSafeLevel() >= 4 && isTaint()) {
1595 throw getRuntime().newSecurityError("Insecure: can't close");
1598 OpenFile myOpenFile = getOpenFileChecked();
1600 if (myOpenFile.getPipeStream() == null && myOpenFile.isWritable()) {
1601 throw getRuntime().newIOError("closing non-duplex IO for reading");
1604 if (myOpenFile.getPipeStream() == null) {
1605 close();
1606 } else{
1607 myOpenFile.getMainStream().fclose();
1608 myOpenFile.setMode(myOpenFile.getMode() & ~OpenFile.READABLE);
1609 myOpenFile.setMainStream(myOpenFile.getPipeStream());
1610 myOpenFile.setPipeStream(null);
1611 // TODO
1612 // n is result of fclose; but perhaps having a SysError below is enough?
1613 // if (n != 0) rb_sys_fail(fptr->path);
1615 } catch (IOException ioe) {
1616 // I believe Ruby bails out with a "bug" if closing fails
1617 throw getRuntime().newIOErrorFromException(ioe);
1619 return this;
1622 /** Flushes the IO output stream.
1624 * @return The IO.
1626 @JRubyMethod(name = "flush")
1627 public RubyIO flush() {
1628 try {
1629 getOpenFileChecked().getWriteStream().fflush();
1630 } catch (BadDescriptorException e) {
1631 throw getRuntime().newErrnoEBADFError();
1632 } catch (IOException e) {
1633 throw getRuntime().newIOError(e.getMessage());
1636 return this;
1639 /** Read a line.
1642 @JRubyMethod(name = "gets", optional = 1)
1643 public IRubyObject gets(IRubyObject[] args) {
1644 ByteList separator = getSeparatorForGets(args);
1646 IRubyObject result = getline(separator);
1648 if (!result.isNil()) getRuntime().getCurrentContext().getCurrentFrame().setLastLine(result);
1650 return result;
1653 public boolean getBlocking() {
1654 return ((ChannelStream) openFile.getMainStream()).isBlocking();
1657 @JRubyMethod(name = "fcntl", required = 2)
1658 public IRubyObject fcntl(IRubyObject cmd, IRubyObject arg) {
1659 // TODO: This version differs from ioctl by checking whether fcntl exists
1660 // and raising notimplemented if it doesn't; perhaps no difference for us?
1661 return ctl(cmd, arg);
1664 @JRubyMethod(name = "ioctl", required = 1, optional = 1)
1665 public IRubyObject ioctl(IRubyObject[] args) {
1666 IRubyObject cmd = args[0];
1667 IRubyObject arg;
1669 if (args.length == 2) {
1670 arg = args[1];
1671 } else {
1672 arg = getRuntime().getNil();
1675 return ctl(cmd, arg);
1678 public IRubyObject ctl(IRubyObject cmd, IRubyObject arg) {
1679 long realCmd = cmd.convertToInteger().getLongValue();
1680 long nArg = 0;
1682 // FIXME: Arg may also be true, false, and nil and still be valid. Strangely enough,
1683 // protocol conversion is not happening in Ruby on this arg?
1684 if (arg.isNil() || arg == getRuntime().getFalse()) {
1685 nArg = 0;
1686 } else if (arg instanceof RubyFixnum) {
1687 nArg = RubyFixnum.fix2long(arg);
1688 } else if (arg == getRuntime().getTrue()) {
1689 nArg = 1;
1690 } else {
1691 throw getRuntime().newNotImplementedError("JRuby does not support string for second fcntl/ioctl argument yet");
1694 OpenFile myOpenFile = getOpenFileChecked();
1696 // Fixme: Only F_SETFL is current supported
1697 if (realCmd == 1L) { // cmd is F_SETFL
1698 boolean block = true;
1700 if ((nArg & ModeFlags.NONBLOCK) == ModeFlags.NONBLOCK) {
1701 block = false;
1704 try {
1705 myOpenFile.getMainStream().setBlocking(block);
1706 } catch (IOException e) {
1707 throw getRuntime().newIOError(e.getMessage());
1709 } else {
1710 throw getRuntime().newNotImplementedError("JRuby only supports F_SETFL for fcntl/ioctl currently");
1713 return getRuntime().newFixnum(0);
1716 private static final ByteList NIL_BYTELIST = ByteList.create("nil");
1717 private static final ByteList RECURSIVE_BYTELIST = ByteList.create("[...]");
1719 @JRubyMethod(name = "puts", rest = true)
1720 public IRubyObject puts(IRubyObject[] args) {
1721 ThreadContext context = getRuntime().getCurrentContext();
1723 assert getRuntime().getGlobalVariables().getDefaultSeparator() instanceof RubyString;
1724 RubyString separator = (RubyString)getRuntime().getGlobalVariables().getDefaultSeparator();
1726 if (args.length == 0) {
1727 write(context, separator.getByteList());
1728 return getRuntime().getNil();
1731 for (int i = 0; i < args.length; i++) {
1732 ByteList line;
1734 if (args[i].isNil()) {
1735 line = NIL_BYTELIST;
1736 } else if (getRuntime().isInspecting(args[i])) {
1737 line = RECURSIVE_BYTELIST;
1738 } else if (args[i] instanceof RubyArray) {
1739 inspectPuts((RubyArray) args[i]);
1740 continue;
1741 } else {
1742 line = args[i].asString().getByteList();
1745 write(context, line);
1747 if (line.length() == 0 || !line.endsWith(separator.getByteList())) {
1748 write(context, separator.getByteList());
1751 return getRuntime().getNil();
1754 protected void write(ThreadContext context, ByteList byteList) {
1755 callMethod(context, "write", getRuntime().newStringShared(byteList));
1758 private IRubyObject inspectPuts(RubyArray array) {
1759 try {
1760 getRuntime().registerInspecting(array);
1761 return puts(array.toJavaArray());
1762 } finally {
1763 getRuntime().unregisterInspecting(array);
1767 /** Read a line.
1770 @JRubyMethod(name = "readline", optional = 1)
1771 public IRubyObject readline(IRubyObject[] args) {
1772 IRubyObject line = gets(args);
1774 if (line.isNil()) {
1775 throw getRuntime().newEOFError();
1778 return line;
1781 /** Read a byte. On EOF returns nil.
1784 @JRubyMethod(name = "getc")
1785 public IRubyObject getc() {
1786 try {
1787 OpenFile myOpenFile = getOpenFileChecked();
1789 myOpenFile.checkReadable(getRuntime());
1791 Stream stream = myOpenFile.getMainStream();
1793 readCheck(stream);
1794 stream.clearerr();
1796 int c = myOpenFile.getMainStream().fgetc();
1798 if (c == -1) {
1799 // TODO: check for ferror, clear it, and try once more up above readCheck
1800 // if (ferror(f)) {
1801 // clearerr(f);
1802 // if (!rb_io_wait_readable(fileno(f)))
1803 // rb_sys_fail(fptr->path);
1804 // goto retry;
1805 // }
1806 return getRuntime().getNil();
1809 return getRuntime().newFixnum(c);
1810 } catch (PipeException ex) {
1811 throw getRuntime().newErrnoEPIPEError();
1812 } catch (InvalidValueException ex) {
1813 throw getRuntime().newErrnoEINVALError();
1814 } catch (BadDescriptorException e) {
1815 throw getRuntime().newErrnoEBADFError();
1816 } catch (EOFException e) {
1817 throw getRuntime().newEOFError();
1818 } catch (IOException e) {
1819 throw getRuntime().newIOError(e.getMessage());
1823 private void readCheck(Stream stream) {
1824 if (!stream.readDataBuffered()) {
1825 openFile.checkClosed(getRuntime());
1829 /**
1830 * <p>Pushes char represented by int back onto IOS.</p>
1832 * @param number to push back
1834 @JRubyMethod(name = "ungetc", required = 1)
1835 public IRubyObject ungetc(IRubyObject number) {
1836 int ch = RubyNumeric.fix2int(number);
1838 OpenFile myOpenFile = getOpenFileChecked();
1840 if (!myOpenFile.isReadBuffered()) {
1841 throw getRuntime().newIOError("unread stream");
1844 try {
1845 myOpenFile.checkReadable(getRuntime());
1847 if (myOpenFile.getMainStream().ungetc(ch) == -1 && ch != -1) {
1848 throw getRuntime().newIOError("ungetc failed");
1850 } catch (PipeException ex) {
1851 throw getRuntime().newErrnoEPIPEError();
1852 } catch (InvalidValueException ex) {
1853 throw getRuntime().newErrnoEINVALError();
1854 } catch (BadDescriptorException e) {
1855 throw getRuntime().newErrnoEBADFError();
1856 } catch (EOFException e) {
1857 throw getRuntime().newEOFError();
1858 } catch (IOException e) {
1859 throw getRuntime().newIOError(e.getMessage());
1862 return getRuntime().getNil();
1865 @JRubyMethod(name = "readpartial", required = 1, optional = 1)
1866 public IRubyObject readpartial(IRubyObject[] args) {
1867 if(!(openFile.getMainStream() instanceof ChannelStream)) {
1868 // cryptic for the uninitiated...
1869 throw getRuntime().newNotImplementedError("readpartial only works with Nio based handlers");
1871 try {
1872 ByteList buf = ((ChannelStream)openFile.getMainStream()).readpartial(RubyNumeric.fix2int(args[0]));
1873 IRubyObject strbuf = RubyString.newString(getRuntime(), buf == null ? new ByteList(ByteList.NULL_ARRAY) : buf);
1874 if(args.length > 1) {
1875 args[1].callMethod(getRuntime().getCurrentContext(),MethodIndex.OP_LSHIFT, "<<", strbuf);
1876 return args[1];
1879 return strbuf;
1880 } catch (BadDescriptorException e) {
1881 throw getRuntime().newErrnoEBADFError();
1882 } catch (EOFException e) {
1883 return getRuntime().getNil();
1884 } catch (IOException e) {
1885 throw getRuntime().newIOError(e.getMessage());
1889 @JRubyMethod(name = "sysread", required = 1, optional = 1)
1890 public IRubyObject sysread(IRubyObject[] args) {
1891 int len = (int)RubyNumeric.num2long(args[0]);
1892 if (len < 0) throw getRuntime().newArgumentError("Negative size");
1894 try {
1895 RubyString str;
1896 ByteList buffer;
1897 if (args.length == 1 || args[1].isNil()) {
1898 if (len == 0) {
1899 return RubyString.newStringShared(getRuntime(), ByteList.EMPTY_BYTELIST);
1902 buffer = new ByteList(len);
1903 str = RubyString.newString(getRuntime(), buffer);
1904 } else {
1905 str = args[1].convertToString();
1906 str.modify(len);
1908 if (len == 0) {
1909 return str;
1912 buffer = str.getByteList();
1915 OpenFile myOpenFile = getOpenFileChecked();
1917 myOpenFile.checkReadable(getRuntime());
1919 if (myOpenFile.getMainStream().readDataBuffered()) {
1920 throw getRuntime().newIOError("sysread for buffered IO");
1923 // TODO: Ruby locks the string here
1925 getRuntime().getCurrentContext().getThread().beforeBlockingCall();
1926 myOpenFile.checkClosed(getRuntime());
1928 // TODO: Ruby re-checks that the buffer string hasn't been modified
1930 int bytesRead = myOpenFile.getMainStream().getDescriptor().read(len, str.getByteList());
1932 // TODO: Ruby unlocks the string here
1934 // TODO: Ruby truncates string to specific size here, but our bytelist should handle this already?
1936 if (bytesRead == -1 || (bytesRead == 0 && len > 0)) {
1937 throw getRuntime().newEOFError();
1940 str.setTaint(true);
1942 return str;
1943 } catch (BadDescriptorException e) {
1944 throw getRuntime().newErrnoEBADFError();
1945 } catch (InvalidValueException e) {
1946 throw getRuntime().newErrnoEINVALError();
1947 } catch (PipeException e) {
1948 throw getRuntime().newErrnoEPIPEError();
1949 } catch (EOFException e) {
1950 throw getRuntime().newEOFError();
1951 } catch (IOException e) {
1952 // All errors to sysread should be SystemCallErrors, but on a closed stream
1953 // Ruby returns an IOError. Java throws same exception for all errors so
1954 // we resort to this hack...
1955 if ("File not open".equals(e.getMessage())) {
1956 throw getRuntime().newIOError(e.getMessage());
1958 throw getRuntime().newSystemCallError(e.getMessage());
1959 } finally {
1960 getRuntime().getCurrentContext().getThread().afterBlockingCall();
1964 @JRubyMethod(name = "read_nonblock", required = 1, optional = 1)
1965 public IRubyObject read_nonblock(IRubyObject[] args) {
1966 // TODO: Obviously, we're not doing a nonblocking read here...
1967 return read(args);
1970 @JRubyMethod(name = "read", optional = 2)
1971 public IRubyObject read(IRubyObject[] args) {
1972 int argCount = args.length;
1974 OpenFile myOpenFile = getOpenFileChecked();
1976 if (argCount == 0 || args[0].isNil()) {
1977 try {
1978 myOpenFile.checkReadable(getRuntime());
1980 if (args.length == 2) {
1981 return readAll(args[1]);
1982 } else {
1983 return readAll(getRuntime().getNil());
1985 } catch (PipeException ex) {
1986 throw getRuntime().newErrnoEPIPEError();
1987 } catch (InvalidValueException ex) {
1988 throw getRuntime().newErrnoEINVALError();
1989 } catch (EOFException ex) {
1990 throw getRuntime().newEOFError();
1991 } catch (IOException ex) {
1992 throw getRuntime().newIOErrorFromException(ex);
1993 } catch (BadDescriptorException ex) {
1994 throw getRuntime().newErrnoEBADFError();
1998 int length = RubyNumeric.num2int(args[0]);
2000 if (length < 0) {
2001 throw getRuntime().newArgumentError("negative length " + length + " given");
2004 RubyString str = null;
2005 // ByteList buffer = null;
2006 if (args.length == 1 || args[1].isNil()) {
2007 // buffer = new ByteList(length);
2008 // str = RubyString.newString(getRuntime(), buffer);
2009 } else {
2010 str = args[1].convertToString();
2011 str.modify(length);
2013 if (length == 0) {
2014 return str;
2017 // buffer = str.getByteList();
2020 try {
2021 myOpenFile.checkReadable(getRuntime());
2023 if (myOpenFile.getMainStream().feof()) {
2024 return getRuntime().getNil();
2027 // TODO: Ruby locks the string here
2029 // READ_CHECK from MRI io.c
2030 readCheck(myOpenFile.getMainStream());
2032 // TODO: check buffer length again?
2033 // if (RSTRING(str)->len != len) {
2034 // rb_raise(rb_eRuntimeError, "buffer string modified");
2035 // }
2037 // TODO: read into buffer using all the fread logic
2038 // int read = openFile.getMainStream().fread(buffer);
2039 ByteList newBuffer = myOpenFile.getMainStream().fread(length);
2041 // TODO: Ruby unlocks the string here
2043 // TODO: change this to check number read into buffer once that's working
2044 // if (read == 0) {
2046 if (newBuffer == null || newBuffer.length() == 0) {
2047 if (myOpenFile.getMainStream() == null) {
2048 return getRuntime().getNil();
2051 if (myOpenFile.getMainStream().feof()) {
2052 // truncate buffer string to zero, if provided
2053 if (str != null) {
2054 str.setValue(ByteList.EMPTY_BYTELIST.dup());
2057 return getRuntime().getNil();
2060 if (length > 0) {
2061 // I think this is only partly correct; sys fail based on errno in Ruby
2062 throw getRuntime().newEOFError();
2067 // TODO: Ruby truncates string to specific size here, but our bytelist should handle this already?
2069 // FIXME: I don't like the null checks here
2070 if (str == null) {
2071 if (newBuffer == null) {
2072 str = getRuntime().newString();
2073 } else {
2074 str = RubyString.newString(getRuntime(), newBuffer);
2076 } else {
2077 if (newBuffer == null) {
2078 str.setValue(ByteList.EMPTY_BYTELIST.dup());
2079 } else {
2080 str.setValue(newBuffer);
2083 str.setTaint(true);
2085 return str;
2086 } catch (EOFException ex) {
2087 throw getRuntime().newEOFError();
2088 } catch (PipeException ex) {
2089 throw getRuntime().newErrnoEPIPEError();
2090 } catch (InvalidValueException ex) {
2091 throw getRuntime().newErrnoEINVALError();
2092 } catch (IOException ex) {
2093 throw getRuntime().newIOErrorFromException(ex);
2094 } catch (BadDescriptorException ex) {
2095 throw getRuntime().newErrnoEBADFError();
2099 protected IRubyObject readAll(IRubyObject buffer) throws BadDescriptorException, EOFException, IOException {
2100 // TODO: handle writing into original buffer better
2102 RubyString str = null;
2103 if (buffer instanceof RubyString) {
2104 str = (RubyString)buffer;
2107 // TODO: ruby locks the string here
2109 // READ_CHECK from MRI io.c
2110 if (openFile.getMainStream().readDataBuffered()) {
2111 openFile.checkClosed(getRuntime());
2114 ByteList newBuffer = openFile.getMainStream().readall();
2116 // TODO same zero-length checks as file above
2118 if (str == null) {
2119 if (newBuffer == null) {
2120 str = RubyString.newStringShared(getRuntime(), ByteList.EMPTY_BYTELIST);
2121 } else {
2122 str = RubyString.newString(getRuntime(), newBuffer);
2124 } else {
2125 if (newBuffer == null) {
2126 str.setValue(ByteList.EMPTY_BYTELIST.dup());
2127 } else {
2128 str.setValue(newBuffer);
2132 str.taint();
2134 return str;
2135 // long bytes = 0;
2136 // long n;
2138 // if (siz == 0) siz = BUFSIZ;
2139 // if (NIL_P(str)) {
2140 // str = rb_str_new(0, siz);
2141 // }
2142 // else {
2143 // rb_str_resize(str, siz);
2144 // }
2145 // for (;;) {
2146 // rb_str_locktmp(str);
2147 // READ_CHECK(fptr->f);
2148 // n = io_fread(RSTRING(str)->ptr+bytes, siz-bytes, fptr);
2149 // rb_str_unlocktmp(str);
2150 // if (n == 0 && bytes == 0) {
2151 // if (!fptr->f) break;
2152 // if (feof(fptr->f)) break;
2153 // if (!ferror(fptr->f)) break;
2154 // rb_sys_fail(fptr->path);
2155 // }
2156 // bytes += n;
2157 // if (bytes < siz) break;
2158 // siz += BUFSIZ;
2159 // rb_str_resize(str, siz);
2160 // }
2161 // if (bytes != siz) rb_str_resize(str, bytes);
2162 // OBJ_TAINT(str);
2164 // return str;
2167 // TODO: There's a lot of complexity here due to error handling and
2168 // nonblocking IO; much of this goes away, but for now I'm just
2169 // having read call ChannelStream.fread directly.
2170 // protected int fread(int len, ByteList buffer) {
2171 // long n = len;
2172 // int c;
2173 // int saved_errno;
2175 // while (n > 0) {
2176 // c = read_buffered_data(ptr, n, fptr->f);
2177 // if (c < 0) goto eof;
2178 // if (c > 0) {
2179 // ptr += c;
2180 // if ((n -= c) <= 0) break;
2181 // }
2182 // rb_thread_wait_fd(fileno(fptr->f));
2183 // rb_io_check_closed(fptr);
2184 // clearerr(fptr->f);
2185 // TRAP_BEG;
2186 // c = getc(fptr->f);
2187 // TRAP_END;
2188 // if (c == EOF) {
2189 // eof:
2190 // if (ferror(fptr->f)) {
2191 // switch (errno) {
2192 // case EINTR:
2193 // #if defined(ERESTART)
2194 // case ERESTART:
2195 // #endif
2196 // clearerr(fptr->f);
2197 // continue;
2198 // case EAGAIN:
2199 // #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
2200 // case EWOULDBLOCK:
2201 // #endif
2202 // if (len > n) {
2203 // clearerr(fptr->f);
2204 // }
2205 // saved_errno = errno;
2206 // rb_warning("nonblocking IO#read is obsolete; use IO#readpartial or IO#sysread");
2207 // errno = saved_errno;
2208 // }
2209 // if (len == n) return 0;
2210 // }
2211 // break;
2212 // }
2213 // *ptr++ = c;
2214 // n--;
2215 // }
2216 // return len - n;
2218 // }
2220 /** Read a byte. On EOF throw EOFError.
2223 @JRubyMethod(name = "readchar")
2224 public IRubyObject readchar() {
2225 IRubyObject c = getc();
2227 if (c.isNil()) throw getRuntime().newEOFError();
2229 return c;
2232 @JRubyMethod
2233 public IRubyObject stat() {
2234 openFile.checkClosed(getRuntime());
2235 return getRuntime().newFileStat(getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor());
2238 /**
2239 * <p>Invoke a block for each byte.</p>
2241 @JRubyMethod(name = "each_byte", frame = true)
2242 public IRubyObject each_byte(Block block) {
2243 try {
2244 Ruby runtime = getRuntime();
2245 ThreadContext context = runtime.getCurrentContext();
2246 OpenFile myOpenFile = getOpenFileChecked();
2248 while (true) {
2249 myOpenFile.checkReadable(runtime);
2251 // TODO: READ_CHECK from MRI
2253 int c = myOpenFile.getMainStream().fgetc();
2255 if (c == -1) {
2256 // TODO: check for error, clear it, and wait until readable before trying once more
2257 // if (ferror(f)) {
2258 // clearerr(f);
2259 // if (!rb_io_wait_readable(fileno(f)))
2260 // rb_sys_fail(fptr->path);
2261 // continue;
2262 // }
2263 break;
2266 assert c < 256;
2267 block.yield(context, getRuntime().newFixnum(c));
2270 // TODO: one more check for error
2271 // if (ferror(f)) rb_sys_fail(fptr->path);
2272 return this;
2273 } catch (PipeException ex) {
2274 throw getRuntime().newErrnoEPIPEError();
2275 } catch (InvalidValueException ex) {
2276 throw getRuntime().newErrnoEINVALError();
2277 } catch (BadDescriptorException e) {
2278 throw getRuntime().newErrnoEBADFError();
2279 } catch (EOFException e) {
2280 return getRuntime().getNil();
2281 } catch (IOException e) {
2282 throw getRuntime().newIOError(e.getMessage());
2286 /**
2287 * <p>Invoke a block for each line.</p>
2289 @JRubyMethod(name = {"each_line", "each"}, optional = 1, frame = true)
2290 public RubyIO each_line(IRubyObject[] args, Block block) {
2291 ThreadContext context = getRuntime().getCurrentContext();
2292 ByteList separator = getSeparatorForGets(args);
2294 for (IRubyObject line = getline(separator); !line.isNil();
2295 line = getline(separator)) {
2296 block.yield(context, line);
2299 return this;
2303 @JRubyMethod(name = "readlines", optional = 1)
2304 public RubyArray readlines(IRubyObject[] args) {
2305 ByteList separator;
2306 if (args.length > 0) {
2307 if (!getRuntime().getNilClass().isInstance(args[0]) &&
2308 !getRuntime().getString().isInstance(args[0])) {
2309 throw getRuntime().newTypeError(args[0],
2310 getRuntime().getString());
2312 separator = getSeparatorForGets(new IRubyObject[] { args[0] });
2313 } else {
2314 separator = getSeparatorForGets(IRubyObject.NULL_ARRAY);
2317 RubyArray result = getRuntime().newArray();
2318 IRubyObject line;
2319 while (! (line = getline(separator)).isNil()) {
2320 result.append(line);
2322 return result;
2325 @JRubyMethod(name = "to_io")
2326 public RubyIO to_io() {
2327 return this;
2330 @Override
2331 public String toString() {
2332 return "RubyIO(" + openFile.getMode() + ", " + openFile.getMainStream().getDescriptor().getFileno() + ")";
2335 /* class methods for IO */
2337 /** rb_io_s_foreach
2340 @JRubyMethod(name = "foreach", required = 1, optional = 1, frame = true, meta = true)
2341 public static IRubyObject foreach(IRubyObject recv, IRubyObject[] args, Block block) {
2342 Ruby runtime = recv.getRuntime();
2343 int count = args.length;
2344 IRubyObject filename = args[0].convertToString();
2345 runtime.checkSafeString(filename);
2347 ByteList separator = getSeparatorFromArgs(runtime, args, 1);
2349 RubyIO io = (RubyIO)RubyFile.open(runtime.getFile(), new IRubyObject[] { filename }, Block.NULL_BLOCK);
2351 if (!io.isNil()) {
2352 try {
2353 IRubyObject str = io.getline(separator);
2354 ThreadContext context = runtime.getCurrentContext();
2355 while (!str.isNil()) {
2356 block.yield(context, str);
2357 str = io.getline(separator);
2359 } finally {
2360 io.close();
2364 return runtime.getNil();
2367 private static RubyIO registerSelect(Selector selector, IRubyObject obj, int ops) throws IOException {
2368 RubyIO ioObj;
2370 if (!(obj instanceof RubyIO)) {
2371 // invoke to_io
2372 if (!obj.respondsTo("to_io")) return null;
2374 ioObj = (RubyIO) obj.callMethod(obj.getRuntime().getCurrentContext(), "to_io");
2375 } else {
2376 ioObj = (RubyIO) obj;
2379 Channel channel = ioObj.getChannel();
2380 if (channel == null || !(channel instanceof SelectableChannel)) {
2381 return null;
2384 ((SelectableChannel) channel).configureBlocking(false);
2385 int real_ops = ((SelectableChannel) channel).validOps() & ops;
2386 SelectionKey key = ((SelectableChannel) channel).keyFor(selector);
2388 if (key == null) {
2389 ((SelectableChannel) channel).register(selector, real_ops, obj);
2390 } else {
2391 key.interestOps(key.interestOps()|real_ops);
2394 return ioObj;
2397 @JRubyMethod(name = "select", required = 1, optional = 3, meta = true)
2398 public static IRubyObject select(IRubyObject recv, IRubyObject[] args) {
2399 return select_static(recv.getRuntime(), args);
2402 public static IRubyObject select_static(Ruby runtime, IRubyObject[] args) {
2403 try {
2404 // FIXME: This needs to be ported
2405 boolean atLeastOneDescriptor = false;
2407 Set pending = new HashSet();
2408 Selector selector = Selector.open();
2409 if (!args[0].isNil()) {
2410 atLeastOneDescriptor = true;
2412 // read
2413 for (Iterator i = ((RubyArray) args[0]).getList().iterator(); i.hasNext(); ) {
2414 IRubyObject obj = (IRubyObject) i.next();
2415 RubyIO ioObj = registerSelect(selector, obj,
2416 SelectionKey.OP_READ | SelectionKey.OP_ACCEPT);
2418 if (ioObj!=null && ioObj.writeDataBuffered()) pending.add(obj);
2421 if (args.length > 1 && !args[1].isNil()) {
2422 atLeastOneDescriptor = true;
2423 // write
2424 for (Iterator i = ((RubyArray) args[1]).getList().iterator(); i.hasNext(); ) {
2425 IRubyObject obj = (IRubyObject) i.next();
2426 registerSelect(selector, obj, SelectionKey.OP_WRITE);
2429 if (args.length > 2 && !args[2].isNil()) {
2430 atLeastOneDescriptor = true;
2431 // Java's select doesn't do anything about this, so we leave it be.
2434 long timeout = 0;
2435 if(args.length > 3 && !args[3].isNil()) {
2436 if (args[3] instanceof RubyFloat) {
2437 timeout = Math.round(((RubyFloat) args[3]).getDoubleValue() * 1000);
2438 } else {
2439 timeout = Math.round(((RubyFixnum) args[3]).getDoubleValue() * 1000);
2442 if (timeout < 0) {
2443 throw runtime.newArgumentError("negative timeout given");
2447 if (!atLeastOneDescriptor) {
2448 return runtime.getNil();
2451 if (pending.isEmpty()) {
2452 if (args.length > 3) {
2453 if (timeout==0) {
2454 selector.selectNow();
2455 } else {
2456 selector.select(timeout);
2458 } else {
2459 selector.select();
2461 } else {
2462 selector.selectNow();
2465 List r = new ArrayList();
2466 List w = new ArrayList();
2467 List e = new ArrayList();
2468 for (Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) {
2469 SelectionKey key = (SelectionKey) i.next();
2470 if ((key.interestOps() & key.readyOps()
2471 & (SelectionKey.OP_READ|SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT)) != 0) {
2472 r.add(key.attachment());
2473 pending.remove(key.attachment());
2475 if ((key.interestOps() & key.readyOps() & (SelectionKey.OP_WRITE)) != 0) {
2476 w.add(key.attachment());
2479 r.addAll(pending);
2481 // make all sockets blocking as configured again
2482 for (Iterator i = selector.keys().iterator(); i.hasNext(); ) {
2483 SelectionKey key = (SelectionKey) i.next();
2484 SelectableChannel channel = key.channel();
2485 synchronized(channel.blockingLock()) {
2486 boolean blocking = ((RubyIO) key.attachment()).getBlocking();
2487 key.cancel();
2488 channel.configureBlocking(blocking);
2491 selector.close();
2493 if (r.size() == 0 && w.size() == 0 && e.size() == 0) {
2494 return runtime.getNil();
2497 List ret = new ArrayList();
2499 ret.add(RubyArray.newArray(runtime, r));
2500 ret.add(RubyArray.newArray(runtime, w));
2501 ret.add(RubyArray.newArray(runtime, e));
2503 return RubyArray.newArray(runtime, ret);
2504 } catch(IOException e) {
2505 throw runtime.newIOError(e.getMessage());
2509 @JRubyMethod(name = "read", required = 1, optional = 2, meta = true)
2510 public static IRubyObject read(IRubyObject recv, IRubyObject[] args, Block block) {
2511 IRubyObject[] fileArguments = new IRubyObject[] {args[0]};
2512 RubyIO file = (RubyIO) RubyKernel.open(recv, fileArguments, block);
2513 IRubyObject[] readArguments;
2515 if (args.length >= 2 && !args[1].isNil()) {
2516 readArguments = new IRubyObject[] {args[1].convertToInteger()};
2517 } else {
2518 readArguments = new IRubyObject[] {};
2521 try {
2523 if (args.length == 3 && !args[2].isNil()) {
2524 file.seek(new IRubyObject[] {args[2].convertToInteger()});
2527 return file.read(readArguments);
2528 } finally {
2529 file.close();
2533 @JRubyMethod(name = "readlines", required = 1, optional = 1, meta = true)
2534 public static RubyArray readlines(IRubyObject recv, IRubyObject[] args, Block block) {
2535 int count = args.length;
2537 IRubyObject[] fileArguments = new IRubyObject[]{args[0]};
2538 IRubyObject[] separatorArguments = count >= 2 ? new IRubyObject[]{args[1]} : IRubyObject.NULL_ARRAY;
2539 RubyIO file = (RubyIO) RubyKernel.open(recv, fileArguments, block);
2540 try {
2541 return file.readlines(separatorArguments);
2542 } finally {
2543 file.close();
2547 @JRubyMethod(name = "popen", required = 1, optional = 1, meta = true)
2548 public static IRubyObject popen(IRubyObject recv, IRubyObject[] args, Block block) {
2549 Ruby runtime = recv.getRuntime();
2550 int mode;
2552 IRubyObject cmdObj = args[0].convertToString();
2553 runtime.checkSafeString(cmdObj);
2555 if ("-".equals(cmdObj.toString())) {
2556 throw recv.getRuntime().newNotImplementedError(
2557 "popen(\"-\") is unimplemented");
2560 try {
2561 if (args.length == 1) {
2562 mode = ModeFlags.RDONLY;
2563 } else if (args[1] instanceof RubyFixnum) {
2564 mode = RubyFixnum.num2int(args[1]);
2565 } else {
2566 mode = getIOModesIntFromString(runtime, args[1].convertToString().toString());
2569 ModeFlags modes = new ModeFlags(mode);
2571 Process process = new ShellLauncher(runtime).run(cmdObj);
2572 RubyIO io = new RubyIO(runtime, process, modes);
2574 if (block.isGiven()) {
2575 try {
2576 return block.yield(runtime.getCurrentContext(), io);
2577 } finally {
2578 if (io.openFile.isOpen()) {
2579 io.close();
2581 runtime.getGlobalVariables().set("$?", RubyProcess.RubyStatus.newProcessStatus(runtime, (process.waitFor() * 256)));
2584 return io;
2585 } catch (InvalidValueException ex) {
2586 throw runtime.newErrnoEINVALError();
2587 } catch (IOException e) {
2588 throw runtime.newIOErrorFromException(e);
2589 } catch (InterruptedException e) {
2590 throw runtime.newThreadError("unexpected interrupt");
2594 // NIO based pipe
2595 @JRubyMethod(name = "pipe", meta = true)
2596 public static IRubyObject pipe(IRubyObject recv) throws Exception {
2597 // TODO: This isn't an exact port of MRI's pipe behavior, so revisit
2598 Ruby runtime = recv.getRuntime();
2599 Pipe pipe = Pipe.open();
2601 RubyIO source = new RubyIO(runtime, pipe.source());
2602 RubyIO sink = new RubyIO(runtime, pipe.sink());
2604 sink.openFile.getMainStream().setSync(true);
2605 return runtime.newArrayNoCopy(new IRubyObject[]{
2606 source,
2607 sink