/** * Flush the write buffer to the channel (if needed) * * @throws IOException */ private boolean flushWrite(final boolean block) throws IOException, BadDescriptorException { if (reading || !modes.isWritable() || buffer.position() == 0) return false; // Don't bother int len = buffer.position(); int nWritten = 0; buffer.flip(); // For Sockets, only write as much as will fit. if (descriptor.getChannel() instanceof SelectableChannel) { SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel(); synchronized (selectableChannel.blockingLock()) { boolean oldBlocking = selectableChannel.isBlocking(); try { if (oldBlocking != block) { selectableChannel.configureBlocking(block); } nWritten = descriptor.write(buffer); } finally { if (oldBlocking != block) { selectableChannel.configureBlocking(oldBlocking); } } } } else { nWritten = descriptor.write(buffer); } if (nWritten != len) { buffer.compact(); return false; } buffer.clear(); return true; }
public synchronized ByteList readnonblock(int number) throws IOException, BadDescriptorException, EOFException { assert number >= 0; if (number == 0) { return null; } if (descriptor.getChannel() instanceof SelectableChannel) { SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel(); synchronized (selectableChannel.blockingLock()) { boolean oldBlocking = selectableChannel.isBlocking(); try { selectableChannel.configureBlocking(false); return readpartial(number); } finally { selectableChannel.configureBlocking(oldBlocking); } } } else if (descriptor.getChannel() instanceof FileChannel) { return fread(number); } else { return null; } }
public int ready() throws IOException { if (descriptor.getChannel() instanceof SelectableChannel) { int ready_stat = 0; java.nio.channels.Selector sel = SelectorFactory.openWithRetryFrom( null, ((SelectableChannel) descriptor.getChannel()).provider()); SelectableChannel selchan = (SelectableChannel) descriptor.getChannel(); synchronized (selchan.blockingLock()) { boolean is_block = selchan.isBlocking(); try { selchan.configureBlocking(false); selchan.register(sel, java.nio.channels.SelectionKey.OP_READ); ready_stat = sel.selectNow(); sel.close(); } catch (Throwable ex) { } finally { if (sel != null) { try { sel.close(); } catch (Exception e) { } } selchan.configureBlocking(is_block); } } return ready_stat; } else { return newInputStream().available(); } }
public synchronized int writenonblock(ByteList buf) throws IOException, BadDescriptorException { checkWritable(); ensureWrite(); // Ruby ignores empty syswrites if (buf == null || buf.length() == 0) return 0; if (buffer.position() != 0 && !flushWrite(false)) return 0; if (descriptor.getChannel() instanceof SelectableChannel) { SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel(); synchronized (selectableChannel.blockingLock()) { boolean oldBlocking = selectableChannel.isBlocking(); try { if (oldBlocking) { selectableChannel.configureBlocking(false); } return descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length())); } finally { if (oldBlocking) { selectableChannel.configureBlocking(oldBlocking); } } } } else { // can't set nonblocking, so go ahead with it...not much else we can do return descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length())); } }
private ByteList bufferedRead(int number) throws IOException, BadDescriptorException { checkReadable(); ensureRead(); int resultSize = 0; // 128K seems to be the minimum at which the stat+seek is faster than reallocation final int BULK_THRESHOLD = 128 * 1024; if (number >= BULK_THRESHOLD && descriptor.isSeekable() && descriptor.getChannel() instanceof FileChannel) { // // If it is a file channel, then we can pre-allocate the output buffer // to the total size of buffered + remaining bytes in file // FileChannel fileChannel = (FileChannel) descriptor.getChannel(); resultSize = (int) Math.min( fileChannel.size() - fileChannel.position() + bufferedInputBytesRemaining(), number); } else { // // Cannot discern the total read length - allocate at least enough for the buffered data // resultSize = Math.min(bufferedInputBytesRemaining(), number); } ByteList result = new ByteList(resultSize); bufferedRead(result, number); return result; }
private void resetForWrite() throws IOException { if (descriptor.isSeekable()) { FileChannel fileChannel = (FileChannel) descriptor.getChannel(); if (buffer.hasRemaining()) { // we have read ahead, and need to back up fileChannel.position(fileChannel.position() - buffer.remaining()); } } // FIXME: Clearing read buffer here...is this appropriate? buffer.clear(); reading = false; }
public void setBlocking(boolean block) throws IOException { if (!(descriptor.getChannel() instanceof SelectableChannel)) { return; } synchronized (((SelectableChannel) descriptor.getChannel()).blockingLock()) { blocking = block; try { ((SelectableChannel) descriptor.getChannel()).configureBlocking(block); } catch (IllegalBlockingModeException e) { // ignore this; select() will set the correct mode when it is finished } } }
public synchronized void freopen(Ruby runtime, String path, ModeFlags modes) throws DirectoryAsFileException, IOException, InvalidValueException, PipeException, BadDescriptorException { // flush first flushWrite(); // reset buffer buffer.clear(); if (reading) { buffer.flip(); } this.modes = modes; if (descriptor.isOpen()) { descriptor.close(); } if (path.equals("/dev/null") || path.equalsIgnoreCase("nul:") || path.equalsIgnoreCase("nul")) { descriptor = descriptor.reopen(new NullChannel(), modes); } else { String cwd = runtime.getCurrentDirectory(); JRubyFile theFile = JRubyFile.create(cwd, path); if (theFile.isDirectory() && modes.isWritable()) throw new DirectoryAsFileException(); if (modes.isCreate()) { if (theFile.exists() && modes.isExclusive()) { throw runtime.newErrnoEEXISTError("File exists - " + path); } theFile.createNewFile(); } else { if (!theFile.exists()) { throw runtime.newErrnoENOENTError("file not found - " + path); } } // We always open this rw since we can only open it r or rw. RandomAccessFile file = new RandomAccessFile(theFile, modes.toJavaModeString()); if (modes.isTruncate()) file.setLength(0L); descriptor = descriptor.reopen(file, modes); try { if (modes.isAppendable()) lseek(0, SEEK_END); } catch (PipeException pe) { // ignore, it's a pipe or fifo } } }
private void finish(boolean close) throws BadDescriptorException, IOException { try { flushWrite(); if (DEBUG) LOG.info("Descriptor for fileno {} closed by stream", descriptor.getFileno()); } finally { buffer = EMPTY_BUFFER; // clear runtime so it doesn't get stuck in memory (JRUBY-2933) runtime = null; // finish descriptor descriptor.finish(close); } }
public static Stream fdopen(Ruby runtime, ChannelDescriptor descriptor, ModeFlags modes) throws InvalidValueException { // check these modes before constructing, so we don't finalize the partially-initialized stream descriptor.checkNewModes(modes); return maybeWrapWithLineEndingWrapper( new ChannelStream(runtime, descriptor, modes, true), modes); }
private int bufferedRead(ByteList dst, int number) throws IOException, BadDescriptorException { int bytesRead = 0; // // Copy what is in the buffer, if there is some buffered data // bytesRead += copyBufferedBytes(dst, number); boolean done = false; // // Avoid double-copying for reads that are larger than the buffer size // while ((number - bytesRead) >= BUFSIZE) { // // limit each iteration to a max of BULK_READ_SIZE to avoid over-size allocations // final int bytesToRead = Math.min(BULK_READ_SIZE, number - bytesRead); final int n = descriptor.read(bytesToRead, dst); if (n == -1) { eof = true; done = true; break; } else if (n == 0) { done = true; break; } bytesRead += n; } // // Complete the request by filling the read buffer first // while (!done && bytesRead < number) { int read = refillBuffer(); if (read == -1) { eof = true; break; } else if (read == 0) { break; } // append what we read into our buffer and allow the loop to continue final int len = Math.min(buffer.remaining(), number - bytesRead); dst.append(buffer, len); bytesRead += len; } if (bytesRead == 0 && number != 0) { if (eof) { throw newEOFException(); } } return bytesRead; }
/** @throws IOException */ public synchronized long fgetpos() throws IOException, PipeException, InvalidValueException, BadDescriptorException { // Correct position for read / write buffering (we could invalidate, but expensive) if (descriptor.isSeekable()) { FileChannel fileChannel = (FileChannel) descriptor.getChannel(); long pos = fileChannel.position(); // Adjust for buffered data if (reading) { pos -= buffer.remaining(); return pos - (pos > 0 && ungotc != -1 ? 1 : 0); } else { return pos + buffer.position(); } } else if (descriptor.isNull()) { return 0; } else { throw new PipeException(); } }
private ChannelStream(Ruby runtime, ChannelDescriptor descriptor, boolean autoclose) { this.runtime = runtime; this.descriptor = descriptor; this.modes = descriptor.getOriginalModes(); buffer = ByteBuffer.allocate(BUFSIZE); buffer.flip(); this.reading = true; this.autoclose = autoclose; runtime.addInternalFinalizer(this); }
public static Stream fopen(Ruby runtime, String path, ModeFlags modes) throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException, InvalidValueException, PipeException, BadDescriptorException { ChannelDescriptor descriptor = ChannelDescriptor.open( runtime.getCurrentDirectory(), path, modes, runtime.getClassLoader()); Stream stream = fdopen(runtime, descriptor, modes); return stream; }
/** * Invalidate buffer before a position change has occurred (e.g. seek), flushing writes if * required, and correcting file position if reading * * @throws IOException */ private void invalidateBuffer() throws IOException, BadDescriptorException { if (!reading) flushWrite(); int posOverrun = buffer.remaining(); // how far ahead we are when reading buffer.clear(); if (reading) { buffer.flip(); // if the read buffer is ahead, back up FileChannel fileChannel = (FileChannel) descriptor.getChannel(); if (posOverrun != 0) fileChannel.position(fileChannel.position() - posOverrun); } }
/** * Flush the write buffer to the channel (if needed) * * @throws IOException */ private void flushWrite() throws IOException, BadDescriptorException { if (reading || !modes.isWritable() || buffer.position() == 0) return; // Don't bother int len = buffer.position(); buffer.flip(); int n = descriptor.write(buffer); if (n != len) { // TODO: check the return value here } buffer.clear(); }
/** * Mimics the POSIX dup2(2) function, returning a new descriptor that references the same open * channel but with a specified fileno. This differs from the fileno version by making the target * descriptor into a new reference to the current descriptor's channel, closing what it originally * pointed to and preserving its original fileno. * * @param other the descriptor to dup this one into */ public void dup2Into(ChannelDescriptor other) throws BadDescriptorException, IOException { synchronized (refCounter) { refCounter.incrementAndGet(); if (DEBUG) LOG.info("Reopen fileno {}, refs now: {}", internalFileno, refCounter.get()); other.close(); other.channel = channel; other.originalModes = originalModes; other.fileDescriptor = fileDescriptor; other.refCounter = refCounter; other.canBeSeekable = canBeSeekable; other.readableChannel = readableChannel; other.writableChannel = writableChannel; other.seekableChannel = seekableChannel; } }
/** * Implementation of libc "lseek", which seeks on seekable streams, raises EPIPE if the fd is * assocated with a pipe, socket, or FIFO, and doesn't do anything for other cases (like stdio). * * @throws IOException * @throws InvalidValueException */ public synchronized void lseek(long offset, int type) throws IOException, InvalidValueException, PipeException, BadDescriptorException { if (descriptor.isSeekable()) { FileChannel fileChannel = (FileChannel) descriptor.getChannel(); ungotc = -1; int adj = 0; if (reading) { // for SEEK_CUR, need to adjust for buffered data adj = buffer.remaining(); buffer.clear(); buffer.flip(); } else { flushWrite(); } try { switch (type) { case SEEK_SET: fileChannel.position(offset); break; case SEEK_CUR: fileChannel.position(fileChannel.position() - adj + offset); break; case SEEK_END: fileChannel.position(fileChannel.size() + offset); break; } } catch (IllegalArgumentException e) { throw new InvalidValueException(); } catch (IOException ioe) { throw ioe; } } else if (descriptor.getChannel() instanceof SelectableChannel) { // TODO: It's perhaps just a coincidence that all the channels for // which we should raise are instanceof SelectableChannel, since // stdio is not...so this bothers me slightly. -CON throw new PipeException(); } else { } }
@JRubyMethod(meta = true) public static IRubyObject for_fd(ThreadContext context, IRubyObject _klass, IRubyObject _fileno) { Ruby runtime = context.runtime; int fileno = (int) _fileno.convertToInteger().getLongValue(); RubyClass klass = (RubyClass) _klass; ChannelDescriptor descriptor = ChannelDescriptor.getDescriptorByFileno(runtime.getFilenoExtMap(fileno)); RubyBasicSocket basicSocket = (RubyBasicSocket) klass.getAllocator().allocate(runtime, klass); basicSocket.initSocket(runtime, descriptor); return basicSocket; }
public synchronized ByteList read(int number) throws IOException, BadDescriptorException { checkReadable(); ensureReadNonBuffered(); ByteList byteList = new ByteList(number); // TODO this should entry into error handling somewhere int bytesRead = descriptor.read(number, byteList); if (bytesRead == -1) { eof = true; } return byteList; }
/** Ensure close (especially flush) when we're finished with. */ @Override public void finalize() throws Throwable { super.finalize(); if (closedExplicitly) return; if (DEBUG) { LOG.info("finalize() for not explicitly closed stream"); } // FIXME: I got a bunch of NPEs when I didn't check for nulls here...HOW?! if (descriptor != null && descriptor.isOpen()) { // tidy up finish(autoclose); } }
public synchronized int read() throws IOException, BadDescriptorException { try { descriptor.checkOpen(); if (ungotc >= 0) { int c = ungotc; ungotc = -1; return c; } return bufferedRead(); } catch (EOFException e) { eof = true; return -1; } }
public synchronized ByteList readpartial(int number) throws IOException, BadDescriptorException, EOFException { assert number >= 0; if (number == 0) { return null; } if (descriptor.getChannel() instanceof FileChannel) { return fread(number); } if (hasBufferedInputBytes()) { // already have some bytes buffered, just return those return bufferedRead(Math.min(bufferedInputBytesRemaining(), number)); } else { // otherwise, we try an unbuffered read to get whatever's available return read(number); } }
public synchronized int getline(ByteList dst, byte terminator, long limit) throws IOException, BadDescriptorException { checkReadable(); ensureRead(); descriptor.checkOpen(); int totalRead = 0; boolean found = false; if (ungotc != -1) { dst.append((byte) ungotc); found = ungotc == terminator; ungotc = -1; limit--; ++totalRead; } while (!found) { final byte[] bytes = buffer.array(); final int begin = buffer.arrayOffset() + buffer.position(); final int end = begin + buffer.remaining(); int len = 0; for (int i = begin; i < end && limit-- > 0 && !found; ++i) { found = bytes[i] == terminator; ++len; } if (limit < 1) found = true; if (len > 0) { dst.append(buffer, len); totalRead += len; } if (!found) { int n = refillBuffer(); if (n <= 0) { if (n < 0 && totalRead < 1) { return -1; } break; } } } return totalRead; }
public synchronized void ftruncate(long newLength) throws IOException, BadDescriptorException, InvalidValueException { Channel ch = descriptor.getChannel(); if (!(ch instanceof FileChannel)) { throw new InvalidValueException(); } invalidateBuffer(); FileChannel fileChannel = (FileChannel) ch; long position = fileChannel.position(); if (newLength > fileChannel.size()) { // truncate can't lengthen files, so we save position, seek/write, and go back int difference = (int) (newLength - fileChannel.size()); fileChannel.position(fileChannel.size()); // FIXME: This worries me a bit, since it could allocate a lot with a large newLength fileChannel.write(ByteBuffer.allocate(difference)); } else { fileChannel.truncate(newLength); } fileChannel.position(position); }
/** * @throws IOException * @throws BadDescriptorException */ private int bufferedWrite(ByteBuffer buf) throws IOException, BadDescriptorException { checkWritable(); ensureWrite(); // Ruby ignores empty syswrites if (buf == null || !buf.hasRemaining()) return 0; final int nbytes = buf.remaining(); if (nbytes >= buffer.capacity()) { // Doesn't fit in buffer. Write immediately. flushWrite(); // ensure nothing left to write descriptor.write(buf); // TODO: check the return value here } else { if (nbytes > buffer.remaining()) flushWrite(); buffer.put(buf); } if (isSync()) flushWrite(); return nbytes - buf.remaining(); }
/** * @throws IOException * @throws BadDescriptorException */ private int bufferedWrite(ByteList buf) throws IOException, BadDescriptorException { checkWritable(); ensureWrite(); // Ruby ignores empty syswrites if (buf == null || buf.length() == 0) return 0; if (buf.length() > buffer.capacity()) { // Doesn't fit in buffer. Write immediately. flushWrite(); // ensure nothing left to write int n = descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length())); if (n != buf.length()) { // TODO: check the return value here } } else { if (buf.length() > buffer.remaining()) flushWrite(); buffer.put(buf.getUnsafeBytes(), buf.begin(), buf.length()); } if (isSync()) flushWrite(); return buf.getRealSize(); }
private int bufferedRead(ByteBuffer dst, boolean partial) throws IOException, BadDescriptorException { checkReadable(); ensureRead(); boolean done = false; int bytesRead = 0; // // Copy what is in the buffer, if there is some buffered data // bytesRead += copyBufferedBytes(dst); // // Avoid double-copying for reads that are larger than the buffer size, or // the destination is a direct buffer. // while ((bytesRead < 1 || !partial) && (dst.remaining() >= BUFSIZE || dst.isDirect())) { ByteBuffer tmpDst = dst; if (!dst.isDirect()) { // // We limit reads to BULK_READ_SIZED chunks to avoid NIO allocating // a huge temporary native buffer, when doing reads into a heap buffer // If the dst buffer is direct, then no need to limit. // int bytesToRead = Math.min(BULK_READ_SIZE, dst.remaining()); if (bytesToRead < dst.remaining()) { tmpDst = dst.duplicate(); tmpDst.limit(tmpDst.position() + bytesToRead); } } int n = descriptor.read(tmpDst); if (n == -1) { eof = true; done = true; break; } else if (n == 0) { done = true; break; } else { bytesRead += n; } } // // Complete the request by filling the read buffer first // while (!done && dst.hasRemaining() && (bytesRead < 1 || !partial)) { int read = refillBuffer(); if (read == -1) { eof = true; done = true; break; } else if (read == 0) { done = true; break; } else { // append what we read into our buffer and allow the loop to continue bytesRead += copyBufferedBytes(dst); } } if (eof && bytesRead == 0 && dst.remaining() != 0) { throw newEOFException(); } return bytesRead; }
public InputStream newInputStream() { InputStream in = descriptor.getBaseInputStream(); return in == null ? new InputStreamAdapter(this) : in; }
/** * @deprecated readall do busy loop for the IO which has NONBLOCK bit. You should implement the * logic by yourself with fread(). */ @Deprecated public synchronized ByteList readall() throws IOException, BadDescriptorException { final long fileSize = descriptor.isSeekable() && descriptor.getChannel() instanceof FileChannel ? ((FileChannel) descriptor.getChannel()).size() : 0; // // Check file size - special files in /proc have zero size and need to be // handled by the generic read path. // if (fileSize > 0) { ensureRead(); FileChannel channel = (FileChannel) descriptor.getChannel(); final long left = fileSize - channel.position() + bufferedInputBytesRemaining(); if (left <= 0) { eof = true; return null; } if (left > Integer.MAX_VALUE) { if (getRuntime() != null) { throw getRuntime().newIOError("File too large"); } else { throw new IOException("File too large"); } } ByteList result = new ByteList((int) left); ByteBuffer buf = ByteBuffer.wrap(result.getUnsafeBytes(), result.begin(), (int) left); // // Copy any buffered data (including ungetc byte) // copyBufferedBytes(buf); // // Now read unbuffered directly from the file // while (buf.hasRemaining()) { final int MAX_READ_CHUNK = 1 * 1024 * 1024; // // When reading into a heap buffer, the jvm allocates a temporary // direct ByteBuffer of the requested size. To avoid allocating // a huge direct buffer when doing ludicrous reads (e.g. 1G or more) // we split the read up into chunks of no more than 1M // ByteBuffer tmp = buf.duplicate(); if (tmp.remaining() > MAX_READ_CHUNK) { tmp.limit(tmp.position() + MAX_READ_CHUNK); } int n = channel.read(tmp); if (n <= 0) { break; } buf.position(tmp.position()); } eof = true; result.length(buf.position()); return result; } else if (descriptor.isNull()) { return new ByteList(0); } else { checkReadable(); ByteList byteList = new ByteList(); ByteList read = fread(BUFSIZE); if (read == null) { eof = true; return byteList; } while (read != null) { byteList.append(read); read = fread(BUFSIZE); } return byteList; } }