FileChannelImpl.java revision 6ab5999b58777725b4556e4d81bdec56b6d6c182
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
18package java.nio;
19
20import java.io.Closeable;
21import java.io.FileDescriptor;
22import java.io.IOException;
23import java.nio.channels.ClosedChannelException;
24import java.nio.channels.FileChannel;
25import java.nio.channels.FileLock;
26import java.nio.channels.NonReadableChannelException;
27import java.nio.channels.NonWritableChannelException;
28import java.nio.channels.OverlappingFileLockException;
29import java.nio.channels.ReadableByteChannel;
30import java.nio.channels.WritableByteChannel;
31import java.util.Arrays;
32import java.util.Comparator;
33import java.util.SortedSet;
34import java.util.TreeSet;
35import org.apache.harmony.luni.platform.Platform;
36import static libcore.io.OsConstants.*;
37
38/*
39 * The file channel impl class is the bridge between the logical channels
40 * described by the NIO channel framework, and the file system implementation
41 * provided by the port layer.
42 *
43 * This class is non-API, but implements the API of the FileChannel interface.
44 */
45final class FileChannelImpl extends FileChannel {
46    private static final int ALLOC_GRANULARITY = Platform.FILE_SYSTEM.getAllocGranularity();
47
48    private static final Comparator<FileLock> LOCK_COMPARATOR = new Comparator<FileLock>() {
49        public int compare(FileLock lock1, FileLock lock2) {
50            long position1 = lock1.position();
51            long position2 = lock2.position();
52            return position1 > position2 ? 1 : (position1 < position2 ? -1 : 0);
53        }
54    };
55
56    private final Object stream;
57    private final int fd;
58    private final int mode;
59
60    // The set of acquired and pending locks.
61    private final SortedSet<FileLock> locks = new TreeSet<FileLock>(LOCK_COMPARATOR);
62
63    private final Object repositioningLock = new Object();
64
65    /**
66     * Create a new file channel implementation class that wraps the given
67     * fd and operates in the specified mode.
68     */
69    public FileChannelImpl(Object stream, int fd, int mode) {
70        this.fd = fd;
71        this.stream = stream;
72        this.mode = mode;
73    }
74
75    /**
76     * Helper method to throw an exception if the channel is already closed.
77     * Note that we don't bother to synchronize on this test since the file may
78     * be closed by operations beyond our control anyways.
79     */
80    private void checkOpen() throws ClosedChannelException {
81        if (!isOpen()) {
82            throw new ClosedChannelException();
83        }
84    }
85
86    private void checkReadable() {
87        if ((mode & O_ACCMODE) == O_WRONLY) {
88            throw new NonReadableChannelException();
89        }
90    }
91
92    private void checkWritable() {
93        if ((mode & O_ACCMODE) == O_RDONLY) {
94            throw new NonWritableChannelException();
95        }
96    }
97
98    protected void implCloseChannel() throws IOException {
99        if (stream instanceof Closeable) {
100            ((Closeable) stream).close();
101        }
102    }
103
104    private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException {
105        int accessMode = (mode & O_ACCMODE);
106        if (accessMode == O_RDONLY) {
107            if (!shared) {
108                throw new NonWritableChannelException();
109            }
110        } else if (accessMode == O_WRONLY) {
111            if (shared) {
112                throw new NonReadableChannelException();
113            }
114        }
115
116        if (position < 0 || size < 0) {
117            throw new IllegalArgumentException("position=" + position + " size=" + size);
118        }
119        FileLock pendingLock = new FileLockImpl(this, position, size, shared);
120        addLock(pendingLock);
121
122        if (Platform.FILE_SYSTEM.lock(fd, position, size, shared, wait)) {
123            return pendingLock;
124        }
125
126        // Lock acquisition failed
127        removeLock(pendingLock);
128        return null;
129    }
130
131    private static final class FileLockImpl extends FileLock {
132        private boolean isReleased = false;
133
134        public FileLockImpl(FileChannel channel, long position, long size, boolean shared) {
135            super(channel, position, size, shared);
136        }
137
138        public boolean isValid() {
139            return !isReleased && channel().isOpen();
140        }
141
142        public void release() throws IOException {
143            if (!channel().isOpen()) {
144                throw new ClosedChannelException();
145            }
146            if (!isReleased) {
147                ((FileChannelImpl) channel()).release(this);
148                isReleased = true;
149            }
150        }
151    }
152
153    public final FileLock lock(long position, long size, boolean shared) throws IOException {
154        checkOpen();
155        FileLock resultLock = null;
156        {
157            boolean completed = false;
158            try {
159                begin();
160                resultLock = basicLock(position, size, shared, true);
161                completed = true;
162            } finally {
163                end(completed);
164            }
165        }
166        return resultLock;
167    }
168
169    public final FileLock tryLock(long position, long size, boolean shared) throws IOException {
170        checkOpen();
171        return basicLock(position, size, shared, false);
172    }
173
174    /**
175     * Non-API method to release a given lock on a file channel. Assumes that
176     * the lock will mark itself invalid after successful unlocking.
177     */
178    public void release(FileLock lock) throws IOException {
179        checkOpen();
180        Platform.FILE_SYSTEM.unlock(fd, lock.position(), lock.size());
181        removeLock(lock);
182    }
183
184    public void force(boolean metadata) throws IOException {
185        checkOpen();
186        if ((mode & O_ACCMODE) != O_RDONLY) {
187            Platform.FILE_SYSTEM.fsync(fd, metadata);
188        }
189    }
190
191    public final MappedByteBuffer map(MapMode mapMode, long position, long size) throws IOException {
192        checkOpen();
193        if (mapMode == null) {
194            throw new NullPointerException("mapMode == null");
195        }
196        if (position < 0 || size < 0 || size > Integer.MAX_VALUE) {
197            throw new IllegalArgumentException("position=" + position + " size=" + size);
198        }
199        int accessMode = (mode & O_ACCMODE);
200        if (accessMode == O_RDONLY) {
201            if (mapMode != MapMode.READ_ONLY) {
202                throw new NonWritableChannelException();
203            }
204        } else if (accessMode == O_WRONLY) {
205            throw new NonReadableChannelException();
206        }
207        if (position + size > size()) {
208            Platform.FILE_SYSTEM.truncate(fd, position + size);
209        }
210        long alignment = position - position % ALLOC_GRANULARITY;
211        int offset = (int) (position - alignment);
212        MemoryBlock block = MemoryBlock.mmap(fd, alignment, size + offset, mapMode);
213        return new MappedByteBufferAdapter(block, (int) size, offset, mapMode);
214    }
215
216    /**
217     * Returns the current file position.
218     */
219    public long position() throws IOException {
220        checkOpen();
221        if ((mode & O_APPEND) != 0) {
222            return size();
223        }
224        return Platform.FILE_SYSTEM.seek(fd, 0L, SEEK_CUR);
225    }
226
227    /**
228     * Sets the file pointer.
229     */
230    public FileChannel position(long newPosition) throws IOException {
231        checkOpen();
232        if (newPosition < 0) {
233            throw new IllegalArgumentException("position: " + newPosition);
234        }
235
236        synchronized (repositioningLock) {
237            Platform.FILE_SYSTEM.seek(fd, newPosition, SEEK_SET);
238        }
239        return this;
240    }
241
242    public int read(ByteBuffer buffer, long position) throws IOException {
243        FileChannelImpl.checkWritable(buffer);
244        if (position < 0) {
245            throw new IllegalArgumentException("position: " + position);
246        }
247        checkOpen();
248        checkReadable();
249        if (!buffer.hasRemaining()) {
250            return 0;
251        }
252        synchronized (repositioningLock) {
253            int bytesRead = 0;
254            long preReadPosition = position();
255            position(position);
256            try {
257                bytesRead = read(buffer);
258            } finally {
259                position(preReadPosition);
260            }
261            return bytesRead;
262        }
263    }
264
265    public int read(ByteBuffer buffer) throws IOException {
266        FileChannelImpl.checkWritable(buffer);
267        checkOpen();
268        checkReadable();
269        if (!buffer.hasRemaining()) {
270            return 0;
271        }
272        boolean completed = false;
273        int bytesRead = 0;
274        synchronized (repositioningLock) {
275            if (buffer.isDirect()) {
276                try {
277                    begin();
278                    /*
279                     * if (bytesRead <= EOF) dealt by read completed = false;
280                     */
281                    int address = NioUtils.getDirectBufferAddress(buffer);
282                    bytesRead = (int) Platform.FILE_SYSTEM.readDirect(fd, address,
283                            buffer.position(), buffer.remaining());
284                    completed = true;
285                } finally {
286                    end(completed && bytesRead >= 0);
287                }
288            } else {
289                try {
290                    begin();
291                    /*
292                     * if (bytesRead <= EOF) dealt by read completed = false;
293                     */
294                    bytesRead = (int) Platform.FILE_SYSTEM.read(fd, buffer.array(),
295                            buffer.arrayOffset() + buffer.position(), buffer.remaining());
296                    completed = true;
297                } finally {
298                    end(completed && bytesRead >= 0);
299                }
300            }
301            if (bytesRead > 0) {
302                buffer.position(buffer.position() + bytesRead);
303            }
304        }
305        return bytesRead;
306    }
307
308    public long read(ByteBuffer[] buffers, int offset, int length) throws IOException {
309        Arrays.checkOffsetAndCount(buffers.length, offset, length);
310        checkOpen();
311        checkReadable();
312        int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, true);
313        if (count == 0) {
314            return 0;
315        }
316        ByteBuffer[] directBuffers = new ByteBuffer[length];
317        int[] handles = new int[length];
318        int[] offsets = new int[length];
319        int[] lengths = new int[length];
320        for (int i = 0; i < length; i++) {
321            ByteBuffer buffer = buffers[i + offset];
322            if (!buffer.isDirect()) {
323                buffer = ByteBuffer.allocateDirect(buffer.remaining());
324                directBuffers[i] = buffer;
325                offsets[i] = 0;
326            } else {
327                offsets[i] = buffer.position();
328            }
329            handles[i] = NioUtils.getDirectBufferAddress(buffer);
330            lengths[i] = buffer.remaining();
331        }
332        long bytesRead = 0;
333        {
334            boolean completed = false;
335            try {
336                begin();
337                synchronized (repositioningLock) {
338                    bytesRead = Platform.FILE_SYSTEM.readv(fd, handles, offsets, lengths, length);
339
340                }
341                completed = true;
342                /*
343                 * if (bytesRead < EOF) //dealt by readv? completed = false;
344                 */
345            } finally {
346                end(completed);
347            }
348        }
349        int end = offset + length;
350        long bytesRemaining = bytesRead;
351        for (int i = offset; i < end && bytesRemaining > 0; i++) {
352            if (buffers[i].isDirect()) {
353                if (lengths[i] < bytesRemaining) {
354                    int pos = buffers[i].limit();
355                    buffers[i].position(pos);
356                    bytesRemaining -= lengths[i];
357                } else {
358                    int pos = (int) bytesRemaining;
359                    buffers[i].position(pos);
360                    break;
361                }
362            } else {
363                ByteBuffer buf = directBuffers[i - offset];
364                if (bytesRemaining < buf.remaining()) {
365                    // this is the last step.
366                    int pos = buf.position();
367                    buffers[i].put(buf);
368                    buffers[i].position(pos + (int) bytesRemaining);
369                    bytesRemaining = 0;
370                } else {
371                    bytesRemaining -= buf.remaining();
372                    buffers[i].put(buf);
373                }
374            }
375        }
376        return bytesRead;
377    }
378
379    /**
380     * Returns the current file size, as an integer number of bytes.
381     */
382    public long size() throws IOException {
383        checkOpen();
384        return Platform.FILE_SYSTEM.length(fd);
385    }
386
387    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
388        checkOpen();
389        if (!src.isOpen()) {
390            throw new ClosedChannelException();
391        }
392        checkWritable();
393        if (position < 0 || count < 0 || count > Integer.MAX_VALUE) {
394            throw new IllegalArgumentException("position=" + position + " count=" + count);
395        }
396        if (position > size()) {
397            return 0;
398        }
399
400        ByteBuffer buffer = null;
401
402        try {
403            if (src instanceof FileChannel) {
404                FileChannel fileSrc = (FileChannel) src;
405                long size = fileSrc.size();
406                long filePosition = fileSrc.position();
407                count = Math.min(count, size - filePosition);
408                buffer = fileSrc.map(MapMode.READ_ONLY, filePosition, count);
409                fileSrc.position(filePosition + count);
410            } else {
411                buffer = ByteBuffer.allocateDirect((int) count);
412                src.read(buffer);
413                buffer.flip();
414            }
415            return write(buffer, position);
416        } finally {
417            NioUtils.freeDirectBuffer(buffer);
418        }
419    }
420
421    public long transferTo(long position, long count, WritableByteChannel target)
422            throws IOException {
423        checkOpen();
424        if (!target.isOpen()) {
425            throw new ClosedChannelException();
426        }
427        if (target instanceof FileChannelImpl) {
428            ((FileChannelImpl) target).checkWritable();
429        }
430        if (position < 0 || count < 0) {
431            throw new IllegalArgumentException("position=" + position + " count=" + count);
432        }
433
434        if (count == 0 || position >= size()) {
435            return 0;
436        }
437        ByteBuffer buffer = null;
438        count = Math.min(count, size() - position);
439        if (target instanceof SocketChannelImpl) {
440            // only socket can be transfered by system call
441            return transferToSocket(fd, ((SocketChannelImpl) target).getFD(), position, count);
442        }
443
444        try {
445            buffer = map(MapMode.READ_ONLY, position, count);
446            return target.write(buffer);
447        } finally {
448            NioUtils.freeDirectBuffer(buffer);
449        }
450    }
451
452    private long transferToSocket(int l, FileDescriptor fd, long position, long count) throws IOException {
453        boolean completed = false;
454        try {
455            begin();
456            long ret = Platform.FILE_SYSTEM.transfer(l, fd, position, count);
457            completed = true;
458            return ret;
459        } finally {
460            end(completed);
461        }
462    }
463
464    public FileChannel truncate(long size) throws IOException {
465        checkOpen();
466        if (size < 0) {
467            throw new IllegalArgumentException("size: " + size);
468        }
469        checkWritable();
470        if (size < size()) {
471            synchronized (repositioningLock) {
472                long position = position();
473                Platform.FILE_SYSTEM.truncate(fd, size);
474                /*
475                 * FIXME: currently the port library always modifies the
476                 * position to given size. not sure it is a bug or intended
477                 * behavior, so I always reset the position to proper value as
478                 * Java Spec.
479                 */
480                position(position > size ? size : position);
481            }
482        }
483        return this;
484    }
485
486    public int write(ByteBuffer buffer, long position) throws IOException {
487        if (buffer == null) {
488            throw new NullPointerException("buffer == null");
489        }
490        if (position < 0) {
491            throw new IllegalArgumentException("position: " + position);
492        }
493        checkOpen();
494        checkWritable();
495        if (!buffer.hasRemaining()) {
496            return 0;
497        }
498        int bytesWritten = 0;
499        synchronized (repositioningLock) {
500            long preWritePosition = position();
501            position(position);
502            try {
503                bytesWritten = writeImpl(buffer);
504            } finally {
505                position(preWritePosition);
506            }
507        }
508        return bytesWritten;
509    }
510
511    public int write(ByteBuffer buffer) throws IOException {
512        checkOpen();
513        checkWritable();
514        if ((mode & O_APPEND) != 0) {
515            position(size());
516        }
517        return writeImpl(buffer);
518    }
519
520    private int writeImpl(ByteBuffer buffer) throws IOException {
521        int bytesWritten;
522        boolean completed = false;
523        synchronized (repositioningLock) {
524            if (buffer.isDirect()) {
525                try {
526                    begin();
527                    int address = NioUtils.getDirectBufferAddress(buffer);
528                    bytesWritten = (int) Platform.FILE_SYSTEM.writeDirect(fd,
529                            address, buffer.position(), buffer.remaining());
530                    completed = true;
531                } finally {
532                    end(completed);
533                }
534            } else {
535                try {
536                    begin();
537                    bytesWritten = (int) Platform.FILE_SYSTEM.write(fd, buffer
538                            .array(), buffer.arrayOffset() + buffer.position(),
539                            buffer.remaining());
540                    completed = true;
541                } finally {
542                    end(completed);
543                }
544            }
545            if (bytesWritten > 0) {
546                buffer.position(buffer.position() + bytesWritten);
547            }
548        }
549        return bytesWritten;
550    }
551
552    public long write(ByteBuffer[] buffers, int offset, int length) throws IOException {
553        Arrays.checkOffsetAndCount(buffers.length, offset, length);
554        checkOpen();
555        checkWritable();
556        int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, false);
557        if (count == 0) {
558            return 0;
559        }
560        int[] handles = new int[length];
561        int[] offsets = new int[length];
562        int[] lengths = new int[length];
563        // list of allocated direct ByteBuffers to prevent them from being GC-ed
564        ByteBuffer[] allocatedBufs = new ByteBuffer[length];
565
566        for (int i = 0; i < length; i++) {
567            ByteBuffer buffer = buffers[i + offset];
568            if (!buffer.isDirect()) {
569                ByteBuffer directBuffer = ByteBuffer.allocateDirect(buffer.remaining());
570                directBuffer.put(buffer);
571                directBuffer.flip();
572                buffer = directBuffer;
573                allocatedBufs[i] = directBuffer;
574                offsets[i] = 0;
575            } else {
576                offsets[i] = buffer.position();
577                allocatedBufs[i] = null;
578            }
579            handles[i] = NioUtils.getDirectBufferAddress(buffer);
580            lengths[i] = buffer.remaining();
581        }
582
583        long bytesWritten = 0;
584        boolean completed = false;
585        synchronized (repositioningLock) {
586            try {
587                begin();
588                bytesWritten = Platform.FILE_SYSTEM.writev(fd, handles, offsets, lengths, length);
589                completed = true;
590            } finally {
591                end(completed);
592                for (ByteBuffer buffer : allocatedBufs) {
593                    NioUtils.freeDirectBuffer(buffer);
594                }
595            }
596        }
597
598        long bytesRemaining = bytesWritten;
599        for (int i = offset; i < length + offset; i++) {
600            if (bytesRemaining > buffers[i].remaining()) {
601                int pos = buffers[i].limit();
602                buffers[i].position(pos);
603                bytesRemaining -= buffers[i].remaining();
604            } else {
605                int pos = buffers[i].position() + (int) bytesRemaining;
606                buffers[i].position(pos);
607                break;
608            }
609        }
610        return bytesWritten;
611    }
612
613    static void checkWritable(ByteBuffer buffer) {
614        if (buffer.isReadOnly()) {
615            throw new IllegalArgumentException("read-only buffer");
616        }
617    }
618
619    /**
620     * @param copyingIn true if we're copying data into the buffers (typically
621     * because the caller is a file/network read operation), false if we're
622     * copying data out of the buffers (for a file/network write operation).
623     */
624    static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length, boolean copyingIn) {
625        int count = 0;
626        for (int i = offset; i < offset + length; ++i) {
627            count += buffers[i].remaining();
628            if (copyingIn) {
629                checkWritable(buffers[i]);
630            }
631        }
632        return count;
633    }
634
635    public int getFd() {
636        return fd;
637    }
638
639    /**
640     * Add a new pending lock to the manager. Throws an exception if the lock
641     * would overlap an existing lock. Once the lock is acquired it remains in
642     * this set as an acquired lock.
643     */
644    private synchronized void addLock(FileLock lock) throws OverlappingFileLockException {
645        long lockEnd = lock.position() + lock.size();
646        for (FileLock existingLock : locks) {
647            if (existingLock.position() > lockEnd) {
648                // This, and all remaining locks, start beyond our end (so
649                // cannot overlap).
650                break;
651            }
652            if (existingLock.overlaps(lock.position(), lock.size())) {
653                throw new OverlappingFileLockException();
654            }
655        }
656        locks.add(lock);
657    }
658
659    /**
660     * Removes an acquired lock from the lock manager. If the lock did not exist
661     * in the lock manager the operation is a no-op.
662     */
663    private synchronized void removeLock(FileLock lock) {
664        locks.remove(lock);
665    }
666}
667