コード例 #1
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /**
   * 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;
  }
コード例 #2
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
    }
  }
コード例 #3
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 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();
   }
 }
コード例 #4
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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()));
    }
  }
コード例 #5
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #6
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 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;
 }
コード例 #7
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 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
     }
   }
 }
コード例 #8
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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
      }
    }
  }
コード例 #9
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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);
    }
  }
コード例 #10
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 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);
 }
コード例 #11
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #12
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 /** @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();
   }
 }
コード例 #13
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 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);
 }
コード例 #14
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #15
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 /**
  * 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);
   }
 }
コード例 #16
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /**
   * 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();
  }
コード例 #17
0
  /**
   * 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;
    }
  }
コード例 #18
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 /**
  * 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 {
   }
 }
コード例 #19
0
  @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;
  }
コード例 #20
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #21
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /** 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);
    }
  }
コード例 #22
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
    }
  }
コード例 #23
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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);
    }
  }
コード例 #24
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #25
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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);
  }
コード例 #26
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /**
   * @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();
  }
コード例 #27
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /**
   * @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();
  }
コード例 #28
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  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;
  }
コード例 #29
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
 public InputStream newInputStream() {
   InputStream in = descriptor.getBaseInputStream();
   return in == null ? new InputStreamAdapter(this) : in;
 }
コード例 #30
0
ファイル: ChannelStream.java プロジェクト: bruceadams/jruby
  /**
   * @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;
    }
  }