• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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