/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.nio; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.channels.OverlappingFileLockException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.StructFlock; import libcore.util.MutableLong; import static libcore.io.OsConstants.*; /** * Our concrete implementation of the abstract FileChannel class. */ final class FileChannelImpl extends FileChannel { private static final Comparator<FileLock> LOCK_COMPARATOR = new Comparator<FileLock>() { public int compare(FileLock lock1, FileLock lock2) { long position1 = lock1.position(); long position2 = lock2.position(); return position1 > position2 ? 1 : (position1 < position2 ? -1 : 0); } }; private final Object stream; private final FileDescriptor fd; private final int mode; // The set of acquired and pending locks. private final SortedSet<FileLock> locks = new TreeSet<FileLock>(LOCK_COMPARATOR); /** * Create a new file channel implementation class that wraps the given * fd and operates in the specified mode. */ public FileChannelImpl(Object stream, FileDescriptor fd, int mode) { this.fd = fd; this.stream = stream; this.mode = mode; } private void checkOpen() throws ClosedChannelException { if (!isOpen()) { throw new ClosedChannelException(); } } private void checkReadable() { if ((mode & O_ACCMODE) == O_WRONLY) { throw new NonReadableChannelException(); } } private void checkWritable() { if ((mode & O_ACCMODE) == O_RDONLY) { throw new NonWritableChannelException(); } } protected void implCloseChannel() throws IOException { if (stream instanceof Closeable) { ((Closeable) stream).close(); } } private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException { int accessMode = (mode & O_ACCMODE); if (accessMode == O_RDONLY) { if (!shared) { throw new NonWritableChannelException(); } } else if (accessMode == O_WRONLY) { if (shared) { throw new NonReadableChannelException(); } } if (position < 0 || size < 0) { throw new IllegalArgumentException("position=" + position + " size=" + size); } FileLock pendingLock = new FileLockImpl(this, position, size, shared); addLock(pendingLock); StructFlock flock = new StructFlock(); flock.l_type = (short) (shared ? F_RDLCK : F_WRLCK); flock.l_whence = (short) SEEK_SET; flock.l_start = position; flock.l_len = translateLockLength(size); boolean success = false; try { success = (Libcore.os.fcntlFlock(fd, wait ? F_SETLKW64 : F_SETLK64, flock) != -1); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } finally { if (!success) { removeLock(pendingLock); } } return success ? pendingLock : null; } private static long translateLockLength(long byteCount) { // FileChannel uses Long.MAX_VALUE to mean "lock the whole file" where POSIX uses 0. return (byteCount == Long.MAX_VALUE) ? 0 : byteCount; } private static final class FileLockImpl extends FileLock { private boolean isReleased = false; public FileLockImpl(FileChannel channel, long position, long size, boolean shared) { super(channel, position, size, shared); } public boolean isValid() { return !isReleased && channel().isOpen(); } public void release() throws IOException { if (!channel().isOpen()) { throw new ClosedChannelException(); } if (!isReleased) { ((FileChannelImpl) channel()).release(this); isReleased = true; } } } public final FileLock lock(long position, long size, boolean shared) throws IOException { checkOpen(); FileLock resultLock = null; { boolean completed = false; try { begin(); resultLock = basicLock(position, size, shared, true); completed = true; } finally { end(completed); } } return resultLock; } public final FileLock tryLock(long position, long size, boolean shared) throws IOException { checkOpen(); return basicLock(position, size, shared, false); } /** * Non-API method to release a given lock on a file channel. Assumes that * the lock will mark itself invalid after successful unlocking. */ public void release(FileLock lock) throws IOException { checkOpen(); StructFlock flock = new StructFlock(); flock.l_type = (short) F_UNLCK; flock.l_whence = (short) SEEK_SET; flock.l_start = lock.position(); flock.l_len = translateLockLength(lock.size()); try { Libcore.os.fcntlFlock(fd, F_SETLKW64, flock); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } removeLock(lock); } public void force(boolean metadata) throws IOException { checkOpen(); if ((mode & O_ACCMODE) != O_RDONLY) { try { if (metadata) { Libcore.os.fsync(fd); } else { Libcore.os.fdatasync(fd); } } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } } } public final MappedByteBuffer map(MapMode mapMode, long position, long size) throws IOException { checkOpen(); if (mapMode == null) { throw new NullPointerException("mapMode == null"); } if (position < 0 || size < 0 || size > Integer.MAX_VALUE) { throw new IllegalArgumentException("position=" + position + " size=" + size); } int accessMode = (mode & O_ACCMODE); if (accessMode == O_RDONLY) { if (mapMode != MapMode.READ_ONLY) { throw new NonWritableChannelException(); } } else if (accessMode == O_WRONLY) { throw new NonReadableChannelException(); } if (position + size > size()) { // We can't defer to FileChannel.truncate because that will only make a file shorter, // and we only care about making our backing file longer here. try { Libcore.os.ftruncate(fd, position + size); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } } long alignment = position - position % Libcore.os.sysconf(_SC_PAGE_SIZE); int offset = (int) (position - alignment); MemoryBlock block = MemoryBlock.mmap(fd, alignment, size + offset, mapMode); return new MappedByteBufferAdapter(block, (int) size, offset, mapMode); } public long position() throws IOException { checkOpen(); try { return Libcore.os.lseek(fd, 0L, SEEK_CUR); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } } public FileChannel position(long newPosition) throws IOException { checkOpen(); if (newPosition < 0) { throw new IllegalArgumentException("position: " + newPosition); } try { Libcore.os.lseek(fd, newPosition, SEEK_SET); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } return this; } public int read(ByteBuffer buffer, long position) throws IOException { if (position < 0) { throw new IllegalArgumentException("position: " + position); } return readImpl(buffer, position); } public int read(ByteBuffer buffer) throws IOException { return readImpl(buffer, -1); } private int readImpl(ByteBuffer buffer, long position) throws IOException { buffer.checkWritable(); checkOpen(); checkReadable(); if (!buffer.hasRemaining()) { return 0; } int bytesRead = 0; boolean completed = false; try { begin(); try { if (position == -1) { bytesRead = Libcore.os.read(fd, buffer); } else { bytesRead = Libcore.os.pread(fd, buffer, position); } if (bytesRead == 0) { bytesRead = -1; } } catch (ErrnoException errnoException) { if (errnoException.errno == EAGAIN) { // We don't throw if we try to read from an empty non-blocking pipe. bytesRead = 0; } else { throw errnoException.rethrowAsIOException(); } } completed = true; } finally { end(completed && bytesRead >= 0); } if (bytesRead > 0) { buffer.position(buffer.position() + bytesRead); } return bytesRead; } private int transferIoVec(IoVec ioVec) throws IOException { if (ioVec.init() == 0) { return 0; } int bytesTransferred = 0; boolean completed = false; try { begin(); bytesTransferred = ioVec.doTransfer(fd); completed = true; } finally { end(completed); } ioVec.didTransfer(bytesTransferred); return bytesTransferred; } public long read(ByteBuffer[] buffers, int offset, int length) throws IOException { Arrays.checkOffsetAndCount(buffers.length, offset, length); checkOpen(); checkReadable(); return transferIoVec(new IoVec(buffers, offset, length, IoVec.Direction.READV)); } public long size() throws IOException { checkOpen(); try { return Libcore.os.fstat(fd).st_size; } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } } public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { checkOpen(); if (!src.isOpen()) { throw new ClosedChannelException(); } checkWritable(); if (position < 0 || count < 0 || count > Integer.MAX_VALUE) { throw new IllegalArgumentException("position=" + position + " count=" + count); } if (position > size()) { return 0; } // Although sendfile(2) originally supported writing to a regular file. // In Linux 2.6 and later, it only supports writing to sockets. // If our source is a regular file, mmap(2) rather than reading. // Callers should only be using transferFrom for large transfers, // so the mmap(2) overhead isn't a concern. if (src instanceof FileChannel) { FileChannel fileSrc = (FileChannel) src; long size = fileSrc.size(); long filePosition = fileSrc.position(); count = Math.min(count, size - filePosition); ByteBuffer buffer = fileSrc.map(MapMode.READ_ONLY, filePosition, count); try { fileSrc.position(filePosition + count); return write(buffer, position); } finally { NioUtils.freeDirectBuffer(buffer); } } // For non-file channels, all we can do is read and write via userspace. ByteBuffer buffer = ByteBuffer.allocate((int) count); src.read(buffer); buffer.flip(); return write(buffer, position); } public long transferTo(long position, long count, WritableByteChannel target) throws IOException { checkOpen(); if (!target.isOpen()) { throw new ClosedChannelException(); } checkReadable(); if (target instanceof FileChannelImpl) { ((FileChannelImpl) target).checkWritable(); } if (position < 0 || count < 0) { throw new IllegalArgumentException("position=" + position + " count=" + count); } if (count == 0 || position >= size()) { return 0; } count = Math.min(count, size() - position); // Try sendfile(2) first... boolean completed = false; if (target instanceof SocketChannelImpl) { FileDescriptor outFd = ((SocketChannelImpl) target).getFD(); try { begin(); try { MutableLong offset = new MutableLong(position); long rc = Libcore.os.sendfile(outFd, fd, offset, count); completed = true; return rc; } catch (ErrnoException errnoException) { // If the OS doesn't support what we asked for, we want to fall through and // try a different approach. If it does support it, but it failed, we're done. if (errnoException.errno != ENOSYS && errnoException.errno != EINVAL) { throw errnoException.rethrowAsIOException(); } } } finally { end(completed); } } // ...fall back to write(2). ByteBuffer buffer = null; try { buffer = map(MapMode.READ_ONLY, position, count); return target.write(buffer); } finally { NioUtils.freeDirectBuffer(buffer); } } public FileChannel truncate(long size) throws IOException { checkOpen(); if (size < 0) { throw new IllegalArgumentException("size: " + size); } checkWritable(); if (size < size()) { try { Libcore.os.ftruncate(fd, size); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } } return this; } public int write(ByteBuffer buffer, long position) throws IOException { if (position < 0) { throw new IllegalArgumentException("position: " + position); } return writeImpl(buffer, position); } public int write(ByteBuffer buffer) throws IOException { return writeImpl(buffer, -1); } private int writeImpl(ByteBuffer buffer, long position) throws IOException { checkOpen(); checkWritable(); if (buffer == null) { throw new NullPointerException("buffer == null"); } if (!buffer.hasRemaining()) { return 0; } int bytesWritten = 0; boolean completed = false; try { begin(); try { if (position == -1) { bytesWritten = Libcore.os.write(fd, buffer); } else { bytesWritten = Libcore.os.pwrite(fd, buffer, position); } } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } completed = true; } finally { end(completed); } if (bytesWritten > 0) { buffer.position(buffer.position() + bytesWritten); } return bytesWritten; } public long write(ByteBuffer[] buffers, int offset, int length) throws IOException { Arrays.checkOffsetAndCount(buffers.length, offset, length); checkOpen(); checkWritable(); return transferIoVec(new IoVec(buffers, offset, length, IoVec.Direction.WRITEV)); } /** * @param copyingIn true if we're copying data into the buffers (typically * because the caller is a file/network read operation), false if we're * copying data out of the buffers (for a file/network write operation). */ static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length, boolean copyingIn) { int count = 0; for (int i = offset; i < offset + length; ++i) { count += buffers[i].remaining(); if (copyingIn) { buffers[i].checkWritable(); } } return count; } public FileDescriptor getFD() { return fd; } /** * Add a new pending lock to the manager. Throws an exception if the lock * would overlap an existing lock. Once the lock is acquired it remains in * this set as an acquired lock. */ private synchronized void addLock(FileLock lock) throws OverlappingFileLockException { long lockEnd = lock.position() + lock.size(); for (FileLock existingLock : locks) { if (existingLock.position() > lockEnd) { // This, and all remaining locks, start beyond our end (so // cannot overlap). break; } if (existingLock.overlaps(lock.position(), lock.size())) { throw new OverlappingFileLockException(); } } locks.add(lock); } /** * Removes an acquired lock from the lock manager. If the lock did not exist * in the lock manager the operation is a no-op. */ private synchronized void removeLock(FileLock lock) { locks.remove(lock); } }