| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.nio.ch; |
| |
| import android.system.ErrnoException; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.DirectByteBuffer; |
| import java.nio.MappedByteBuffer; |
| import java.nio.channels.ClosedByInterruptException; |
| import java.nio.channels.ClosedChannelException; |
| import java.nio.channels.FileChannel; |
| import java.nio.channels.FileLock; |
| import java.nio.channels.FileLockInterruptionException; |
| import java.nio.channels.NonReadableChannelException; |
| import java.nio.channels.NonWritableChannelException; |
| import java.nio.channels.OverlappingFileLockException; |
| import java.nio.channels.ReadableByteChannel; |
| import java.nio.channels.SelectableChannel; |
| import java.nio.channels.WritableByteChannel; |
| import java.security.AccessController; |
| import java.util.ArrayList; |
| import java.util.List; |
| import libcore.io.Libcore; |
| |
| import dalvik.annotation.optimization.ReachabilitySensitive; |
| import dalvik.system.BlockGuard; |
| import dalvik.system.CloseGuard; |
| |
| import jdk.internal.access.JavaIOFileDescriptorAccess; |
| import jdk.internal.access.SharedSecrets; |
| import sun.misc.Cleaner; |
| import sun.security.action.GetPropertyAction; |
| |
| public class FileChannelImpl |
| extends FileChannel |
| { |
| // Access to FileDescriptor internals |
| private static final JavaIOFileDescriptorAccess fdAccess = |
| SharedSecrets.getJavaIOFileDescriptorAccess(); |
| |
| // Memory allocation size for mapping buffers |
| private static final long allocationGranularity; |
| |
| // Used to make native read and write calls |
| private final FileDispatcher nd; |
| |
| // File descriptor |
| // Android-added: @ReachabilitySensitive |
| // If this were reclaimed while we're in an operation on fd, the associated Stream |
| // could be finalized, closing the fd while still in use. This is not the case upstream, |
| // since there the Stream is accessible from the FileDescriptor. |
| // Android-changed: make public. Used by NioUtils.getFD(), and possibly others. |
| @ReachabilitySensitive |
| public final FileDescriptor fd; |
| |
| // File access mode (immutable) |
| private final boolean writable; |
| private final boolean readable; |
| |
| // Required to prevent finalization of creating stream (immutable) |
| private final Object parent; |
| |
| // The path of the referenced file |
| // (null if the parent stream is created with a file descriptor) |
| private final String path; |
| |
| // Thread-safe set of IDs of native threads, for signalling |
| private final NativeThreadSet threads = new NativeThreadSet(2); |
| |
| // Lock for operations involving position and size |
| private final Object positionLock = new Object(); |
| |
| // Android-added: CloseGuard support. |
| @ReachabilitySensitive |
| private final CloseGuard guard = CloseGuard.get(); |
| |
| private FileChannelImpl(FileDescriptor fd, String path, boolean readable, |
| boolean writable, Object parent) |
| { |
| this.fd = fd; |
| this.readable = readable; |
| this.writable = writable; |
| this.parent = parent; |
| this.path = path; |
| this.nd = new FileDispatcherImpl(); |
| // BEGIN Android-added: CloseGuard support. |
| if (fd != null && fd.valid()) { |
| guard.open("close"); |
| } |
| // END Android-added: CloseGuard support. |
| } |
| |
| // Used by FileInputStream.getChannel() and RandomAccessFile.getChannel() |
| public static FileChannel open(FileDescriptor fd, String path, |
| boolean readable, boolean writable, |
| Object parent) |
| { |
| return new FileChannelImpl(fd, path, readable, writable, parent); |
| } |
| |
| private void ensureOpen() throws IOException { |
| if (!isOpen()) |
| throw new ClosedChannelException(); |
| } |
| |
| |
| // -- Standard channel operations -- |
| |
| protected void implCloseChannel() throws IOException { |
| // Android-added: CloseGuard support. |
| guard.close(); |
| // Release and invalidate any locks that we still hold |
| if (fileLockTable != null) { |
| for (FileLock fl: fileLockTable.removeAll()) { |
| synchronized (fl) { |
| if (fl.isValid()) { |
| nd.release(fd, fl.position(), fl.size()); |
| ((FileLockImpl)fl).invalidate(); |
| } |
| } |
| } |
| } |
| |
| // signal any threads blocked on this channel |
| threads.signalAndWait(); |
| |
| if (parent != null) { |
| |
| // Close the fd via the parent stream's close method. The parent |
| // will reinvoke our close method, which is defined in the |
| // superclass AbstractInterruptibleChannel, but the isOpen logic in |
| // that method will prevent this method from being reinvoked. |
| // |
| ((java.io.Closeable)parent).close(); |
| } else { |
| nd.close(fd); |
| } |
| |
| } |
| |
| // BEGIN Android-added: CloseGuard support. |
| protected void finalize() throws Throwable { |
| try { |
| if (guard != null) { |
| guard.warnIfOpen(); |
| } |
| close(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| // END Android-added: CloseGuard support. |
| |
| public int read(ByteBuffer dst) throws IOException { |
| ensureOpen(); |
| if (!readable) |
| throw new NonReadableChannelException(); |
| synchronized (positionLock) { |
| int n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return 0; |
| do { |
| n = IOUtil.read(fd, dst, -1, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| } |
| |
| public long read(ByteBuffer[] dsts, int offset, int length) |
| throws IOException |
| { |
| if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) |
| throw new IndexOutOfBoundsException(); |
| ensureOpen(); |
| if (!readable) |
| throw new NonReadableChannelException(); |
| synchronized (positionLock) { |
| long n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return 0; |
| do { |
| n = IOUtil.read(fd, dsts, offset, length, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| } |
| |
| public int write(ByteBuffer src) throws IOException { |
| ensureOpen(); |
| if (!writable) |
| throw new NonWritableChannelException(); |
| synchronized (positionLock) { |
| int n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return 0; |
| do { |
| n = IOUtil.write(fd, src, -1, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| } |
| |
| public long write(ByteBuffer[] srcs, int offset, int length) |
| throws IOException |
| { |
| if ((offset < 0) || (length < 0) || (offset > srcs.length - length)) |
| throw new IndexOutOfBoundsException(); |
| ensureOpen(); |
| if (!writable) |
| throw new NonWritableChannelException(); |
| synchronized (positionLock) { |
| long n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return 0; |
| do { |
| n = IOUtil.write(fd, srcs, offset, length, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| } |
| |
| // -- Other operations -- |
| |
| public long position() throws IOException { |
| ensureOpen(); |
| synchronized (positionLock) { |
| long p = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return 0; |
| boolean append = fdAccess.getAppend(fd); |
| // BEGIN Android-added: BlockGuard support. |
| // Note: position() itself doesn't seem to block, so this may be overzealous |
| // when position() is not followed by a read/write operation. http://b/77263638 |
| if (append) { |
| BlockGuard.getThreadPolicy().onWriteToDisk(); |
| } |
| // END Android-added: BlockGuard support. |
| do { |
| // in append-mode then position is advanced to end before writing |
| p = (append) ? nd.size(fd) : position0(fd, -1); |
| } while ((p == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(p); |
| } finally { |
| threads.remove(ti); |
| end(p > -1); |
| assert IOStatus.check(p); |
| } |
| } |
| } |
| |
| public FileChannel position(long newPosition) throws IOException { |
| ensureOpen(); |
| if (newPosition < 0) |
| throw new IllegalArgumentException(); |
| synchronized (positionLock) { |
| long p = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return null; |
| // Android-added: BlockGuard support. |
| // Note: position() itself doesn't seem to block, so this may be overzealous |
| // when position() is not followed by a read/write operation. http://b/77263638 |
| BlockGuard.getThreadPolicy().onReadFromDisk(); |
| do { |
| p = position0(fd, newPosition); |
| } while ((p == IOStatus.INTERRUPTED) && isOpen()); |
| return this; |
| } finally { |
| threads.remove(ti); |
| end(p > -1); |
| assert IOStatus.check(p); |
| } |
| } |
| } |
| |
| public long size() throws IOException { |
| ensureOpen(); |
| synchronized (positionLock) { |
| long s = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return -1; |
| do { |
| s = nd.size(fd); |
| } while ((s == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(s); |
| } finally { |
| threads.remove(ti); |
| end(s > -1); |
| assert IOStatus.check(s); |
| } |
| } |
| } |
| |
| public FileChannel truncate(long newSize) throws IOException { |
| ensureOpen(); |
| if (newSize < 0) |
| throw new IllegalArgumentException("Negative size"); |
| if (!writable) |
| throw new NonWritableChannelException(); |
| synchronized (positionLock) { |
| int rv = -1; |
| long p = -1; |
| int ti = -1; |
| long rp = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return null; |
| |
| // get current size |
| long size; |
| do { |
| size = nd.size(fd); |
| } while ((size == IOStatus.INTERRUPTED) && isOpen()); |
| if (!isOpen()) |
| return null; |
| |
| // get current position |
| do { |
| p = position0(fd, -1); |
| } while ((p == IOStatus.INTERRUPTED) && isOpen()); |
| if (!isOpen()) |
| return null; |
| assert p >= 0; |
| |
| // truncate file if given size is less than the current size |
| if (newSize < size) { |
| do { |
| rv = nd.truncate(fd, newSize); |
| } while ((rv == IOStatus.INTERRUPTED) && isOpen()); |
| if (!isOpen()) |
| return null; |
| } |
| |
| // if position is beyond new size then adjust it |
| if (p > newSize) |
| p = newSize; |
| do { |
| rp = position0(fd, p); |
| } while ((rp == IOStatus.INTERRUPTED) && isOpen()); |
| return this; |
| } finally { |
| threads.remove(ti); |
| end(rv > -1); |
| assert IOStatus.check(rv); |
| } |
| } |
| } |
| |
| public void force(boolean metaData) throws IOException { |
| ensureOpen(); |
| int rv = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return; |
| do { |
| rv = nd.force(fd, metaData); |
| } while ((rv == IOStatus.INTERRUPTED) && isOpen()); |
| } finally { |
| threads.remove(ti); |
| end(rv > -1); |
| assert IOStatus.check(rv); |
| } |
| } |
| |
| // Assume at first that the underlying kernel supports sendfile(); |
| // set this to false if we find out later that it doesn't |
| // |
| private static volatile boolean transferSupported = true; |
| |
| // Assume that the underlying kernel sendfile() will work if the target |
| // fd is a pipe; set this to false if we find out later that it doesn't |
| // |
| private static volatile boolean pipeSupported = true; |
| |
| // Assume that the underlying kernel sendfile() will work if the target |
| // fd is a file; set this to false if we find out later that it doesn't |
| // |
| private static volatile boolean fileSupported = true; |
| |
| private long transferToDirectlyInternal(long position, int icount, |
| WritableByteChannel target, |
| FileDescriptor targetFD) |
| throws IOException |
| { |
| assert !nd.transferToDirectlyNeedsPositionLock() || |
| Thread.holdsLock(positionLock); |
| |
| long n = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return -1; |
| // Android-added: BlockGuard support. |
| BlockGuard.getThreadPolicy().onWriteToDisk(); |
| do { |
| n = transferTo0(fd, position, icount, targetFD); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| if (n == IOStatus.UNSUPPORTED_CASE) { |
| if (target instanceof SinkChannelImpl) |
| pipeSupported = false; |
| if (target instanceof FileChannelImpl) |
| fileSupported = false; |
| return IOStatus.UNSUPPORTED_CASE; |
| } |
| if (n == IOStatus.UNSUPPORTED) { |
| // Don't bother trying again |
| transferSupported = false; |
| return IOStatus.UNSUPPORTED; |
| } |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end (n > -1); |
| } |
| } |
| |
| private long transferToDirectly(long position, int icount, |
| WritableByteChannel target) |
| throws IOException |
| { |
| if (!transferSupported) |
| return IOStatus.UNSUPPORTED; |
| |
| FileDescriptor targetFD = null; |
| if (target instanceof FileChannelImpl) { |
| if (!fileSupported) |
| return IOStatus.UNSUPPORTED_CASE; |
| targetFD = ((FileChannelImpl)target).fd; |
| } else if (target instanceof SelChImpl) { |
| // Direct transfer to pipe causes EINVAL on some configurations |
| if ((target instanceof SinkChannelImpl) && !pipeSupported) |
| return IOStatus.UNSUPPORTED_CASE; |
| |
| // Platform-specific restrictions. Now there is only one: |
| // Direct transfer to non-blocking channel could be forbidden |
| SelectableChannel sc = (SelectableChannel)target; |
| if (!nd.canTransferToDirectly(sc)) |
| return IOStatus.UNSUPPORTED_CASE; |
| |
| targetFD = ((SelChImpl)target).getFD(); |
| } |
| |
| if (targetFD == null) |
| return IOStatus.UNSUPPORTED; |
| int thisFDVal = IOUtil.fdVal(fd); |
| int targetFDVal = IOUtil.fdVal(targetFD); |
| if (thisFDVal == targetFDVal) // Not supported on some configurations |
| return IOStatus.UNSUPPORTED; |
| |
| if (nd.transferToDirectlyNeedsPositionLock()) { |
| synchronized (positionLock) { |
| long pos = position(); |
| try { |
| return transferToDirectlyInternal(position, icount, |
| target, targetFD); |
| } finally { |
| position(pos); |
| } |
| } |
| } else { |
| return transferToDirectlyInternal(position, icount, target, targetFD); |
| } |
| } |
| |
| // Maximum size to map when using a mapped buffer |
| private static final long MAPPED_TRANSFER_SIZE = 8L*1024L*1024L; |
| |
| private long transferToTrustedChannel(long position, long count, |
| WritableByteChannel target) |
| throws IOException |
| { |
| boolean isSelChImpl = (target instanceof SelChImpl); |
| if (!((target instanceof FileChannelImpl) || isSelChImpl)) |
| return IOStatus.UNSUPPORTED; |
| |
| // Trusted target: Use a mapped buffer |
| long remaining = count; |
| while (remaining > 0L) { |
| long size = Math.min(remaining, MAPPED_TRANSFER_SIZE); |
| try { |
| MappedByteBuffer dbb = map(MapMode.READ_ONLY, position, size); |
| try { |
| // ## Bug: Closing this channel will not terminate the write |
| int n = target.write(dbb); |
| assert n >= 0; |
| remaining -= n; |
| if (isSelChImpl) { |
| // one attempt to write to selectable channel |
| break; |
| } |
| assert n > 0; |
| position += n; |
| } finally { |
| unmap(dbb); |
| } |
| } catch (ClosedByInterruptException e) { |
| // target closed by interrupt as ClosedByInterruptException needs |
| // to be thrown after closing this channel. |
| assert !target.isOpen(); |
| try { |
| close(); |
| } catch (Throwable suppressed) { |
| e.addSuppressed(suppressed); |
| } |
| throw e; |
| } catch (IOException ioe) { |
| // Only throw exception if no bytes have been written |
| if (remaining == count) |
| throw ioe; |
| break; |
| } |
| } |
| return count - remaining; |
| } |
| |
| private long transferToArbitraryChannel(long position, int icount, |
| WritableByteChannel target) |
| throws IOException |
| { |
| // Untrusted target: Use a newly-erased buffer |
| int c = Math.min(icount, TRANSFER_SIZE); |
| ByteBuffer bb = Util.getTemporaryDirectBuffer(c); |
| long tw = 0; // Total bytes written |
| long pos = position; |
| try { |
| Util.erase(bb); |
| while (tw < icount) { |
| bb.limit(Math.min((int)(icount - tw), TRANSFER_SIZE)); |
| int nr = read(bb, pos); |
| if (nr <= 0) |
| break; |
| bb.flip(); |
| // ## Bug: Will block writing target if this channel |
| // ## is asynchronously closed |
| int nw = target.write(bb); |
| tw += nw; |
| if (nw != nr) |
| break; |
| pos += nw; |
| bb.clear(); |
| } |
| return tw; |
| } catch (IOException x) { |
| if (tw > 0) |
| return tw; |
| throw x; |
| } finally { |
| Util.releaseTemporaryDirectBuffer(bb); |
| } |
| } |
| |
| public long transferTo(long position, long count, |
| WritableByteChannel target) |
| throws IOException |
| { |
| ensureOpen(); |
| if (!target.isOpen()) |
| throw new ClosedChannelException(); |
| if (!readable) |
| throw new NonReadableChannelException(); |
| if (target instanceof FileChannelImpl && |
| !((FileChannelImpl)target).writable) |
| throw new NonWritableChannelException(); |
| if ((position < 0) || (count < 0)) |
| throw new IllegalArgumentException(); |
| long sz = size(); |
| if (position > sz) |
| return 0; |
| int icount = (int)Math.min(count, Integer.MAX_VALUE); |
| if ((sz - position) < icount) |
| icount = (int)(sz - position); |
| |
| long n; |
| |
| // Attempt a direct transfer, if the kernel supports it |
| if ((n = transferToDirectly(position, icount, target)) >= 0) |
| return n; |
| |
| // Attempt a mapped transfer, but only to trusted channel types |
| if ((n = transferToTrustedChannel(position, icount, target)) >= 0) |
| return n; |
| |
| // Slow path for untrusted targets |
| return transferToArbitraryChannel(position, icount, target); |
| } |
| |
| private long transferFromFileChannel(FileChannelImpl src, |
| long position, long count) |
| throws IOException |
| { |
| if (!src.readable) |
| throw new NonReadableChannelException(); |
| synchronized (src.positionLock) { |
| long pos = src.position(); |
| long max = Math.min(count, src.size() - pos); |
| |
| long remaining = max; |
| long p = pos; |
| while (remaining > 0L) { |
| long size = Math.min(remaining, MAPPED_TRANSFER_SIZE); |
| // ## Bug: Closing this channel will not terminate the write |
| MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, size); |
| try { |
| long n = write(bb, position); |
| assert n > 0; |
| p += n; |
| position += n; |
| remaining -= n; |
| } catch (IOException ioe) { |
| // Only throw exception if no bytes have been written |
| if (remaining == max) |
| throw ioe; |
| break; |
| } finally { |
| unmap(bb); |
| } |
| } |
| long nwritten = max - remaining; |
| src.position(pos + nwritten); |
| return nwritten; |
| } |
| } |
| |
| private static final int TRANSFER_SIZE = 8192; |
| |
| private long transferFromArbitraryChannel(ReadableByteChannel src, |
| long position, long count) |
| throws IOException |
| { |
| // Untrusted target: Use a newly-erased buffer |
| int c = (int)Math.min(count, TRANSFER_SIZE); |
| ByteBuffer bb = Util.getTemporaryDirectBuffer(c); |
| long tw = 0; // Total bytes written |
| long pos = position; |
| try { |
| Util.erase(bb); |
| while (tw < count) { |
| bb.limit((int)Math.min((count - tw), (long)TRANSFER_SIZE)); |
| // ## Bug: Will block reading src if this channel |
| // ## is asynchronously closed |
| int nr = src.read(bb); |
| if (nr <= 0) |
| break; |
| bb.flip(); |
| int nw = write(bb, pos); |
| tw += nw; |
| if (nw != nr) |
| break; |
| pos += nw; |
| bb.clear(); |
| } |
| return tw; |
| } catch (IOException x) { |
| if (tw > 0) |
| return tw; |
| throw x; |
| } finally { |
| Util.releaseTemporaryDirectBuffer(bb); |
| } |
| } |
| |
| public long transferFrom(ReadableByteChannel src, |
| long position, long count) |
| throws IOException |
| { |
| ensureOpen(); |
| if (!src.isOpen()) |
| throw new ClosedChannelException(); |
| if (!writable) |
| throw new NonWritableChannelException(); |
| if ((position < 0) || (count < 0)) |
| throw new IllegalArgumentException(); |
| if (position > size()) |
| return 0; |
| if (src instanceof FileChannelImpl) |
| return transferFromFileChannel((FileChannelImpl)src, |
| position, count); |
| |
| return transferFromArbitraryChannel(src, position, count); |
| } |
| |
| public int read(ByteBuffer dst, long position) throws IOException { |
| if (dst == null) |
| throw new NullPointerException(); |
| if (position < 0) |
| throw new IllegalArgumentException("Negative position"); |
| if (!readable) |
| throw new NonReadableChannelException(); |
| ensureOpen(); |
| if (nd.needsPositionLock()) { |
| synchronized (positionLock) { |
| return readInternal(dst, position); |
| } |
| } else { |
| return readInternal(dst, position); |
| } |
| } |
| |
| private int readInternal(ByteBuffer dst, long position) throws IOException { |
| assert !nd.needsPositionLock() || Thread.holdsLock(positionLock); |
| int n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return -1; |
| do { |
| n = IOUtil.read(fd, dst, position, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| |
| public int write(ByteBuffer src, long position) throws IOException { |
| if (src == null) |
| throw new NullPointerException(); |
| if (position < 0) |
| throw new IllegalArgumentException("Negative position"); |
| if (!writable) |
| throw new NonWritableChannelException(); |
| ensureOpen(); |
| if (nd.needsPositionLock()) { |
| synchronized (positionLock) { |
| return writeInternal(src, position); |
| } |
| } else { |
| return writeInternal(src, position); |
| } |
| } |
| |
| private int writeInternal(ByteBuffer src, long position) throws IOException { |
| assert !nd.needsPositionLock() || Thread.holdsLock(positionLock); |
| int n = 0; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return -1; |
| do { |
| n = IOUtil.write(fd, src, position, nd); |
| } while ((n == IOStatus.INTERRUPTED) && isOpen()); |
| return IOStatus.normalize(n); |
| } finally { |
| threads.remove(ti); |
| end(n > 0); |
| assert IOStatus.check(n); |
| } |
| } |
| |
| |
| // -- Memory-mapped buffers -- |
| |
| private static class Unmapper |
| implements Runnable |
| { |
| // may be required to close file |
| private static final NativeDispatcher nd = new FileDispatcherImpl(); |
| |
| // keep track of mapped buffer usage |
| static volatile int count; |
| static volatile long totalSize; |
| static volatile long totalCapacity; |
| |
| private volatile long address; |
| private final long size; |
| private final int cap; |
| private final FileDescriptor fd; |
| |
| private Unmapper(long address, long size, int cap, |
| FileDescriptor fd) |
| { |
| assert (address != 0); |
| this.address = address; |
| this.size = size; |
| this.cap = cap; |
| this.fd = fd; |
| |
| synchronized (Unmapper.class) { |
| count++; |
| totalSize += size; |
| totalCapacity += cap; |
| } |
| } |
| |
| public void run() { |
| if (address == 0) |
| return; |
| unmap0(address, size); |
| address = 0; |
| |
| // if this mapping has a valid file descriptor then we close it |
| if (fd.valid()) { |
| try { |
| nd.close(fd); |
| } catch (IOException ignore) { |
| // nothing we can do |
| } |
| } |
| |
| synchronized (Unmapper.class) { |
| count--; |
| totalSize -= size; |
| totalCapacity -= cap; |
| } |
| } |
| } |
| |
| private static void unmap(MappedByteBuffer bb) { |
| Cleaner cl = ((DirectBuffer)bb).cleaner(); |
| if (cl != null) |
| cl.clean(); |
| } |
| |
| private static final int MAP_RO = 0; |
| private static final int MAP_RW = 1; |
| private static final int MAP_PV = 2; |
| |
| public MappedByteBuffer map(MapMode mode, long position, long size) |
| throws IOException |
| { |
| ensureOpen(); |
| if (mode == null) |
| throw new NullPointerException("Mode is null"); |
| if (position < 0L) |
| throw new IllegalArgumentException("Negative position"); |
| if (size < 0L) |
| throw new IllegalArgumentException("Negative size"); |
| if (position + size < 0) |
| throw new IllegalArgumentException("Position + size overflow"); |
| if (size > Integer.MAX_VALUE) |
| throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE"); |
| |
| int imode = -1; |
| if (mode == MapMode.READ_ONLY) |
| imode = MAP_RO; |
| else if (mode == MapMode.READ_WRITE) |
| imode = MAP_RW; |
| else if (mode == MapMode.PRIVATE) |
| imode = MAP_PV; |
| assert (imode >= 0); |
| if ((mode != MapMode.READ_ONLY) && !writable) |
| throw new NonWritableChannelException(); |
| if (!readable) |
| throw new NonReadableChannelException(); |
| |
| long addr = -1; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return null; |
| |
| long filesize; |
| do { |
| filesize = nd.size(fd); |
| } while ((filesize == IOStatus.INTERRUPTED) && isOpen()); |
| if (!isOpen()) |
| return null; |
| |
| if (filesize < position + size) { // Extend file size |
| if (!writable) { |
| throw new IOException("Channel not open for writing " + |
| "- cannot extend file to required size"); |
| } |
| int rv; |
| do { |
| rv = nd.truncate(fd, position + size); |
| } while ((rv == IOStatus.INTERRUPTED) && isOpen()); |
| if (!isOpen()) |
| return null; |
| } |
| if (size == 0) { |
| addr = 0; |
| // a valid file descriptor is not required |
| FileDescriptor dummy = new FileDescriptor(); |
| // Android-changed: Allocate a DirectByteBuffer directly. |
| /* |
| if ((!writable) || (imode == MAP_RO)) |
| return Util.newMappedByteBufferR(0, 0, dummy, null); |
| else |
| return Util.newMappedByteBuffer(0, 0, dummy, null); |
| */ |
| return new DirectByteBuffer(0, 0, dummy, null, |
| (!writable) || (imode == MAP_RO) /* readOnly */); |
| } |
| |
| int pagePosition = (int)(position % allocationGranularity); |
| long mapPosition = position - pagePosition; |
| long mapSize = size + pagePosition; |
| try { |
| // Android-added: BlockGuard support. |
| BlockGuard.getThreadPolicy().onReadFromDisk(); |
| // If no exception was thrown from map0, the address is valid |
| addr = map0(imode, mapPosition, mapSize); |
| } catch (OutOfMemoryError x) { |
| // An OutOfMemoryError may indicate that we've exhausted memory |
| // so force gc and re-attempt map |
| System.gc(); |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException y) { |
| Thread.currentThread().interrupt(); |
| } |
| try { |
| addr = map0(imode, mapPosition, mapSize); |
| } catch (OutOfMemoryError y) { |
| // After a second OOME, fail |
| throw new IOException("Map failed", y); |
| } |
| } |
| |
| // On Windows, and potentially other platforms, we need an open |
| // file descriptor for some mapping operations. |
| FileDescriptor mfd; |
| try { |
| mfd = nd.duplicateForMapping(fd); |
| } catch (IOException ioe) { |
| unmap0(addr, mapSize); |
| throw ioe; |
| } |
| |
| assert (IOStatus.checkAll(addr)); |
| assert (addr % allocationGranularity == 0); |
| int isize = (int)size; |
| Unmapper um = new Unmapper(addr, mapSize, isize, mfd); |
| // Android-changed: Allocate a DirectByteBuffer directly. |
| /* |
| if ((!writable) || (imode == MAP_RO)) { |
| return Util.newMappedByteBufferR(isize, |
| addr + pagePosition, |
| mfd, |
| um); |
| } else { |
| return Util.newMappedByteBuffer(isize, |
| addr + pagePosition, |
| mfd, |
| um); |
| } |
| */ |
| return new DirectByteBuffer(isize, addr + pagePosition, mfd, um, |
| (!writable) || (imode == MAP_RO)); |
| } finally { |
| threads.remove(ti); |
| end(IOStatus.checkAll(addr)); |
| } |
| } |
| |
| // Android-removed: Unused method getMappedBufferPool(). |
| /* |
| /** |
| * Invoked by sun.management.ManagementFactoryHelper to create the management |
| * interface for mapped buffers. |
| * |
| public static sun.misc.JavaNioAccess.BufferPool getMappedBufferPool() { |
| return new sun.misc.JavaNioAccess.BufferPool() { |
| @Override |
| public String getName() { |
| return "mapped"; |
| } |
| @Override |
| public long getCount() { |
| return Unmapper.count; |
| } |
| @Override |
| public long getTotalCapacity() { |
| return Unmapper.totalCapacity; |
| } |
| @Override |
| public long getMemoryUsed() { |
| return Unmapper.totalSize; |
| } |
| }; |
| } |
| */ |
| |
| // -- Locks -- |
| |
| |
| |
| // keeps track of locks on this file |
| private volatile FileLockTable fileLockTable; |
| |
| // indicates if file locks are maintained system-wide (as per spec) |
| private static boolean isSharedFileLockTable; |
| |
| // indicates if the disableSystemWideOverlappingFileLockCheck property |
| // has been checked |
| private static volatile boolean propertyChecked; |
| |
| // The lock list in J2SE 1.4/5.0 was local to each FileChannel instance so |
| // the overlap check wasn't system wide when there were multiple channels to |
| // the same file. This property is used to get 1.4/5.0 behavior if desired. |
| private static boolean isSharedFileLockTable() { |
| if (!propertyChecked) { |
| synchronized (FileChannelImpl.class) { |
| if (!propertyChecked) { |
| String value = AccessController.doPrivileged( |
| new GetPropertyAction( |
| "sun.nio.ch.disableSystemWideOverlappingFileLockCheck")); |
| isSharedFileLockTable = ((value == null) || value.equals("false")); |
| propertyChecked = true; |
| } |
| } |
| } |
| return isSharedFileLockTable; |
| } |
| |
| private FileLockTable fileLockTable() throws IOException { |
| if (fileLockTable == null) { |
| synchronized (this) { |
| if (fileLockTable == null) { |
| if (isSharedFileLockTable()) { |
| int ti = threads.add(); |
| try { |
| ensureOpen(); |
| fileLockTable = FileLockTable.newSharedFileLockTable(this, fd); |
| } finally { |
| threads.remove(ti); |
| } |
| } else { |
| fileLockTable = new SimpleFileLockTable(); |
| } |
| } |
| } |
| } |
| return fileLockTable; |
| } |
| |
| public FileLock lock(long position, long size, boolean shared) |
| throws IOException |
| { |
| ensureOpen(); |
| if (shared && !readable) |
| throw new NonReadableChannelException(); |
| if (!shared && !writable) |
| throw new NonWritableChannelException(); |
| FileLockImpl fli = new FileLockImpl(this, position, size, shared); |
| FileLockTable flt = fileLockTable(); |
| flt.add(fli); |
| boolean completed = false; |
| int ti = -1; |
| try { |
| begin(); |
| ti = threads.add(); |
| if (!isOpen()) |
| return null; |
| int n; |
| do { |
| n = nd.lock(fd, true, position, size, shared); |
| } while ((n == FileDispatcher.INTERRUPTED) && isOpen()); |
| if (isOpen()) { |
| if (n == FileDispatcher.RET_EX_LOCK) { |
| assert shared; |
| FileLockImpl fli2 = new FileLockImpl(this, position, size, |
| false); |
| flt.replace(fli, fli2); |
| fli = fli2; |
| } |
| completed = true; |
| } |
| } finally { |
| if (!completed) |
| flt.remove(fli); |
| threads.remove(ti); |
| try { |
| end(completed); |
| } catch (ClosedByInterruptException e) { |
| throw new FileLockInterruptionException(); |
| } |
| } |
| return fli; |
| } |
| |
| public FileLock tryLock(long position, long size, boolean shared) |
| throws IOException |
| { |
| ensureOpen(); |
| if (shared && !readable) |
| throw new NonReadableChannelException(); |
| if (!shared && !writable) |
| throw new NonWritableChannelException(); |
| FileLockImpl fli = new FileLockImpl(this, position, size, shared); |
| FileLockTable flt = fileLockTable(); |
| flt.add(fli); |
| int result; |
| |
| int ti = threads.add(); |
| try { |
| try { |
| ensureOpen(); |
| result = nd.lock(fd, false, position, size, shared); |
| } catch (IOException e) { |
| flt.remove(fli); |
| throw e; |
| } |
| if (result == FileDispatcher.NO_LOCK) { |
| flt.remove(fli); |
| return null; |
| } |
| if (result == FileDispatcher.RET_EX_LOCK) { |
| assert shared; |
| FileLockImpl fli2 = new FileLockImpl(this, position, size, |
| false); |
| flt.replace(fli, fli2); |
| return fli2; |
| } |
| return fli; |
| } finally { |
| threads.remove(ti); |
| } |
| } |
| |
| void release(FileLockImpl fli) throws IOException { |
| int ti = threads.add(); |
| try { |
| ensureOpen(); |
| nd.release(fd, fli.position(), fli.size()); |
| } finally { |
| threads.remove(ti); |
| } |
| assert fileLockTable != null; |
| fileLockTable.remove(fli); |
| } |
| |
| // -- File lock support -- |
| |
| /** |
| * A simple file lock table that maintains a list of FileLocks obtained by a |
| * FileChannel. Use to get 1.4/5.0 behaviour. |
| */ |
| private static class SimpleFileLockTable extends FileLockTable { |
| // synchronize on list for access |
| private final List<FileLock> lockList = new ArrayList<FileLock>(2); |
| |
| public SimpleFileLockTable() { |
| } |
| |
| private void checkList(long position, long size) |
| throws OverlappingFileLockException |
| { |
| assert Thread.holdsLock(lockList); |
| for (FileLock fl: lockList) { |
| if (fl.overlaps(position, size)) { |
| throw new OverlappingFileLockException(); |
| } |
| } |
| } |
| |
| public void add(FileLock fl) throws OverlappingFileLockException { |
| synchronized (lockList) { |
| checkList(fl.position(), fl.size()); |
| lockList.add(fl); |
| } |
| } |
| |
| public void remove(FileLock fl) { |
| synchronized (lockList) { |
| lockList.remove(fl); |
| } |
| } |
| |
| public List<FileLock> removeAll() { |
| synchronized(lockList) { |
| List<FileLock> result = new ArrayList<FileLock>(lockList); |
| lockList.clear(); |
| return result; |
| } |
| } |
| |
| public void replace(FileLock fl1, FileLock fl2) { |
| synchronized (lockList) { |
| lockList.remove(fl1); |
| lockList.add(fl2); |
| } |
| } |
| } |
| |
| // -- Native methods -- |
| |
| // Creates a new mapping |
| private native long map0(int prot, long position, long length) |
| throws IOException; |
| |
| // Removes an existing mapping |
| private static native int unmap0(long address, long length); |
| |
| // Transfers from src to dst, or returns -2 if kernel can't do that |
| private native long transferTo0(FileDescriptor src, long position, |
| long count, FileDescriptor dst); |
| |
| // Sets or reports this file's position |
| // If offset is -1, the current position is returned |
| // otherwise the position is set to offset |
| private native long position0(FileDescriptor fd, long offset); |
| |
| // Caches fieldIDs |
| private static native long initIDs(); |
| |
| static { |
| // Android-removed: Move clinit code to JNI registration functions. |
| // IOUtil.load(); |
| allocationGranularity = initIDs(); |
| } |
| |
| } |