1 /* 2 * Copyright 2013 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.jimfs; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.jimfs.Util.clear; 22 import static com.google.common.jimfs.Util.nextPowerOf2; 23 24 import com.google.common.annotations.VisibleForTesting; 25 import com.google.common.primitives.UnsignedBytes; 26 import java.io.IOException; 27 import java.nio.ByteBuffer; 28 import java.nio.channels.FileChannel; 29 import java.nio.channels.ReadableByteChannel; 30 import java.nio.channels.WritableByteChannel; 31 import java.util.Arrays; 32 import java.util.concurrent.locks.Lock; 33 import java.util.concurrent.locks.ReadWriteLock; 34 import java.util.concurrent.locks.ReentrantReadWriteLock; 35 36 /** 37 * A mutable, resizable store for bytes. Bytes are stored in fixed-sized byte arrays (blocks) 38 * allocated by a {@link HeapDisk}. 39 * 40 * @author Colin Decker 41 */ 42 final class RegularFile extends File { 43 44 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 45 46 private final HeapDisk disk; 47 48 /** Block list for the file. */ 49 private byte[][] blocks; 50 /** Block count for the the file, which also acts as the head of the block list. */ 51 private int blockCount; 52 53 private long size; 54 55 /** Creates a new regular file with the given ID and using the given disk. */ create(int id, HeapDisk disk)56 public static RegularFile create(int id, HeapDisk disk) { 57 return new RegularFile(id, disk, new byte[32][], 0, 0); 58 } 59 RegularFile(int id, HeapDisk disk, byte[][] blocks, int blockCount, long size)60 RegularFile(int id, HeapDisk disk, byte[][] blocks, int blockCount, long size) { 61 super(id); 62 this.disk = checkNotNull(disk); 63 this.blocks = checkNotNull(blocks); 64 this.blockCount = blockCount; 65 66 checkArgument(size >= 0); 67 this.size = size; 68 } 69 70 private int openCount = 0; 71 private boolean deleted = false; 72 73 /** Returns the read lock for this file. */ readLock()74 public Lock readLock() { 75 return lock.readLock(); 76 } 77 78 /** Returns the write lock for this file. */ writeLock()79 public Lock writeLock() { 80 return lock.writeLock(); 81 } 82 83 // lower-level methods dealing with the blocks array 84 expandIfNecessary(int minBlockCount)85 private void expandIfNecessary(int minBlockCount) { 86 if (minBlockCount > blocks.length) { 87 this.blocks = Arrays.copyOf(blocks, nextPowerOf2(minBlockCount)); 88 } 89 } 90 91 /** Returns the number of blocks this file contains. */ blockCount()92 int blockCount() { 93 return blockCount; 94 } 95 96 /** Copies the last {@code count} blocks from this file to the end of the given target file. */ copyBlocksTo(RegularFile target, int count)97 void copyBlocksTo(RegularFile target, int count) { 98 int start = blockCount - count; 99 int targetEnd = target.blockCount + count; 100 target.expandIfNecessary(targetEnd); 101 102 System.arraycopy(this.blocks, start, target.blocks, target.blockCount, count); 103 target.blockCount = targetEnd; 104 } 105 106 /** Transfers the last {@code count} blocks from this file to the end of the given target file. */ transferBlocksTo(RegularFile target, int count)107 void transferBlocksTo(RegularFile target, int count) { 108 copyBlocksTo(target, count); 109 truncateBlocks(blockCount - count); 110 } 111 112 /** Truncates the blocks of this file to the given block count. */ truncateBlocks(int count)113 void truncateBlocks(int count) { 114 clear(blocks, count, blockCount - count); 115 blockCount = count; 116 } 117 118 /** Adds the given block to the end of this file. */ addBlock(byte[] block)119 void addBlock(byte[] block) { 120 expandIfNecessary(blockCount + 1); 121 blocks[blockCount++] = block; 122 } 123 124 /** Gets the block at the given index in this file. */ 125 @VisibleForTesting getBlock(int index)126 byte[] getBlock(int index) { 127 return blocks[index]; 128 } 129 130 // end of lower-level methods dealing with the blocks array 131 132 /** 133 * Gets the current size of this file in bytes. Does not do locking, so should only be called when 134 * holding a lock. 135 */ sizeWithoutLocking()136 public long sizeWithoutLocking() { 137 return size; 138 } 139 140 // need to lock in these methods since they're defined by an interface 141 142 @Override size()143 public long size() { 144 readLock().lock(); 145 try { 146 return size; 147 } finally { 148 readLock().unlock(); 149 } 150 } 151 152 @Override copyWithoutContent(int id)153 RegularFile copyWithoutContent(int id) { 154 byte[][] copyBlocks = new byte[Math.max(blockCount * 2, 32)][]; 155 return new RegularFile(id, disk, copyBlocks, 0, size); 156 } 157 158 @Override copyContentTo(File file)159 void copyContentTo(File file) throws IOException { 160 RegularFile copy = (RegularFile) file; 161 disk.allocate(copy, blockCount); 162 163 for (int i = 0; i < blockCount; i++) { 164 byte[] block = blocks[i]; 165 byte[] copyBlock = copy.blocks[i]; 166 System.arraycopy(block, 0, copyBlock, 0, block.length); 167 } 168 } 169 170 @Override contentLock()171 ReadWriteLock contentLock() { 172 return lock; 173 } 174 175 // opened/closed/delete don't use the read/write lock... they only need to ensure that they are 176 // synchronized among themselves 177 178 @Override opened()179 public synchronized void opened() { 180 openCount++; 181 } 182 183 @Override closed()184 public synchronized void closed() { 185 if (--openCount == 0 && deleted) { 186 deleteContents(); 187 } 188 } 189 190 /** 191 * Marks this file as deleted. If there are no streams or channels open to the file, its contents 192 * are deleted if necessary. 193 */ 194 @Override deleted()195 public synchronized void deleted() { 196 if (links() == 0) { 197 deleted = true; 198 if (openCount == 0) { 199 deleteContents(); 200 } 201 } 202 } 203 204 /** 205 * Deletes the contents of this file. Called when this file has been deleted and all open streams 206 * and channels to it have been closed. 207 */ deleteContents()208 private void deleteContents() { 209 disk.free(this); 210 size = 0; 211 } 212 213 /** 214 * Truncates this file to the given {@code size}. If the given size is less than the current size 215 * of this file, the size of the file is reduced to the given size and any bytes beyond that size 216 * are lost. If the given size is greater than the current size of the file, this method does 217 * nothing. Returns {@code true} if this file was modified by the call (its size changed) and 218 * {@code false} otherwise. 219 */ truncate(long size)220 public boolean truncate(long size) { 221 if (size >= this.size) { 222 return false; 223 } 224 225 long lastPosition = size - 1; 226 this.size = size; 227 228 int newBlockCount = blockIndex(lastPosition) + 1; 229 int blocksToRemove = blockCount - newBlockCount; 230 if (blocksToRemove > 0) { 231 disk.free(this, blocksToRemove); 232 } 233 234 return true; 235 } 236 237 /** Prepares for a write of len bytes starting at position pos. */ prepareForWrite(long pos, long len)238 private void prepareForWrite(long pos, long len) throws IOException { 239 long end = pos + len; 240 241 // allocate any additional blocks needed 242 int lastBlockIndex = blockCount - 1; 243 int endBlockIndex = blockIndex(end - 1); 244 245 if (endBlockIndex > lastBlockIndex) { 246 int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; 247 disk.allocate(this, additionalBlocksNeeded); 248 } 249 250 // zero bytes between current size and pos 251 if (pos > size) { 252 long remaining = pos - size; 253 254 int blockIndex = blockIndex(size); 255 byte[] block = blocks[blockIndex]; 256 int off = offsetInBlock(size); 257 258 remaining -= zero(block, off, length(off, remaining)); 259 260 while (remaining > 0) { 261 block = blocks[++blockIndex]; 262 263 remaining -= zero(block, 0, length(remaining)); 264 } 265 266 size = pos; 267 } 268 } 269 270 /** 271 * Writes the given byte to this file at position {@code pos}. {@code pos} may be greater than the 272 * current size of this file, in which case this file is resized and all bytes between the current 273 * size and {@code pos} are set to 0. Returns the number of bytes written. 274 * 275 * @throws IOException if the file needs more blocks but the disk is full 276 */ write(long pos, byte b)277 public int write(long pos, byte b) throws IOException { 278 prepareForWrite(pos, 1); 279 280 byte[] block = blocks[blockIndex(pos)]; 281 int off = offsetInBlock(pos); 282 block[off] = b; 283 284 if (pos >= size) { 285 size = pos + 1; 286 } 287 288 return 1; 289 } 290 291 /** 292 * Writes {@code len} bytes starting at offset {@code off} in the given byte array to this file 293 * starting at position {@code pos}. {@code pos} may be greater than the current size of this 294 * file, in which case this file is resized and all bytes between the current size and {@code pos} 295 * are set to 0. Returns the number of bytes written. 296 * 297 * @throws IOException if the file needs more blocks but the disk is full 298 */ write(long pos, byte[] b, int off, int len)299 public int write(long pos, byte[] b, int off, int len) throws IOException { 300 prepareForWrite(pos, len); 301 302 if (len == 0) { 303 return 0; 304 } 305 306 int remaining = len; 307 308 int blockIndex = blockIndex(pos); 309 byte[] block = blocks[blockIndex]; 310 int offInBlock = offsetInBlock(pos); 311 312 int written = put(block, offInBlock, b, off, length(offInBlock, remaining)); 313 remaining -= written; 314 off += written; 315 316 while (remaining > 0) { 317 block = blocks[++blockIndex]; 318 319 written = put(block, 0, b, off, length(remaining)); 320 remaining -= written; 321 off += written; 322 } 323 324 long endPos = pos + len; 325 if (endPos > size) { 326 size = endPos; 327 } 328 329 return len; 330 } 331 332 /** 333 * Writes all available bytes from buffer {@code buf} to this file starting at position {@code 334 * pos}. {@code pos} may be greater than the current size of this file, in which case this file is 335 * resized and all bytes between the current size and {@code pos} are set to 0. Returns the number 336 * of bytes written. 337 * 338 * @throws IOException if the file needs more blocks but the disk is full 339 */ write(long pos, ByteBuffer buf)340 public int write(long pos, ByteBuffer buf) throws IOException { 341 int len = buf.remaining(); 342 343 prepareForWrite(pos, len); 344 345 if (len == 0) { 346 return 0; 347 } 348 349 int blockIndex = blockIndex(pos); 350 byte[] block = blocks[blockIndex]; 351 int off = offsetInBlock(pos); 352 353 put(block, off, buf); 354 355 while (buf.hasRemaining()) { 356 block = blocks[++blockIndex]; 357 358 put(block, 0, buf); 359 } 360 361 long endPos = pos + len; 362 if (endPos > size) { 363 size = endPos; 364 } 365 366 return len; 367 } 368 369 /** 370 * Writes all available bytes from each buffer in {@code bufs}, in order, to this file starting at 371 * position {@code pos}. {@code pos} may be greater than the current size of this file, in which 372 * case this file is resized and all bytes between the current size and {@code pos} are set to 0. 373 * Returns the number of bytes written. 374 * 375 * @throws IOException if the file needs more blocks but the disk is full 376 */ write(long pos, Iterable<ByteBuffer> bufs)377 public long write(long pos, Iterable<ByteBuffer> bufs) throws IOException { 378 long start = pos; 379 for (ByteBuffer buf : bufs) { 380 pos += write(pos, buf); 381 } 382 return pos - start; 383 } 384 385 /** 386 * Transfers up to {@code count} bytes from the given channel to this file starting at position 387 * {@code pos}. Returns the number of bytes transferred. If {@code pos} is greater than the 388 * current size of this file, the file is truncated up to size {@code pos} before writing. 389 * 390 * @throws IOException if the file needs more blocks but the disk is full or if reading from src 391 * throws an exception 392 */ transferFrom(ReadableByteChannel src, long pos, long count)393 public long transferFrom(ReadableByteChannel src, long pos, long count) throws IOException { 394 prepareForWrite(pos, 0); // don't assume the full count bytes will be written 395 396 if (count == 0) { 397 return 0; 398 } 399 400 long remaining = count; 401 402 int blockIndex = blockIndex(pos); 403 byte[] block = blockForWrite(blockIndex); 404 int off = offsetInBlock(pos); 405 406 ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); 407 408 long currentPos = pos; 409 int read = 0; 410 while (buf.hasRemaining()) { 411 read = src.read(buf); 412 if (read == -1) { 413 break; 414 } 415 416 currentPos += read; 417 remaining -= read; 418 } 419 420 // update size before trying to get next block in case the disk is out of space 421 if (currentPos > size) { 422 size = currentPos; 423 } 424 425 if (read != -1) { 426 outer: 427 while (remaining > 0) { 428 block = blockForWrite(++blockIndex); 429 430 buf = ByteBuffer.wrap(block, 0, length(remaining)); 431 while (buf.hasRemaining()) { 432 read = src.read(buf); 433 if (read == -1) { 434 break outer; 435 } 436 437 currentPos += read; 438 remaining -= read; 439 } 440 441 if (currentPos > size) { 442 size = currentPos; 443 } 444 } 445 } 446 447 if (currentPos > size) { 448 size = currentPos; 449 } 450 451 return currentPos - pos; 452 } 453 454 /** 455 * Reads the byte at position {@code pos} in this file as an unsigned integer in the range 0-255. 456 * If {@code pos} is greater than or equal to the size of this file, returns -1 instead. 457 */ read(long pos)458 public int read(long pos) { 459 if (pos >= size) { 460 return -1; 461 } 462 463 byte[] block = blocks[blockIndex(pos)]; 464 int off = offsetInBlock(pos); 465 return UnsignedBytes.toInt(block[off]); 466 } 467 468 /** 469 * Reads up to {@code len} bytes starting at position {@code pos} in this file to the given byte 470 * array starting at offset {@code off}. Returns the number of bytes actually read or -1 if {@code 471 * pos} is greater than or equal to the size of this file. 472 */ read(long pos, byte[] b, int off, int len)473 public int read(long pos, byte[] b, int off, int len) { 474 // since max is len (an int), result is guaranteed to be an int 475 int bytesToRead = (int) bytesToRead(pos, len); 476 477 if (bytesToRead > 0) { 478 int remaining = bytesToRead; 479 480 int blockIndex = blockIndex(pos); 481 byte[] block = blocks[blockIndex]; 482 int offsetInBlock = offsetInBlock(pos); 483 484 int read = get(block, offsetInBlock, b, off, length(offsetInBlock, remaining)); 485 remaining -= read; 486 off += read; 487 488 while (remaining > 0) { 489 int index = ++blockIndex; 490 block = blocks[index]; 491 492 read = get(block, 0, b, off, length(remaining)); 493 remaining -= read; 494 off += read; 495 } 496 } 497 498 return bytesToRead; 499 } 500 501 /** 502 * Reads up to {@code buf.remaining()} bytes starting at position {@code pos} in this file to the 503 * given buffer. Returns the number of bytes read or -1 if {@code pos} is greater than or equal to 504 * the size of this file. 505 */ read(long pos, ByteBuffer buf)506 public int read(long pos, ByteBuffer buf) { 507 // since max is buf.remaining() (an int), result is guaranteed to be an int 508 int bytesToRead = (int) bytesToRead(pos, buf.remaining()); 509 510 if (bytesToRead > 0) { 511 int remaining = bytesToRead; 512 513 int blockIndex = blockIndex(pos); 514 byte[] block = blocks[blockIndex]; 515 int off = offsetInBlock(pos); 516 517 remaining -= get(block, off, buf, length(off, remaining)); 518 519 while (remaining > 0) { 520 int index = ++blockIndex; 521 block = blocks[index]; 522 remaining -= get(block, 0, buf, length(remaining)); 523 } 524 } 525 526 return bytesToRead; 527 } 528 529 /** 530 * Reads up to the total {@code remaining()} number of bytes in each of {@code bufs} starting at 531 * position {@code pos} in this file to the given buffers, in order. Returns the number of bytes 532 * read or -1 if {@code pos} is greater than or equal to the size of this file. 533 */ read(long pos, Iterable<ByteBuffer> bufs)534 public long read(long pos, Iterable<ByteBuffer> bufs) { 535 if (pos >= size()) { 536 return -1; 537 } 538 539 long start = pos; 540 for (ByteBuffer buf : bufs) { 541 int read = read(pos, buf); 542 if (read == -1) { 543 break; 544 } else { 545 pos += read; 546 } 547 } 548 549 return pos - start; 550 } 551 552 /** 553 * Transfers up to {@code count} bytes to the given channel starting at position {@code pos} in 554 * this file. Returns the number of bytes transferred, possibly 0. Note that unlike all other read 555 * methods in this class, this method does not return -1 if {@code pos} is greater than or equal 556 * to the current size. This for consistency with {@link FileChannel#transferTo}, which this 557 * method is primarily intended as an implementation of. 558 */ transferTo(long pos, long count, WritableByteChannel dest)559 public long transferTo(long pos, long count, WritableByteChannel dest) throws IOException { 560 long bytesToRead = bytesToRead(pos, count); 561 562 if (bytesToRead > 0) { 563 long remaining = bytesToRead; 564 565 int blockIndex = blockIndex(pos); 566 byte[] block = blocks[blockIndex]; 567 int off = offsetInBlock(pos); 568 569 ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); 570 while (buf.hasRemaining()) { 571 remaining -= dest.write(buf); 572 } 573 buf.clear(); 574 575 while (remaining > 0) { 576 int index = ++blockIndex; 577 block = blocks[index]; 578 579 buf = ByteBuffer.wrap(block, 0, length(remaining)); 580 while (buf.hasRemaining()) { 581 remaining -= dest.write(buf); 582 } 583 buf.clear(); 584 } 585 } 586 587 return Math.max(bytesToRead, 0); // don't return -1 for this method 588 } 589 590 /** Gets the block at the given index, expanding to create the block if necessary. */ blockForWrite(int index)591 private byte[] blockForWrite(int index) throws IOException { 592 if (index >= blockCount) { 593 int additionalBlocksNeeded = index - blockCount + 1; 594 disk.allocate(this, additionalBlocksNeeded); 595 } 596 597 return blocks[index]; 598 } 599 blockIndex(long position)600 private int blockIndex(long position) { 601 return (int) (position / disk.blockSize()); 602 } 603 offsetInBlock(long position)604 private int offsetInBlock(long position) { 605 return (int) (position % disk.blockSize()); 606 } 607 length(long max)608 private int length(long max) { 609 return (int) Math.min(disk.blockSize(), max); 610 } 611 length(int off, long max)612 private int length(int off, long max) { 613 return (int) Math.min(disk.blockSize() - off, max); 614 } 615 616 /** 617 * Returns the number of bytes that can be read starting at position {@code pos} (up to a maximum 618 * of {@code max}) or -1 if {@code pos} is greater than or equal to the current size. 619 */ bytesToRead(long pos, long max)620 private long bytesToRead(long pos, long max) { 621 long available = size - pos; 622 if (available <= 0) { 623 return -1; 624 } 625 return Math.min(available, max); 626 } 627 628 /** Zeroes len bytes in the given block starting at the given offset. Returns len. */ zero(byte[] block, int offset, int len)629 private static int zero(byte[] block, int offset, int len) { 630 Util.zero(block, offset, len); 631 return len; 632 } 633 634 /** Puts the given slice of the given array at the given offset in the given block. */ put(byte[] block, int offset, byte[] b, int off, int len)635 private static int put(byte[] block, int offset, byte[] b, int off, int len) { 636 System.arraycopy(b, off, block, offset, len); 637 return len; 638 } 639 640 /** Puts the contents of the given byte buffer at the given offset in the given block. */ put(byte[] block, int offset, ByteBuffer buf)641 private static int put(byte[] block, int offset, ByteBuffer buf) { 642 int len = Math.min(block.length - offset, buf.remaining()); 643 buf.get(block, offset, len); 644 return len; 645 } 646 647 /** 648 * Reads len bytes starting at the given offset in the given block into the given slice of the 649 * given byte array. 650 */ get(byte[] block, int offset, byte[] b, int off, int len)651 private static int get(byte[] block, int offset, byte[] b, int off, int len) { 652 System.arraycopy(block, offset, b, off, len); 653 return len; 654 } 655 656 /** Reads len bytes starting at the given offset in the given block into the given byte buffer. */ get(byte[] block, int offset, ByteBuffer buf, int len)657 private static int get(byte[] block, int offset, ByteBuffer buf, int len) { 658 buf.put(block, offset, len); 659 return len; 660 } 661 } 662