/* * Copyright (C) 2017, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.internal.storage.dfs; import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import java.text.MessageFormat; import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.internal.storage.pack.PackExt; /** Block based file stored in {@link DfsBlockCache}. */ abstract class BlockBasedFile { /** Cache that owns this file and its data. */ final DfsBlockCache cache; /** Unique identity of this file while in-memory. */ final DfsStreamKey key; /** Description of the associated pack file's storage. */ final DfsPackDescription desc; final PackExt ext; /** * Preferred alignment for loading blocks from the backing file. *

* It is initialized to 0 and filled in on the first read made from the * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS * storing 4091 user bytes and 5 bytes block metadata into a lower level * 4096 byte block on disk. */ volatile int blockSize; /** * Total number of bytes in this pack file. *

* This field initializes to -1 and gets populated when a block is loaded. */ volatile long length; /** True once corruption has been detected that cannot be worked around. */ volatile boolean invalid; /** Exception that caused the packfile to be flagged as invalid */ protected volatile Exception invalidatingCause; BlockBasedFile(DfsBlockCache cache, DfsPackDescription desc, PackExt ext) { this.cache = cache; this.key = desc.getStreamKey(ext); this.desc = desc; this.ext = ext; } String getFileName() { return desc.getFileName(ext); } boolean invalid() { return invalid; } void setInvalid() { invalid = true; } void setBlockSize(int newSize) { blockSize = newSize; } long alignToBlock(long pos) { int size = blockSize; if (size == 0) size = cache.getBlockSize(); return (pos / size) * size; } int blockSize(ReadableChannel rc) { // If the block alignment is not yet known, discover it. Prefer the // larger size from either the cache or the file itself. int size = blockSize; if (size == 0) { size = rc.blockSize(); if (size <= 0) size = cache.getBlockSize(); else if (size < cache.getBlockSize()) size = (cache.getBlockSize() / size) * size; blockSize = size; } return size; } DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException { try (LazyChannel c = new LazyChannel(ctx, desc, ext)) { DfsBlock block = cache.getOrLoad(this, pos, ctx, c); ctx.emitBlockLoad(this, pos, block); return block; } } DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc) throws IOException { if (invalid) { throw new PackInvalidException(getFileName(), invalidatingCause); } ctx.stats.readBlock++; long start = System.nanoTime(); try { int size = blockSize(rc); pos = (pos / size) * size; // If the size of the file is not yet known, try to discover it. // Channels may choose to return -1 to indicate they don't // know the length yet, in this case read up to the size unit // given by the caller, then recheck the length. long len = length; if (len < 0) { len = rc.size(); if (0 <= len) length = len; } if (0 <= len && len < pos + size) size = (int) (len - pos); if (size <= 0) throw new EOFException(MessageFormat.format( DfsText.get().shortReadOfBlock, Long.valueOf(pos), getFileName(), Long.valueOf(0), Long.valueOf(0))); byte[] buf = new byte[size]; rc.position(pos); int cnt = read(rc, ByteBuffer.wrap(buf, 0, size)); ctx.stats.readBlockBytes += cnt; if (cnt != size) { if (0 <= len) { throw new EOFException(MessageFormat.format( DfsText.get().shortReadOfBlock, Long.valueOf(pos), getFileName(), Integer.valueOf(size), Integer.valueOf(cnt))); } // Assume the entire thing was read in a single shot, compact // the buffer to only the space required. byte[] n = new byte[cnt]; System.arraycopy(buf, 0, n, 0, n.length); buf = n; } else if (len < 0) { // With no length at the start of the read, the channel should // have the length available at the end. length = len = rc.size(); } return new DfsBlock(key, pos, buf); } finally { ctx.stats.readBlockMicros += elapsedMicros(start); } } static int read(ReadableChannel rc, ByteBuffer buf) throws IOException { int n; do { n = rc.read(buf); } while (0 < n && buf.hasRemaining()); return buf.position(); } static long elapsedMicros(long start) { return (System.nanoTime() - start) / 1000L; } /** * A supplier of readable channel that opens the channel lazily. */ private static class LazyChannel implements AutoCloseable, DfsBlockCache.ReadableChannelSupplier { private final DfsReader ctx; private final DfsPackDescription desc; private final PackExt ext; private ReadableChannel rc; LazyChannel(DfsReader ctx, DfsPackDescription desc, PackExt ext) { this.ctx = ctx; this.desc = desc; this.ext = ext; } @Override public ReadableChannel get() throws IOException { if (rc == null) { rc = ctx.db.openFile(desc, ext); } return rc; } @Override public void close() throws IOException { if (rc != null) { rc.close(); } } } }