Home | History | Annotate | Download | only in nio
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package java.nio;
     19 
     20 import java.io.Closeable;
     21 import java.io.FileDescriptor;
     22 import java.io.IOException;
     23 import java.nio.channels.ClosedChannelException;
     24 import java.nio.channels.FileChannel;
     25 import java.nio.channels.FileLock;
     26 import java.nio.channels.NonReadableChannelException;
     27 import java.nio.channels.NonWritableChannelException;
     28 import java.nio.channels.OverlappingFileLockException;
     29 import java.nio.channels.ReadableByteChannel;
     30 import java.nio.channels.WritableByteChannel;
     31 import java.util.Arrays;
     32 import java.util.Comparator;
     33 import java.util.SortedSet;
     34 import java.util.TreeSet;
     35 import libcore.io.ErrnoException;
     36 import libcore.io.Libcore;
     37 import libcore.io.StructFlock;
     38 import libcore.util.MutableLong;
     39 import static libcore.io.OsConstants.*;
     40 
     41 /**
     42  * Our concrete implementation of the abstract FileChannel class.
     43  */
     44 final class FileChannelImpl extends FileChannel {
     45     private static final Comparator<FileLock> LOCK_COMPARATOR = new Comparator<FileLock>() {
     46         public int compare(FileLock lock1, FileLock lock2) {
     47             long position1 = lock1.position();
     48             long position2 = lock2.position();
     49             return position1 > position2 ? 1 : (position1 < position2 ? -1 : 0);
     50         }
     51     };
     52 
     53     private final Object stream;
     54     private final FileDescriptor fd;
     55     private final int mode;
     56 
     57     // The set of acquired and pending locks.
     58     private final SortedSet<FileLock> locks = new TreeSet<FileLock>(LOCK_COMPARATOR);
     59 
     60     /**
     61      * Create a new file channel implementation class that wraps the given
     62      * fd and operates in the specified mode.
     63      */
     64     public FileChannelImpl(Object stream, FileDescriptor fd, int mode) {
     65         this.fd = fd;
     66         this.stream = stream;
     67         this.mode = mode;
     68     }
     69 
     70     private void checkOpen() throws ClosedChannelException {
     71         if (!isOpen()) {
     72             throw new ClosedChannelException();
     73         }
     74     }
     75 
     76     private void checkReadable() {
     77         if ((mode & O_ACCMODE) == O_WRONLY) {
     78             throw new NonReadableChannelException();
     79         }
     80     }
     81 
     82     private void checkWritable() {
     83         if ((mode & O_ACCMODE) == O_RDONLY) {
     84             throw new NonWritableChannelException();
     85         }
     86     }
     87 
     88     protected void implCloseChannel() throws IOException {
     89         if (stream instanceof Closeable) {
     90             ((Closeable) stream).close();
     91         }
     92     }
     93 
     94     private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException {
     95         int accessMode = (mode & O_ACCMODE);
     96         if (accessMode == O_RDONLY) {
     97             if (!shared) {
     98                 throw new NonWritableChannelException();
     99             }
    100         } else if (accessMode == O_WRONLY) {
    101             if (shared) {
    102                 throw new NonReadableChannelException();
    103             }
    104         }
    105 
    106         if (position < 0 || size < 0) {
    107             throw new IllegalArgumentException("position=" + position + " size=" + size);
    108         }
    109 
    110         FileLock pendingLock = new FileLockImpl(this, position, size, shared);
    111         addLock(pendingLock);
    112 
    113         StructFlock flock = new StructFlock();
    114         flock.l_type = (short) (shared ? F_RDLCK : F_WRLCK);
    115         flock.l_whence = (short) SEEK_SET;
    116         flock.l_start = position;
    117         flock.l_len = translateLockLength(size);
    118 
    119         boolean success = false;
    120         try {
    121             success = (Libcore.os.fcntlFlock(fd, wait ? F_SETLKW64 : F_SETLK64, flock) != -1);
    122         } catch (ErrnoException errnoException) {
    123             throw errnoException.rethrowAsIOException();
    124         } finally {
    125             if (!success) {
    126                 removeLock(pendingLock);
    127             }
    128         }
    129         return success ? pendingLock : null;
    130     }
    131 
    132     private static long translateLockLength(long byteCount) {
    133         // FileChannel uses Long.MAX_VALUE to mean "lock the whole file" where POSIX uses 0.
    134         return (byteCount == Long.MAX_VALUE) ? 0 : byteCount;
    135     }
    136 
    137     private static final class FileLockImpl extends FileLock {
    138         private boolean isReleased = false;
    139 
    140         public FileLockImpl(FileChannel channel, long position, long size, boolean shared) {
    141             super(channel, position, size, shared);
    142         }
    143 
    144         public boolean isValid() {
    145             return !isReleased && channel().isOpen();
    146         }
    147 
    148         public void release() throws IOException {
    149             if (!channel().isOpen()) {
    150                 throw new ClosedChannelException();
    151             }
    152             if (!isReleased) {
    153                 ((FileChannelImpl) channel()).release(this);
    154                 isReleased = true;
    155             }
    156         }
    157     }
    158 
    159     public final FileLock lock(long position, long size, boolean shared) throws IOException {
    160         checkOpen();
    161         FileLock resultLock = null;
    162         {
    163             boolean completed = false;
    164             try {
    165                 begin();
    166                 resultLock = basicLock(position, size, shared, true);
    167                 completed = true;
    168             } finally {
    169                 end(completed);
    170             }
    171         }
    172         return resultLock;
    173     }
    174 
    175     public final FileLock tryLock(long position, long size, boolean shared) throws IOException {
    176         checkOpen();
    177         return basicLock(position, size, shared, false);
    178     }
    179 
    180     /**
    181      * Non-API method to release a given lock on a file channel. Assumes that
    182      * the lock will mark itself invalid after successful unlocking.
    183      */
    184     public void release(FileLock lock) throws IOException {
    185         checkOpen();
    186 
    187         StructFlock flock = new StructFlock();
    188         flock.l_type = (short) F_UNLCK;
    189         flock.l_whence = (short) SEEK_SET;
    190         flock.l_start = lock.position();
    191         flock.l_len = translateLockLength(lock.size());
    192         try {
    193             Libcore.os.fcntlFlock(fd, F_SETLKW64, flock);
    194         } catch (ErrnoException errnoException) {
    195             throw errnoException.rethrowAsIOException();
    196         }
    197 
    198         removeLock(lock);
    199     }
    200 
    201     public void force(boolean metadata) throws IOException {
    202         checkOpen();
    203         if ((mode & O_ACCMODE) != O_RDONLY) {
    204             try {
    205                 if (metadata) {
    206                     Libcore.os.fsync(fd);
    207                 } else {
    208                     Libcore.os.fdatasync(fd);
    209                 }
    210             } catch (ErrnoException errnoException) {
    211                 throw errnoException.rethrowAsIOException();
    212             }
    213         }
    214     }
    215 
    216     public final MappedByteBuffer map(MapMode mapMode, long position, long size) throws IOException {
    217         checkOpen();
    218         if (mapMode == null) {
    219             throw new NullPointerException("mapMode == null");
    220         }
    221         if (position < 0 || size < 0 || size > Integer.MAX_VALUE) {
    222             throw new IllegalArgumentException("position=" + position + " size=" + size);
    223         }
    224         int accessMode = (mode & O_ACCMODE);
    225         if (accessMode == O_RDONLY) {
    226             if (mapMode != MapMode.READ_ONLY) {
    227                 throw new NonWritableChannelException();
    228             }
    229         } else if (accessMode == O_WRONLY) {
    230             throw new NonReadableChannelException();
    231         }
    232         if (position + size > size()) {
    233             // We can't defer to FileChannel.truncate because that will only make a file shorter,
    234             // and we only care about making our backing file longer here.
    235             try {
    236                 Libcore.os.ftruncate(fd, position + size);
    237             } catch (ErrnoException ftruncateException) {
    238                 // EINVAL can be thrown if we're dealing with non-regular
    239                 // files, for example, character devices such as /dev/zero.
    240                 // In those cases, we ignore the failed truncation and
    241                 // continue on.
    242                 try {
    243                     if (S_ISREG(Libcore.os.fstat(fd).st_mode) || ftruncateException.errno != EINVAL) {
    244                         throw ftruncateException.rethrowAsIOException();
    245                     }
    246                 } catch (ErrnoException fstatException) {
    247                     throw fstatException.rethrowAsIOException();
    248                 }
    249             }
    250         }
    251         long alignment = position - position % Libcore.os.sysconf(_SC_PAGE_SIZE);
    252         int offset = (int) (position - alignment);
    253         MemoryBlock block = MemoryBlock.mmap(fd, alignment, size + offset, mapMode);
    254         return new DirectByteBuffer(block, (int) size, offset, (mapMode == MapMode.READ_ONLY), mapMode);
    255     }
    256 
    257     public long position() throws IOException {
    258         checkOpen();
    259         try {
    260             return Libcore.os.lseek(fd, 0L, SEEK_CUR);
    261         } catch (ErrnoException errnoException) {
    262             throw errnoException.rethrowAsIOException();
    263         }
    264     }
    265 
    266     public FileChannel position(long newPosition) throws IOException {
    267         checkOpen();
    268         if (newPosition < 0) {
    269             throw new IllegalArgumentException("position: " + newPosition);
    270         }
    271         try {
    272             Libcore.os.lseek(fd, newPosition, SEEK_SET);
    273         } catch (ErrnoException errnoException) {
    274             throw errnoException.rethrowAsIOException();
    275         }
    276         return this;
    277     }
    278 
    279     public int read(ByteBuffer buffer, long position) throws IOException {
    280         if (position < 0) {
    281             throw new IllegalArgumentException("position: " + position);
    282         }
    283         return readImpl(buffer, position);
    284     }
    285 
    286     public int read(ByteBuffer buffer) throws IOException {
    287         return readImpl(buffer, -1);
    288     }
    289 
    290     private int readImpl(ByteBuffer buffer, long position) throws IOException {
    291         buffer.checkWritable();
    292         checkOpen();
    293         checkReadable();
    294         if (!buffer.hasRemaining()) {
    295             return 0;
    296         }
    297         int bytesRead = 0;
    298         boolean completed = false;
    299         try {
    300             begin();
    301             try {
    302                 if (position == -1) {
    303                     bytesRead = Libcore.os.read(fd, buffer);
    304                 } else {
    305                     bytesRead = Libcore.os.pread(fd, buffer, position);
    306                 }
    307                 if (bytesRead == 0) {
    308                     bytesRead = -1;
    309                 }
    310             } catch (ErrnoException errnoException) {
    311                 if (errnoException.errno == EAGAIN) {
    312                     // We don't throw if we try to read from an empty non-blocking pipe.
    313                     bytesRead = 0;
    314                 } else {
    315                     throw errnoException.rethrowAsIOException();
    316                 }
    317             }
    318             completed = true;
    319         } finally {
    320             end(completed && bytesRead >= 0);
    321         }
    322         if (bytesRead > 0) {
    323             buffer.position(buffer.position() + bytesRead);
    324         }
    325         return bytesRead;
    326     }
    327 
    328     private int transferIoVec(IoVec ioVec) throws IOException {
    329         if (ioVec.init() == 0) {
    330             return 0;
    331         }
    332         int bytesTransferred = 0;
    333         boolean completed = false;
    334         try {
    335             begin();
    336             bytesTransferred = ioVec.doTransfer(fd);
    337             completed = true;
    338         } finally {
    339             end(completed);
    340         }
    341         ioVec.didTransfer(bytesTransferred);
    342         return bytesTransferred;
    343     }
    344 
    345     public long read(ByteBuffer[] buffers, int offset, int length) throws IOException {
    346         Arrays.checkOffsetAndCount(buffers.length, offset, length);
    347         checkOpen();
    348         checkReadable();
    349         return transferIoVec(new IoVec(buffers, offset, length, IoVec.Direction.READV));
    350     }
    351 
    352     public long size() throws IOException {
    353         checkOpen();
    354         try {
    355             return Libcore.os.fstat(fd).st_size;
    356         } catch (ErrnoException errnoException) {
    357             throw errnoException.rethrowAsIOException();
    358         }
    359     }
    360 
    361     public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
    362         checkOpen();
    363         if (!src.isOpen()) {
    364             throw new ClosedChannelException();
    365         }
    366         checkWritable();
    367         if (position < 0 || count < 0 || count > Integer.MAX_VALUE) {
    368             throw new IllegalArgumentException("position=" + position + " count=" + count);
    369         }
    370         if (position > size()) {
    371             return 0;
    372         }
    373 
    374         // Although sendfile(2) originally supported writing to a regular file.
    375         // In Linux 2.6 and later, it only supports writing to sockets.
    376 
    377         // If our source is a regular file, mmap(2) rather than reading.
    378         // Callers should only be using transferFrom for large transfers,
    379         // so the mmap(2) overhead isn't a concern.
    380         if (src instanceof FileChannel) {
    381             FileChannel fileSrc = (FileChannel) src;
    382             long size = fileSrc.size();
    383             long filePosition = fileSrc.position();
    384             count = Math.min(count, size - filePosition);
    385             ByteBuffer buffer = fileSrc.map(MapMode.READ_ONLY, filePosition, count);
    386             try {
    387                 fileSrc.position(filePosition + count);
    388                 return write(buffer, position);
    389             } finally {
    390                 NioUtils.freeDirectBuffer(buffer);
    391             }
    392         }
    393 
    394         // For non-file channels, all we can do is read and write via userspace.
    395         ByteBuffer buffer = ByteBuffer.allocate((int) count);
    396         src.read(buffer);
    397         buffer.flip();
    398         return write(buffer, position);
    399     }
    400 
    401     public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
    402         checkOpen();
    403         if (!target.isOpen()) {
    404             throw new ClosedChannelException();
    405         }
    406         checkReadable();
    407         if (target instanceof FileChannelImpl) {
    408             ((FileChannelImpl) target).checkWritable();
    409         }
    410         if (position < 0 || count < 0) {
    411             throw new IllegalArgumentException("position=" + position + " count=" + count);
    412         }
    413 
    414         if (count == 0 || position >= size()) {
    415             return 0;
    416         }
    417         count = Math.min(count, size() - position);
    418 
    419         // Try sendfile(2) first...
    420         boolean completed = false;
    421         if (target instanceof SocketChannelImpl) {
    422             FileDescriptor outFd = ((SocketChannelImpl) target).getFD();
    423             try {
    424                 begin();
    425                 try {
    426                     MutableLong offset = new MutableLong(position);
    427                     long rc = Libcore.os.sendfile(outFd, fd, offset, count);
    428                     completed = true;
    429                     return rc;
    430                 } catch (ErrnoException errnoException) {
    431                     // If the OS doesn't support what we asked for, we want to fall through and
    432                     // try a different approach. If it does support it, but it failed, we're done.
    433                     if (errnoException.errno != ENOSYS && errnoException.errno != EINVAL) {
    434                         throw errnoException.rethrowAsIOException();
    435                     }
    436                 }
    437             } finally {
    438                 end(completed);
    439             }
    440         }
    441         // ...fall back to write(2).
    442         ByteBuffer buffer = null;
    443         try {
    444             buffer = map(MapMode.READ_ONLY, position, count);
    445             return target.write(buffer);
    446         } finally {
    447             NioUtils.freeDirectBuffer(buffer);
    448         }
    449     }
    450 
    451     public FileChannel truncate(long size) throws IOException {
    452         checkOpen();
    453         if (size < 0) {
    454             throw new IllegalArgumentException("size < 0: " + size);
    455         }
    456         checkWritable();
    457         if (size < size()) {
    458             try {
    459                 Libcore.os.ftruncate(fd, size);
    460             } catch (ErrnoException errnoException) {
    461                 throw errnoException.rethrowAsIOException();
    462             }
    463         }
    464         return this;
    465     }
    466 
    467     public int write(ByteBuffer buffer, long position) throws IOException {
    468         if (position < 0) {
    469             throw new IllegalArgumentException("position < 0: " + position);
    470         }
    471         return writeImpl(buffer, position);
    472     }
    473 
    474     public int write(ByteBuffer buffer) throws IOException {
    475         return writeImpl(buffer, -1);
    476     }
    477 
    478     private int writeImpl(ByteBuffer buffer, long position) throws IOException {
    479         checkOpen();
    480         checkWritable();
    481         if (buffer == null) {
    482             throw new NullPointerException("buffer == null");
    483         }
    484         if (!buffer.hasRemaining()) {
    485             return 0;
    486         }
    487         int bytesWritten = 0;
    488         boolean completed = false;
    489         try {
    490             begin();
    491             try {
    492                 if (position == -1) {
    493                     bytesWritten = Libcore.os.write(fd, buffer);
    494                 } else {
    495                     bytesWritten = Libcore.os.pwrite(fd, buffer, position);
    496                 }
    497             } catch (ErrnoException errnoException) {
    498                 throw errnoException.rethrowAsIOException();
    499             }
    500             completed = true;
    501         } finally {
    502             end(completed);
    503         }
    504         if (bytesWritten > 0) {
    505             buffer.position(buffer.position() + bytesWritten);
    506         }
    507         return bytesWritten;
    508     }
    509 
    510     public long write(ByteBuffer[] buffers, int offset, int length) throws IOException {
    511         Arrays.checkOffsetAndCount(buffers.length, offset, length);
    512         checkOpen();
    513         checkWritable();
    514         return transferIoVec(new IoVec(buffers, offset, length, IoVec.Direction.WRITEV));
    515     }
    516 
    517     /**
    518      * @param copyingIn true if we're copying data into the buffers (typically
    519      * because the caller is a file/network read operation), false if we're
    520      * copying data out of the buffers (for a file/network write operation).
    521      */
    522     static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length, boolean copyingIn) {
    523         int count = 0;
    524         for (int i = offset; i < offset + length; ++i) {
    525             count += buffers[i].remaining();
    526             if (copyingIn) {
    527                 buffers[i].checkWritable();
    528             }
    529         }
    530         return count;
    531     }
    532 
    533     public FileDescriptor getFD() {
    534         return fd;
    535     }
    536 
    537     /**
    538      * Add a new pending lock to the manager. Throws an exception if the lock
    539      * would overlap an existing lock. Once the lock is acquired it remains in
    540      * this set as an acquired lock.
    541      */
    542     private synchronized void addLock(FileLock lock) throws OverlappingFileLockException {
    543         long lockEnd = lock.position() + lock.size();
    544         for (FileLock existingLock : locks) {
    545             if (existingLock.position() > lockEnd) {
    546                 // This, and all remaining locks, start beyond our end (so
    547                 // cannot overlap).
    548                 break;
    549             }
    550             if (existingLock.overlaps(lock.position(), lock.size())) {
    551                 throw new OverlappingFileLockException();
    552             }
    553         }
    554         locks.add(lock);
    555     }
    556 
    557     /**
    558      * Removes an acquired lock from the lock manager. If the lock did not exist
    559      * in the lock manager the operation is a no-op.
    560      */
    561     private synchronized void removeLock(FileLock lock) {
    562         locks.remove(lock);
    563     }
    564 }
    565