/** * Creates a new regular file with the given ID and using the given disk. */ public static RegularFile create(int id, HeapDisk disk) { return new RegularFile(id, disk, new byte[32][], 0, 0); }
/** * Allocates the given number of blocks and adds them to the given file. */ public synchronized void allocate(RegularFile file, int count) throws IOException { int newAllocatedBlockCount = allocatedBlockCount + count; if (newAllocatedBlockCount > maxBlockCount) { throw new IOException("out of disk space"); } int newBlocksNeeded = Math.max(count - blockCache.blockCount(), 0); for (int i = 0; i < newBlocksNeeded; i++) { file.addBlock(new byte[blockSize]); } if (newBlocksNeeded != count) { blockCache.transferBlocksTo(file, count - newBlocksNeeded); } allocatedBlockCount = newAllocatedBlockCount; }
prepareForWrite(pos, 0); // don't assume the full count bytes will be written int blockIndex = blockIndex(pos); byte[] block = blockForWrite(blockIndex); int off = offsetInBlock(pos); ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); outer: while (remaining > 0) { block = blockForWrite(++blockIndex); buf = ByteBuffer.wrap(block, 0, length(remaining)); while (buf.hasRemaining()) { read = src.read(buf);
/** * Marks this file as deleted. If there are no streams or channels open to the file, its * contents are deleted if necessary. */ @Override public synchronized void deleted() { if (links() == 0) { deleted = true; if (openCount == 0) { deleteContents(); } } }
/** * Transfers the last {@code count} blocks from this file to the end of the given target file. */ void transferBlocksTo(RegularFile target, int count) { copyBlocksTo(target, count); truncateBlocks(blockCount - count); }
/** * Frees the last {@code count} blocks from the given file. */ public synchronized void free(RegularFile file, int count) { int remainingCacheSpace = maxCachedBlockCount - blockCache.blockCount(); if (remainingCacheSpace > 0) { file.copyBlocksTo(blockCache, Math.min(count, remainingCacheSpace)); } file.truncateBlocks(file.blockCount() - count); allocatedBlockCount -= count; } }
/** * Reads up to {@code len} bytes starting at position {@code pos} in this file to the given byte * array starting at offset {@code off}. Returns the number of bytes actually read or -1 if {@code * pos} is greater than or equal to the size of this file. */ public int read(long pos, byte[] b, int off, int len) { // since max is len (an int), result is guaranteed to be an int int bytesToRead = (int) bytesToRead(pos, len); if (bytesToRead > 0) { int remaining = bytesToRead; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int offsetInBlock = offsetInBlock(pos); int read = get(block, offsetInBlock, b, off, length(offsetInBlock, remaining)); remaining -= read; off += read; while (remaining > 0) { int index = ++blockIndex; block = blocks[index]; read = get(block, 0, b, off, length(remaining)); remaining -= read; off += read; } } return bytesToRead; }
prepareForWrite(pos, len); int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int offInBlock = offsetInBlock(pos); int written = put(block, offInBlock, b, off, length(offInBlock, remaining)); remaining -= written; off += written; block = blocks[++blockIndex]; written = put(block, 0, b, off, length(remaining)); remaining -= written; off += written;
/** * Prepares for a write of len bytes starting at position pos. */ private void prepareForWrite(long pos, long len) throws IOException { long end = pos + len; // allocate any additional blocks needed int lastBlockIndex = blockCount - 1; int endBlockIndex = blockIndex(end - 1); if (endBlockIndex > lastBlockIndex) { int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; disk.allocate(this, additionalBlocksNeeded); } // zero bytes between current size and pos if (pos > size) { long remaining = pos - size; int blockIndex = blockIndex(size); byte[] block = blocks[blockIndex]; int off = offsetInBlock(size); remaining -= zero(block, off, length(off, remaining)); while (remaining > 0) { block = blocks[++blockIndex]; remaining -= zero(block, 0, length(remaining)); } size = pos; } }
/** * Writes the given byte to this file at position {@code pos}. {@code pos} may be greater than * the current size of this file, in which case this file is resized and all bytes between the * current size and {@code pos} are set to 0. Returns the number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public int write(long pos, byte b) throws IOException { prepareForWrite(pos, 1); byte[] block = blocks[blockIndex(pos)]; int off = offsetInBlock(pos); block[off] = b; if (pos >= size) { size = pos + 1; } return 1; }
/** * Reads the byte at position {@code pos} in this file as an unsigned integer in the range 0-255. * If {@code pos} is greater than or equal to the size of this file, returns -1 instead. */ public int read(long pos) { if (pos >= size) { return -1; } byte[] block = blocks[blockIndex(pos)]; int off = offsetInBlock(pos); return UnsignedBytes.toInt(block[off]); }
@Override protected void implCloseChannel() { // interrupt the current blocking threads, if any, causing them to throw // ClosedByInterruptException try { synchronized (blockingThreads) { for (Thread thread : blockingThreads) { thread.interrupt(); } } } finally { fileSystemState.unregister(this); file.closed(); } }
/** * Creates a new regular file. */ @VisibleForTesting RegularFile createRegularFile() { return RegularFile.create(nextFileId(), disk); }
@Override public synchronized void closed() { if (--openCount == 0 && deleted) { deleteContents(); } }
/** * Frees all blocks in the given file. */ public void free(RegularFile file) { free(file, file.blockCount()); }
/** * Truncates this file to the given {@code size}. If the given size is less than the current size * of this file, the size of the file is reduced to the given size and any bytes beyond that * size are lost. If the given size is greater than the current size of the file, this method * does nothing. Returns {@code true} if this file was modified by the call (its size changed) * and {@code false} otherwise. */ public boolean truncate(long size) { if (size >= this.size) { return false; } long lastPosition = size - 1; this.size = size; int newBlockCount = blockIndex(lastPosition) + 1; int blocksToRemove = blockCount - newBlockCount; if (blocksToRemove > 0) { disk.free(this, blocksToRemove); } return true; }
/** * Reads up to {@code buf.remaining()} bytes starting at position {@code pos} in this file to the * given buffer. Returns the number of bytes read or -1 if {@code pos} is greater than or equal to * the size of this file. */ public int read(long pos, ByteBuffer buf) { // since max is buf.remaining() (an int), result is guaranteed to be an int int bytesToRead = (int) bytesToRead(pos, buf.remaining()); if (bytesToRead > 0) { int remaining = bytesToRead; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int off = offsetInBlock(pos); remaining -= get(block, off, buf, length(off, remaining)); while (remaining > 0) { int index = ++blockIndex; block = blocks[index]; remaining -= get(block, 0, buf, length(remaining)); } } return bytesToRead; }
/** * Writes all available bytes from buffer {@code buf} to this file starting at position {@code * pos}. {@code pos} may be greater than the current size of this file, in which case this file * is resized and all bytes between the current size and {@code pos} are set to 0. Returns the * number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public int write(long pos, ByteBuffer buf) throws IOException { int len = buf.remaining(); prepareForWrite(pos, len); if (len == 0) { return 0; } int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int off = offsetInBlock(pos); put(block, off, buf); while (buf.hasRemaining()) { block = blocks[++blockIndex]; put(block, 0, buf); } long endPos = pos + len; if (endPos > size) { size = endPos; } return len; }
/** * Prepares for a write of len bytes starting at position pos. */ private void prepareForWrite(long pos, long len) throws IOException { long end = pos + len; // allocate any additional blocks needed int lastBlockIndex = blockCount - 1; int endBlockIndex = blockIndex(end - 1); if (endBlockIndex > lastBlockIndex) { int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; disk.allocate(this, additionalBlocksNeeded); } // zero bytes between current size and pos if (pos > size) { long remaining = pos - size; int blockIndex = blockIndex(size); byte[] block = blocks[blockIndex]; int off = offsetInBlock(size); remaining -= zero(block, off, length(off, remaining)); while (remaining > 0) { block = blocks[++blockIndex]; remaining -= zero(block, 0, length(remaining)); } size = pos; } }
/** * Frees the last {@code count} blocks from the given file. */ public synchronized void free(RegularFile file, int count) { int remainingCacheSpace = maxCachedBlockCount - blockCache.blockCount(); if (remainingCacheSpace > 0) { file.copyBlocksTo(blockCache, Math.min(count, remainingCacheSpace)); } file.truncateBlocks(file.blockCount() - count); allocatedBlockCount -= count; } }