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