blob: 4aa21dbd18ad9f28955bd6079f88d9a2703fceb7 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.nio.ch;
27
28import java.io.FileDescriptor;
29import java.io.FileInputStream;
30import java.io.FileOutputStream;
31import java.io.RandomAccessFile;
32import java.io.IOException;
33import java.nio.ByteBuffer;
34import java.nio.MappedByteBuffer;
35import java.nio.channels.*;
36import java.nio.channels.spi.*;
37import java.util.ArrayList;
38import java.util.List;
39import java.util.Iterator;
40import java.util.concurrent.ConcurrentHashMap;
41import java.lang.ref.WeakReference;
42import java.lang.ref.ReferenceQueue;
43import java.lang.reflect.Field;
44import java.security.AccessController;
45import java.security.PrivilegedAction;
46import sun.misc.Cleaner;
47import sun.security.action.GetPropertyAction;
48
49
50public class FileChannelImpl
51 extends FileChannel
52{
53
54 // Used to make native read and write calls
55 private static NativeDispatcher nd;
56
57 // Memory allocation size for mapping buffers
58 private static long allocationGranularity;
59
60 // Cached field for MappedByteBuffer.isAMappedBuffer
61 private static Field isAMappedBufferField;
62
63 // File descriptor
64 private FileDescriptor fd;
65
66 // File access mode (immutable)
67 private boolean writable;
68 private boolean readable;
69 private boolean appending;
70
71 // Required to prevent finalization of creating stream (immutable)
72 private Object parent;
73
74 // Thread-safe set of IDs of native threads, for signalling
75 private NativeThreadSet threads = new NativeThreadSet(2);
76
77 // Lock for operations involving position and size
78 private Object positionLock = new Object();
79
80 private FileChannelImpl(FileDescriptor fd, boolean readable,
81 boolean writable, Object parent, boolean append)
82 {
83 this.fd = fd;
84 this.readable = readable;
85 this.writable = writable;
86 this.parent = parent;
87 this.appending = append;
88 }
89
90 // Invoked by getChannel() methods
91 // of java.io.File{Input,Output}Stream and RandomAccessFile
92 //
93 public static FileChannel open(FileDescriptor fd,
94 boolean readable, boolean writable,
95 Object parent)
96 {
97 return new FileChannelImpl(fd, readable, writable, parent, false);
98 }
99
100 public static FileChannel open(FileDescriptor fd,
101 boolean readable, boolean writable,
102 Object parent, boolean append)
103 {
104 return new FileChannelImpl(fd, readable, writable, parent, append);
105 }
106
107 private void ensureOpen() throws IOException {
108 if (!isOpen())
109 throw new ClosedChannelException();
110 }
111
112
113 // -- Standard channel operations --
114
115 protected void implCloseChannel() throws IOException {
116
117 nd.preClose(fd);
118 threads.signal();
119
120 // Invalidate and release any locks that we still hold
121 if (fileLockTable != null) {
122 fileLockTable.removeAll( new FileLockTable.Releaser() {
123 public void release(FileLock fl) throws IOException {
124 ((FileLockImpl)fl).invalidate();
125 release0(fd, fl.position(), fl.size());
126 }
127 });
128 }
129
130 if (parent != null) {
131
132 // Close the fd via the parent stream's close method. The parent
133 // will reinvoke our close method, which is defined in the
134 // superclass AbstractInterruptibleChannel, but the isOpen logic in
135 // that method will prevent this method from being reinvoked.
136 //
137 if (parent instanceof FileInputStream)
138 ((FileInputStream)parent).close();
139 else if (parent instanceof FileOutputStream)
140 ((FileOutputStream)parent).close();
141 else if (parent instanceof RandomAccessFile)
142 ((RandomAccessFile)parent).close();
143 else
144 assert false;
145
146 } else {
147 nd.close(fd);
148 }
149
150 }
151
152 public int read(ByteBuffer dst) throws IOException {
153 ensureOpen();
154 if (!readable)
155 throw new NonReadableChannelException();
156 synchronized (positionLock) {
157 int n = 0;
158 int ti = -1;
159 try {
160 begin();
161 if (!isOpen())
162 return 0;
163 ti = threads.add();
164 do {
165 n = IOUtil.read(fd, dst, -1, nd, positionLock);
166 } while ((n == IOStatus.INTERRUPTED) && isOpen());
167 return IOStatus.normalize(n);
168 } finally {
169 threads.remove(ti);
170 end(n > 0);
171 assert IOStatus.check(n);
172 }
173 }
174 }
175
176 private long read0(ByteBuffer[] dsts) throws IOException {
177 ensureOpen();
178 if (!readable)
179 throw new NonReadableChannelException();
180 synchronized (positionLock) {
181 long n = 0;
182 int ti = -1;
183 try {
184 begin();
185 if (!isOpen())
186 return 0;
187 ti = threads.add();
188 do {
189 n = IOUtil.read(fd, dsts, nd);
190 } while ((n == IOStatus.INTERRUPTED) && isOpen());
191 return IOStatus.normalize(n);
192 } finally {
193 threads.remove(ti);
194 end(n > 0);
195 assert IOStatus.check(n);
196 }
197 }
198 }
199
200 public long read(ByteBuffer[] dsts, int offset, int length)
201 throws IOException
202 {
203 if ((offset < 0) || (length < 0) || (offset > dsts.length - length))
204 throw new IndexOutOfBoundsException();
205 // ## Fix IOUtil.write so that we can avoid this array copy
206 return read0(Util.subsequence(dsts, offset, length));
207 }
208
209 public int write(ByteBuffer src) throws IOException {
210 ensureOpen();
211 if (!writable)
212 throw new NonWritableChannelException();
213 synchronized (positionLock) {
214 int n = 0;
215 int ti = -1;
216 try {
217 begin();
218 if (!isOpen())
219 return 0;
220 ti = threads.add();
221 if (appending)
222 position(size());
223 do {
224 n = IOUtil.write(fd, src, -1, nd, positionLock);
225 } while ((n == IOStatus.INTERRUPTED) && isOpen());
226 return IOStatus.normalize(n);
227 } finally {
228 threads.remove(ti);
229 end(n > 0);
230 assert IOStatus.check(n);
231 }
232 }
233 }
234
235 private long write0(ByteBuffer[] srcs) throws IOException {
236 ensureOpen();
237 if (!writable)
238 throw new NonWritableChannelException();
239 synchronized (positionLock) {
240 long n = 0;
241 int ti = -1;
242 try {
243 begin();
244 if (!isOpen())
245 return 0;
246 ti = threads.add();
247 if (appending)
248 position(size());
249 do {
250 n = IOUtil.write(fd, srcs, nd);
251 } while ((n == IOStatus.INTERRUPTED) && isOpen());
252 return IOStatus.normalize(n);
253 } finally {
254 threads.remove(ti);
255 end(n > 0);
256 assert IOStatus.check(n);
257 }
258 }
259 }
260
261 public long write(ByteBuffer[] srcs, int offset, int length)
262 throws IOException
263 {
264 if ((offset < 0) || (length < 0) || (offset > srcs.length - length))
265 throw new IndexOutOfBoundsException();
266 // ## Fix IOUtil.write so that we can avoid this array copy
267 return write0(Util.subsequence(srcs, offset, length));
268 }
269
270
271 // -- Other operations --
272
273 public long position() throws IOException {
274 ensureOpen();
275 synchronized (positionLock) {
276 long p = -1;
277 int ti = -1;
278 try {
279 begin();
280 if (!isOpen())
281 return 0;
282 ti = threads.add();
283 do {
284 p = position0(fd, -1);
285 } while ((p == IOStatus.INTERRUPTED) && isOpen());
286 return IOStatus.normalize(p);
287 } finally {
288 threads.remove(ti);
289 end(p > -1);
290 assert IOStatus.check(p);
291 }
292 }
293 }
294
295 public FileChannel position(long newPosition) throws IOException {
296 ensureOpen();
297 if (newPosition < 0)
298 throw new IllegalArgumentException();
299 synchronized (positionLock) {
300 long p = -1;
301 int ti = -1;
302 try {
303 begin();
304 if (!isOpen())
305 return null;
306 ti = threads.add();
307 do {
308 p = position0(fd, newPosition);
309 } while ((p == IOStatus.INTERRUPTED) && isOpen());
310 return this;
311 } finally {
312 threads.remove(ti);
313 end(p > -1);
314 assert IOStatus.check(p);
315 }
316 }
317 }
318
319 public long size() throws IOException {
320 ensureOpen();
321 synchronized (positionLock) {
322 long s = -1;
323 int ti = -1;
324 try {
325 begin();
326 if (!isOpen())
327 return -1;
328 ti = threads.add();
329 do {
330 s = size0(fd);
331 } while ((s == IOStatus.INTERRUPTED) && isOpen());
332 return IOStatus.normalize(s);
333 } finally {
334 threads.remove(ti);
335 end(s > -1);
336 assert IOStatus.check(s);
337 }
338 }
339 }
340
341 public FileChannel truncate(long size) throws IOException {
342 ensureOpen();
343 if (size < 0)
344 throw new IllegalArgumentException();
345 if (size > size())
346 return this;
347 if (!writable)
348 throw new NonWritableChannelException();
349 synchronized (positionLock) {
350 int rv = -1;
351 long p = -1;
352 int ti = -1;
353 try {
354 begin();
355 if (!isOpen())
356 return null;
357 ti = threads.add();
358
359 // get current position
360 do {
361 p = position0(fd, -1);
362 } while ((p == IOStatus.INTERRUPTED) && isOpen());
363 if (!isOpen())
364 return null;
365 assert p >= 0;
366
367 // truncate file
368 do {
369 rv = truncate0(fd, size);
370 } while ((rv == IOStatus.INTERRUPTED) && isOpen());
371 if (!isOpen())
372 return null;
373
374 // set position to size if greater than size
375 if (p > size)
376 p = size;
377 do {
378 rv = (int)position0(fd, p);
379 } while ((rv == IOStatus.INTERRUPTED) && isOpen());
380 return this;
381 } finally {
382 threads.remove(ti);
383 end(rv > -1);
384 assert IOStatus.check(rv);
385 }
386 }
387 }
388
389 public void force(boolean metaData) throws IOException {
390 ensureOpen();
391 int rv = -1;
392 int ti = -1;
393 try {
394 begin();
395 if (!isOpen())
396 return;
397 ti = threads.add();
398 do {
399 rv = force0(fd, metaData);
400 } while ((rv == IOStatus.INTERRUPTED) && isOpen());
401 } finally {
402 threads.remove(ti);
403 end(rv > -1);
404 assert IOStatus.check(rv);
405 }
406 }
407
408 // Assume at first that the underlying kernel supports sendfile();
409 // set this to false if we find out later that it doesn't
410 //
411 private static volatile boolean transferSupported = true;
412
413 // Assume that the underlying kernel sendfile() will work if the target
414 // fd is a pipe; set this to false if we find out later that it doesn't
415 //
416 private static volatile boolean pipeSupported = true;
417
418 // Assume that the underlying kernel sendfile() will work if the target
419 // fd is a file; set this to false if we find out later that it doesn't
420 //
421 private static volatile boolean fileSupported = true;
422
423 private long transferToDirectly(long position, int icount,
424 WritableByteChannel target)
425 throws IOException
426 {
427 if (!transferSupported)
428 return IOStatus.UNSUPPORTED;
429
430 FileDescriptor targetFD = null;
431 if (target instanceof FileChannelImpl) {
432 if (!fileSupported)
433 return IOStatus.UNSUPPORTED_CASE;
434 targetFD = ((FileChannelImpl)target).fd;
435 } else if (target instanceof SelChImpl) {
436 // Direct transfer to pipe causes EINVAL on some configurations
437 if ((target instanceof SinkChannelImpl) && !pipeSupported)
438 return IOStatus.UNSUPPORTED_CASE;
439 targetFD = ((SelChImpl)target).getFD();
440 }
441 if (targetFD == null)
442 return IOStatus.UNSUPPORTED;
443 int thisFDVal = IOUtil.fdVal(fd);
444 int targetFDVal = IOUtil.fdVal(targetFD);
445 if (thisFDVal == targetFDVal) // Not supported on some configurations
446 return IOStatus.UNSUPPORTED;
447
448 long n = -1;
449 int ti = -1;
450 try {
451 begin();
452 if (!isOpen())
453 return -1;
454 ti = threads.add();
455 do {
456 n = transferTo0(thisFDVal, position, icount, targetFDVal);
457 } while ((n == IOStatus.INTERRUPTED) && isOpen());
458 if (n == IOStatus.UNSUPPORTED_CASE) {
459 if (target instanceof SinkChannelImpl)
460 pipeSupported = false;
461 if (target instanceof FileChannelImpl)
462 fileSupported = false;
463 return IOStatus.UNSUPPORTED_CASE;
464 }
465 if (n == IOStatus.UNSUPPORTED) {
466 // Don't bother trying again
467 transferSupported = false;
468 return IOStatus.UNSUPPORTED;
469 }
470 return IOStatus.normalize(n);
471 } finally {
472 threads.remove(ti);
473 end (n > -1);
474 }
475 }
476
477 private long transferToTrustedChannel(long position, int icount,
478 WritableByteChannel target)
479 throws IOException
480 {
481 if ( !((target instanceof FileChannelImpl)
482 || (target instanceof SelChImpl)))
483 return IOStatus.UNSUPPORTED;
484
485 // Trusted target: Use a mapped buffer
486 MappedByteBuffer dbb = null;
487 try {
488 dbb = map(MapMode.READ_ONLY, position, icount);
489 // ## Bug: Closing this channel will not terminate the write
490 return target.write(dbb);
491 } finally {
492 if (dbb != null)
493 unmap(dbb);
494 }
495 }
496
497 private long transferToArbitraryChannel(long position, int icount,
498 WritableByteChannel target)
499 throws IOException
500 {
501 // Untrusted target: Use a newly-erased buffer
502 int c = Math.min(icount, TRANSFER_SIZE);
503 ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
504 long tw = 0; // Total bytes written
505 long pos = position;
506 try {
507 Util.erase(bb);
508 while (tw < icount) {
509 bb.limit(Math.min((int)(icount - tw), TRANSFER_SIZE));
510 int nr = read(bb, pos);
511 if (nr <= 0)
512 break;
513 bb.flip();
514 // ## Bug: Will block writing target if this channel
515 // ## is asynchronously closed
516 int nw = target.write(bb);
517 tw += nw;
518 if (nw != nr)
519 break;
520 pos += nw;
521 bb.clear();
522 }
523 return tw;
524 } catch (IOException x) {
525 if (tw > 0)
526 return tw;
527 throw x;
528 } finally {
529 Util.releaseTemporaryDirectBuffer(bb);
530 }
531 }
532
533 public long transferTo(long position, long count,
534 WritableByteChannel target)
535 throws IOException
536 {
537 ensureOpen();
538 if (!target.isOpen())
539 throw new ClosedChannelException();
540 if (!readable)
541 throw new NonReadableChannelException();
542 if (target instanceof FileChannelImpl &&
543 !((FileChannelImpl)target).writable)
544 throw new NonWritableChannelException();
545 if ((position < 0) || (count < 0))
546 throw new IllegalArgumentException();
547 long sz = size();
548 if (position > sz)
549 return 0;
550 int icount = (int)Math.min(count, Integer.MAX_VALUE);
551 if ((sz - position) < icount)
552 icount = (int)(sz - position);
553
554 long n;
555
556 // Attempt a direct transfer, if the kernel supports it
557 if ((n = transferToDirectly(position, icount, target)) >= 0)
558 return n;
559
560 // Attempt a mapped transfer, but only to trusted channel types
561 if ((n = transferToTrustedChannel(position, icount, target)) >= 0)
562 return n;
563
564 // Slow path for untrusted targets
565 return transferToArbitraryChannel(position, icount, target);
566 }
567
568 private long transferFromFileChannel(FileChannelImpl src,
569 long position, long count)
570 throws IOException
571 {
572 // Note we could loop here to accumulate more at once
573 synchronized (src.positionLock) {
574 long p = src.position();
575 int icount = (int)Math.min(Math.min(count, Integer.MAX_VALUE),
576 src.size() - p);
577 // ## Bug: Closing this channel will not terminate the write
578 MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, icount);
579 try {
580 long n = write(bb, position);
581 src.position(p + n);
582 return n;
583 } finally {
584 unmap(bb);
585 }
586 }
587 }
588
589 private static final int TRANSFER_SIZE = 8192;
590
591 private long transferFromArbitraryChannel(ReadableByteChannel src,
592 long position, long count)
593 throws IOException
594 {
595 // Untrusted target: Use a newly-erased buffer
596 int c = (int)Math.min(count, TRANSFER_SIZE);
597 ByteBuffer bb = Util.getTemporaryDirectBuffer(c);
598 long tw = 0; // Total bytes written
599 long pos = position;
600 try {
601 Util.erase(bb);
602 while (tw < count) {
603 bb.limit((int)Math.min((count - tw), (long)TRANSFER_SIZE));
604 // ## Bug: Will block reading src if this channel
605 // ## is asynchronously closed
606 int nr = src.read(bb);
607 if (nr <= 0)
608 break;
609 bb.flip();
610 int nw = write(bb, pos);
611 tw += nw;
612 if (nw != nr)
613 break;
614 pos += nw;
615 bb.clear();
616 }
617 return tw;
618 } catch (IOException x) {
619 if (tw > 0)
620 return tw;
621 throw x;
622 } finally {
623 Util.releaseTemporaryDirectBuffer(bb);
624 }
625 }
626
627 public long transferFrom(ReadableByteChannel src,
628 long position, long count)
629 throws IOException
630 {
631 ensureOpen();
632 if (!src.isOpen())
633 throw new ClosedChannelException();
634 if (!writable)
635 throw new NonWritableChannelException();
636 if ((position < 0) || (count < 0))
637 throw new IllegalArgumentException();
638 if (position > size())
639 return 0;
640 if (src instanceof FileChannelImpl)
641 return transferFromFileChannel((FileChannelImpl)src,
642 position, count);
643
644 return transferFromArbitraryChannel(src, position, count);
645 }
646
647 public int read(ByteBuffer dst, long position) throws IOException {
648 if (dst == null)
649 throw new NullPointerException();
650 if (position < 0)
651 throw new IllegalArgumentException("Negative position");
652 if (!readable)
653 throw new NonReadableChannelException();
654 ensureOpen();
655 int n = 0;
656 int ti = -1;
657 try {
658 begin();
659 if (!isOpen())
660 return -1;
661 ti = threads.add();
662 do {
663 n = IOUtil.read(fd, dst, position, nd, positionLock);
664 } while ((n == IOStatus.INTERRUPTED) && isOpen());
665 return IOStatus.normalize(n);
666 } finally {
667 threads.remove(ti);
668 end(n > 0);
669 assert IOStatus.check(n);
670 }
671 }
672
673 public int write(ByteBuffer src, long position) throws IOException {
674 if (src == null)
675 throw new NullPointerException();
676 if (position < 0)
677 throw new IllegalArgumentException("Negative position");
678 if (!writable)
679 throw new NonWritableChannelException();
680 ensureOpen();
681 int n = 0;
682 int ti = -1;
683 try {
684 begin();
685 if (!isOpen())
686 return -1;
687 ti = threads.add();
688 do {
689 n = IOUtil.write(fd, src, position, nd, positionLock);
690 } while ((n == IOStatus.INTERRUPTED) && isOpen());
691 return IOStatus.normalize(n);
692 } finally {
693 threads.remove(ti);
694 end(n > 0);
695 assert IOStatus.check(n);
696 }
697 }
698
699
700 // -- Memory-mapped buffers --
701
702 private static class Unmapper
703 implements Runnable
704 {
705
706 private long address;
707 private long size;
708
709 private Unmapper(long address, long size) {
710 assert (address != 0);
711 this.address = address;
712 this.size = size;
713 }
714
715 public void run() {
716 if (address == 0)
717 return;
718 unmap0(address, size);
719 address = 0;
720 }
721
722 }
723
724 private static void unmap(MappedByteBuffer bb) {
725 Cleaner cl = ((DirectBuffer)bb).cleaner();
726 if (cl != null)
727 cl.clean();
728 }
729
730 private static final int MAP_RO = 0;
731 private static final int MAP_RW = 1;
732 private static final int MAP_PV = 2;
733
734 public MappedByteBuffer map(MapMode mode, long position, long size)
735 throws IOException
736 {
737 ensureOpen();
738 if (position < 0L)
739 throw new IllegalArgumentException("Negative position");
740 if (size < 0L)
741 throw new IllegalArgumentException("Negative size");
742 if (position + size < 0)
743 throw new IllegalArgumentException("Position + size overflow");
744 if (size > Integer.MAX_VALUE)
745 throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
746 int imode = -1;
747 if (mode == MapMode.READ_ONLY)
748 imode = MAP_RO;
749 else if (mode == MapMode.READ_WRITE)
750 imode = MAP_RW;
751 else if (mode == MapMode.PRIVATE)
752 imode = MAP_PV;
753 assert (imode >= 0);
754 if ((mode != MapMode.READ_ONLY) && !writable)
755 throw new NonWritableChannelException();
756 if (!readable)
757 throw new NonReadableChannelException();
758
759 long addr = -1;
760 int ti = -1;
761 try {
762 begin();
763 if (!isOpen())
764 return null;
765 ti = threads.add();
766 if (size() < position + size) { // Extend file size
767 if (!writable) {
768 throw new IOException("Channel not open for writing " +
769 "- cannot extend file to required size");
770 }
771 int rv;
772 do {
773 rv = truncate0(fd, position + size);
774 } while ((rv == IOStatus.INTERRUPTED) && isOpen());
775 }
776 if (size == 0) {
777 addr = 0;
778 if ((!writable) || (imode == MAP_RO))
779 return Util.newMappedByteBufferR(0, 0, null);
780 else
781 return Util.newMappedByteBuffer(0, 0, null);
782 }
783
784 int pagePosition = (int)(position % allocationGranularity);
785 long mapPosition = position - pagePosition;
786 long mapSize = size + pagePosition;
787 try {
788 // If no exception was thrown from map0, the address is valid
789 addr = map0(imode, mapPosition, mapSize);
790 } catch (OutOfMemoryError x) {
791 // An OutOfMemoryError may indicate that we've exhausted memory
792 // so force gc and re-attempt map
793 System.gc();
794 try {
795 Thread.sleep(100);
796 } catch (InterruptedException y) {
797 Thread.currentThread().interrupt();
798 }
799 try {
800 addr = map0(imode, mapPosition, mapSize);
801 } catch (OutOfMemoryError y) {
802 // After a second OOME, fail
803 throw new IOException("Map failed", y);
804 }
805 }
806
807 assert (IOStatus.checkAll(addr));
808 assert (addr % allocationGranularity == 0);
809 int isize = (int)size;
810 Unmapper um = new Unmapper(addr, size + pagePosition);
811 if ((!writable) || (imode == MAP_RO))
812 return Util.newMappedByteBufferR(isize, addr + pagePosition, um);
813 else
814 return Util.newMappedByteBuffer(isize, addr + pagePosition, um);
815 } finally {
816 threads.remove(ti);
817 end(IOStatus.checkAll(addr));
818 }
819 }
820
821
822 // -- Locks --
823
824 public static final int NO_LOCK = -1; // Failed to lock
825 public static final int LOCKED = 0; // Obtained requested lock
826 public static final int RET_EX_LOCK = 1; // Obtained exclusive lock
827 public static final int INTERRUPTED = 2; // Request interrupted
828
829 // keeps track of locks on this file
830 private volatile FileLockTable fileLockTable;
831
832 // indicates if file locks are maintained system-wide (as per spec)
833 private static boolean isSharedFileLockTable;
834
835 // indicates if the disableSystemWideOverlappingFileLockCheck property
836 // has been checked
837 private static volatile boolean propertyChecked;
838
839 // The lock list in J2SE 1.4/5.0 was local to each FileChannel instance so
840 // the overlap check wasn't system wide when there were multiple channels to
841 // the same file. This property is used to get 1.4/5.0 behavior if desired.
842 private static boolean isSharedFileLockTable() {
843 if (!propertyChecked) {
844 synchronized (FileChannelImpl.class) {
845 if (!propertyChecked) {
846 String value = AccessController.doPrivileged(
847 new GetPropertyAction(
848 "sun.nio.ch.disableSystemWideOverlappingFileLockCheck"));
849 isSharedFileLockTable = ((value == null) || value.equals("false"));
850 propertyChecked = true;
851 }
852 }
853 }
854 return isSharedFileLockTable;
855 }
856
857 private FileLockTable fileLockTable() {
858 if (fileLockTable == null) {
859 synchronized (this) {
860 if (fileLockTable == null) {
861 fileLockTable = isSharedFileLockTable() ?
862 new SharedFileLockTable(this) : new SimpleFileLockTable();
863 }
864 }
865 }
866 return fileLockTable;
867 }
868
869 public FileLock lock(long position, long size, boolean shared)
870 throws IOException
871 {
872 ensureOpen();
873 if (shared && !readable)
874 throw new NonReadableChannelException();
875 if (!shared && !writable)
876 throw new NonWritableChannelException();
877 FileLockImpl fli = new FileLockImpl(this, position, size, shared);
878 FileLockTable flt = fileLockTable();
879 flt.add(fli);
880 boolean i = true;
881 int ti = -1;
882 try {
883 begin();
884 if (!isOpen())
885 return null;
886 ti = threads.add();
887 int result = lock0(fd, true, position, size, shared);
888 if (result == RET_EX_LOCK) {
889 assert shared;
890 FileLockImpl fli2 = new FileLockImpl(this, position, size,
891 false);
892 flt.replace(fli, fli2);
893 return fli2;
894 }
895 if (result == INTERRUPTED || result == NO_LOCK) {
896 flt.remove(fli);
897 i = false;
898 }
899 } catch (IOException e) {
900 flt.remove(fli);
901 throw e;
902 } finally {
903 threads.remove(ti);
904 try {
905 end(i);
906 } catch (ClosedByInterruptException e) {
907 throw new FileLockInterruptionException();
908 }
909 }
910 return fli;
911 }
912
913 public FileLock tryLock(long position, long size, boolean shared)
914 throws IOException
915 {
916 ensureOpen();
917 if (shared && !readable)
918 throw new NonReadableChannelException();
919 if (!shared && !writable)
920 throw new NonWritableChannelException();
921 FileLockImpl fli = new FileLockImpl(this, position, size, shared);
922 FileLockTable flt = fileLockTable();
923 flt.add(fli);
924 int result = lock0(fd, false, position, size, shared);
925 if (result == NO_LOCK) {
926 flt.remove(fli);
927 return null;
928 }
929 if (result == RET_EX_LOCK) {
930 assert shared;
931 FileLockImpl fli2 = new FileLockImpl(this, position, size,
932 false);
933 flt.replace(fli, fli2);
934 return fli2;
935 }
936 return fli;
937 }
938
939 void release(FileLockImpl fli) throws IOException {
940 ensureOpen();
941 release0(fd, fli.position(), fli.size());
942 assert fileLockTable != null;
943 fileLockTable.remove(fli);
944 }
945
946
947 // -- File lock support --
948
949 /**
950 * A table of FileLocks.
951 */
952 private interface FileLockTable {
953 /**
954 * Adds a file lock to the table.
955 *
956 * @throws OverlappingFileLockException if the file lock overlaps
957 * with an existing file lock in the table
958 */
959 void add(FileLock fl) throws OverlappingFileLockException;
960
961 /**
962 * Remove an existing file lock from the table.
963 */
964 void remove(FileLock fl);
965
966 /**
967 * An implementation of this interface releases a given file lock.
968 * Used with removeAll.
969 */
970 interface Releaser {
971 void release(FileLock fl) throws IOException;
972 }
973
974 /**
975 * Removes all file locks from the table.
976 * <p>
977 * The Releaser#release method is invoked for each file lock before
978 * it is removed.
979 *
980 * @throws IOException if the release method throws IOException
981 */
982 void removeAll(Releaser r) throws IOException;
983
984 /**
985 * Replaces an existing file lock in the table.
986 */
987 void replace(FileLock fl1, FileLock fl2);
988 }
989
990 /**
991 * A simple file lock table that maintains a list of FileLocks obtained by a
992 * FileChannel. Use to get 1.4/5.0 behaviour.
993 */
994 private static class SimpleFileLockTable implements FileLockTable {
995 // synchronize on list for access
996 private List<FileLock> lockList = new ArrayList<FileLock>(2);
997
998 public SimpleFileLockTable() {
999 }
1000
1001 private void checkList(long position, long size)
1002 throws OverlappingFileLockException
1003 {
1004 assert Thread.holdsLock(lockList);
1005 for (FileLock fl: lockList) {
1006 if (fl.overlaps(position, size)) {
1007 throw new OverlappingFileLockException();
1008 }
1009 }
1010 }
1011
1012 public void add(FileLock fl) throws OverlappingFileLockException {
1013 synchronized (lockList) {
1014 checkList(fl.position(), fl.size());
1015 lockList.add(fl);
1016 }
1017 }
1018
1019 public void remove(FileLock fl) {
1020 synchronized (lockList) {
1021 lockList.remove(fl);
1022 }
1023 }
1024
1025 public void removeAll(Releaser releaser) throws IOException {
1026 synchronized(lockList) {
1027 Iterator<FileLock> i = lockList.iterator();
1028 while (i.hasNext()) {
1029 FileLock fl = i.next();
1030 releaser.release(fl);
1031 i.remove();
1032 }
1033 }
1034 }
1035
1036 public void replace(FileLock fl1, FileLock fl2) {
1037 synchronized (lockList) {
1038 lockList.remove(fl1);
1039 lockList.add(fl2);
1040 }
1041 }
1042 }
1043
1044 /**
1045 * A weak reference to a FileLock.
1046 * <p>
1047 * SharedFileLockTable uses a list of file lock references to avoid keeping the
1048 * FileLock (and FileChannel) alive.
1049 */
1050 private static class FileLockReference extends WeakReference<FileLock> {
1051 private FileKey fileKey;
1052
1053 FileLockReference(FileLock referent,
1054 ReferenceQueue queue,
1055 FileKey key) {
1056 super(referent, queue);
1057 this.fileKey = key;
1058 }
1059
1060 private FileKey fileKey() {
1061 return fileKey;
1062 }
1063 }
1064
1065 /**
1066 * A file lock table that is over a system-wide map of all file locks.
1067 */
1068 private static class SharedFileLockTable implements FileLockTable {
1069 // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey.
1070 // The map value is a list of file locks represented by FileLockReferences.
1071 // All access to the list must be synchronized on the list.
1072 private static ConcurrentHashMap<FileKey, ArrayList<FileLockReference>> lockMap =
1073 new ConcurrentHashMap<FileKey, ArrayList<FileLockReference>>();
1074
1075 // reference queue for cleared refs
1076 private static ReferenceQueue queue = new ReferenceQueue();
1077
1078 // the enclosing file channel
1079 private FileChannelImpl fci;
1080
1081 // File key for the file that this channel is connected to
1082 private FileKey fileKey;
1083
1084 public SharedFileLockTable(FileChannelImpl fci) {
1085 this.fci = fci;
1086 this.fileKey = FileKey.create(fci.fd);
1087 }
1088
1089 public void add(FileLock fl) throws OverlappingFileLockException {
1090 ArrayList<FileLockReference> list = lockMap.get(fileKey);
1091
1092 for (;;) {
1093
1094 // The key isn't in the map so we try to create it atomically
1095 if (list == null) {
1096 list = new ArrayList<FileLockReference>(2);
1097 ArrayList<FileLockReference> prev;
1098 synchronized (list) {
1099 prev = lockMap.putIfAbsent(fileKey, list);
1100 if (prev == null) {
1101 // we successfully created the key so we add the file lock
1102 list.add(new FileLockReference(fl, queue, fileKey));
1103 break;
1104 }
1105 }
1106 // someone else got there first
1107 list = prev;
1108 }
1109
1110 // There is already a key. It is possible that some other thread
1111 // is removing it so we re-fetch the value from the map. If it
1112 // hasn't changed then we check the list for overlapping locks
1113 // and add the new lock to the list.
1114 synchronized (list) {
1115 ArrayList<FileLockReference> current = lockMap.get(fileKey);
1116 if (list == current) {
1117 checkList(list, fl.position(), fl.size());
1118 list.add(new FileLockReference(fl, queue, fileKey));
1119 break;
1120 }
1121 list = current;
1122 }
1123
1124 }
1125
1126 // process any stale entries pending in the reference queue
1127 removeStaleEntries();
1128 }
1129
1130 private void removeKeyIfEmpty(FileKey fk, ArrayList<FileLockReference> list) {
1131 assert Thread.holdsLock(list);
1132 assert lockMap.get(fk) == list;
1133 if (list.isEmpty()) {
1134 lockMap.remove(fk);
1135 }
1136 }
1137
1138 public void remove(FileLock fl) {
1139 assert fl != null;
1140
1141 // the lock must exist so the list of locks must be present
1142 ArrayList<FileLockReference> list = lockMap.get(fileKey);
1143 assert list != null;
1144
1145 synchronized (list) {
1146 int index = 0;
1147 while (index < list.size()) {
1148 FileLockReference ref = list.get(index);
1149 FileLock lock = ref.get();
1150 if (lock == fl) {
1151 assert (lock != null) && (lock.channel() == fci);
1152 ref.clear();
1153 list.remove(index);
1154 break;
1155 }
1156 index++;
1157 }
1158 }
1159 }
1160
1161 public void removeAll(Releaser releaser) throws IOException {
1162 ArrayList<FileLockReference> list = lockMap.get(fileKey);
1163 if (list != null) {
1164 synchronized (list) {
1165 int index = 0;
1166 while (index < list.size()) {
1167 FileLockReference ref = list.get(index);
1168 FileLock lock = ref.get();
1169
1170 // remove locks obtained by this channel
1171 if (lock != null && lock.channel() == fci) {
1172 // invoke the releaser to invalidate/release the lock
1173 releaser.release(lock);
1174
1175 // remove the lock from the list
1176 ref.clear();
1177 list.remove(index);
1178 } else {
1179 index++;
1180 }
1181 }
1182
1183 // once the lock list is empty we remove it from the map
1184 removeKeyIfEmpty(fileKey, list);
1185 }
1186 }
1187 }
1188
1189 public void replace(FileLock fromLock, FileLock toLock) {
1190 // the lock must exist so there must be a list
1191 ArrayList<FileLockReference> list = lockMap.get(fileKey);
1192 assert list != null;
1193
1194 synchronized (list) {
1195 for (int index=0; index<list.size(); index++) {
1196 FileLockReference ref = list.get(index);
1197 FileLock lock = ref.get();
1198 if (lock == fromLock) {
1199 ref.clear();
1200 list.set(index, new FileLockReference(toLock, queue, fileKey));
1201 break;
1202 }
1203 }
1204 }
1205 }
1206
1207 // Check for overlapping file locks
1208 private void checkList(List<FileLockReference> list, long position, long size)
1209 throws OverlappingFileLockException
1210 {
1211 assert Thread.holdsLock(list);
1212 for (FileLockReference ref: list) {
1213 FileLock fl = ref.get();
1214 if (fl != null && fl.overlaps(position, size))
1215 throw new OverlappingFileLockException();
1216 }
1217 }
1218
1219 // Process the reference queue
1220 private void removeStaleEntries() {
1221 FileLockReference ref;
1222 while ((ref = (FileLockReference)queue.poll()) != null) {
1223 FileKey fk = ref.fileKey();
1224 ArrayList<FileLockReference> list = lockMap.get(fk);
1225 if (list != null) {
1226 synchronized (list) {
1227 list.remove(ref);
1228 removeKeyIfEmpty(fk, list);
1229 }
1230 }
1231 }
1232 }
1233 }
1234
1235 // -- Native methods --
1236
1237 // Grabs a file lock
1238 native int lock0(FileDescriptor fd, boolean blocking, long pos, long size,
1239 boolean shared) throws IOException;
1240
1241 // Releases a file lock
1242 native void release0(FileDescriptor fd, long pos, long size)
1243 throws IOException;
1244
1245 // Creates a new mapping
1246 private native long map0(int prot, long position, long length)
1247 throws IOException;
1248
1249 // Removes an existing mapping
1250 private static native int unmap0(long address, long length);
1251
1252 // Forces output to device
1253 private native int force0(FileDescriptor fd, boolean metaData);
1254
1255 // Truncates a file
1256 private native int truncate0(FileDescriptor fd, long size);
1257
1258 // Transfers from src to dst, or returns -2 if kernel can't do that
1259 private native long transferTo0(int src, long position, long count, int dst);
1260
1261 // Sets or reports this file's position
1262 // If offset is -1, the current position is returned
1263 // otherwise the position is set to offset
1264 private native long position0(FileDescriptor fd, long offset);
1265
1266 // Reports this file's size
1267 private native long size0(FileDescriptor fd);
1268
1269 // Caches fieldIDs
1270 private static native long initIDs();
1271
1272 static {
1273 Util.load();
1274 allocationGranularity = initIDs();
1275 nd = new FileDispatcher();
1276 isAMappedBufferField = Reflect.lookupField("java.nio.MappedByteBuffer",
1277 "isAMappedBuffer");
1278 }
1279
1280}