/*-
* Copyright (C) 2006-2009 Erik Larsson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catacombae.hfs.plus;
import java.util.Iterator;
import java.util.LinkedList;
import org.catacombae.hfs.types.hfsplus.HFSPlusVolumeHeader;
import org.catacombae.hfs.types.hfsplus.JournalInfoBlock;
import org.catacombae.io.ReadableRandomAccessStream;
import org.catacombae.hfs.Journal;
import org.catacombae.hfs.types.hfsplus.BlockInfo;
import org.catacombae.hfs.types.hfsplus.BlockList;
import org.catacombae.hfs.types.hfsplus.BlockListHeader;
import org.catacombae.hfs.types.hfsplus.JournalHeader;
import org.catacombae.io.ReadableConcatenatedStream;
import org.catacombae.io.RuntimeIOException;
import org.catacombae.util.ObjectContainer;
import org.catacombae.util.Util;
/**
* @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a>
*/
class HFSPlusJournal extends Journal {
private final HFSPlusVolume vol;
public HFSPlusJournal(HFSPlusVolume vol) {
this.vol = vol;
}
@Override
public byte[] getInfoBlockData() {
HFSPlusVolumeHeader vh = vol.getHFSPlusVolumeHeader();
if(vh.getAttributeVolumeJournaled()) {
long blockNumber = Util.unsign(vh.getJournalInfoBlock());
byte[] data = new byte[JournalInfoBlock.getStructSize()];
ReadableRandomAccessStream fsStream = vol.createFSStream();
try {
fsStream.seek(blockNumber * vh.getBlockSize());
fsStream.readFully(data);
} finally {
fsStream.close();
}
return data;
}
else
return null;
}
@Override
public ReadableRandomAccessStream getJournalDataStream() {
JournalInfoBlock infoBlock = getJournalInfoBlock();
return getJournalDataStream(infoBlock);
}
private ReadableRandomAccessStream getJournalDataStream(
JournalInfoBlock infoBlock)
{
if(!infoBlock.getFlagJournalInFS()) {
/* Searching other devices for the journal is unsupported at this
* time. */
return null;
}
if(infoBlock.getFlagJournalNeedInit()) {
/* Journal needs to be initialized and does not contain any valid
* data. In this case we also return null, because whatever data
* might be in the journal is definitely invalid. */
return null;
}
if(infoBlock.getRawOffset() < 0)
throw new Error("SInt64 overflow for JournalInfoBlock.offset!");
if(infoBlock.getRawSize() < 0)
throw new Error("SInt64 overflow for JournalInfoBlock.size!");
return new ReadableConcatenatedStream(vol.createFSStream(),
infoBlock.getRawOffset(), infoBlock.getRawSize());
}
@Override
public byte[] getJournalData() {
/* TODO: Maybe it's sane to return a stream and not a byte[] since we
* don't know the size of the journal? */
ReadableRandomAccessStream fsStream = getJournalDataStream();
if(fsStream.length() > Integer.MAX_VALUE)
throw new RuntimeException("Java int overflow!");
byte[] dataBuffer = new byte[(int) fsStream.length()];
try {
fsStream.readFully(dataBuffer);
} finally {
fsStream.close();
}
return dataBuffer;
}
@Override
public JournalInfoBlock getJournalInfoBlock() {
byte[] infoBlockData = getInfoBlockData();
if(infoBlockData != null)
return new JournalInfoBlock(infoBlockData, 0);
else
return null;
}
@Override
public JournalHeader getJournalHeader() {
final JournalInfoBlock infoBlock = getJournalInfoBlock();
final ReadableRandomAccessStream journalStream =
getJournalDataStream(infoBlock);
try {
return journalStream != null ?
getJournalHeader(infoBlock, journalStream) : null;
} finally {
if(journalStream != null) {
journalStream.close();
}
}
}
private JournalHeader getJournalHeader(JournalInfoBlock infoBlock,
ReadableRandomAccessStream journalStream)
{
final byte[] headerData = new byte[JournalHeader.length()];
journalStream.readFully(headerData);
JournalHeader jh = new JournalHeader(headerData, 0);
if(jh.getRawChecksum() != jh.calculateChecksum()) {
throw new RuntimeException("Invalid journal header checksum " +
"(expected 0x" + Util.toHexStringBE(jh.getRawChecksum()) +
", got 0x" + Util.toHexStringBE(jh.calculateChecksum()) +
").");
}
if(infoBlock.getRawSize() != jh.getRawSize()) {
throw new RuntimeException("Inconsistency between journal size " +
"as described by journal info block (" +
infoBlock.getSize() + ") and journal header (" +
jh.getSize() + ").");
}
return jh;
}
@Override
public boolean isClean() {
final JournalHeader journalHeader = getJournalHeader();
return journalHeader.getRawStart() == journalHeader.getRawEnd();
}
private long wrappedReadFully(ReadableRandomAccessStream stream,
long currentPos, byte[] data, int offset, int length,
ObjectContainer<Boolean> wrappedAround)
{
int bytesRead;
long res;
bytesRead = stream.read(data, offset, length);
if(bytesRead != length) {
/* Short read. Wrap around. */
if(wrappedAround.o) {
throw new RuntimeException("Wrapped around twice!");
}
res = length - bytesRead;
wrappedAround.o = true;
stream.seek(0);
bytesRead += stream.read(data, offset + bytesRead,
length - bytesRead);
}
else {
res = currentPos + length;
}
if(bytesRead != length) {
throw new RuntimeIOException("Failed to read requested " +
"amount when doing wrapped read. Expected " + length + " " +
"bytes, got " + bytesRead + " bytes.");
}
return res;
}
private long wrappedReadFully(ReadableRandomAccessStream stream,
long currentPos, byte[] data,
ObjectContainer<Boolean> wrappedAround)
{
return wrappedReadFully(stream, currentPos, data, 0, data.length,
wrappedAround);
}
public Transaction[] getPendingTransactions() {
final JournalInfoBlock infoBlock = getJournalInfoBlock();
final ReadableRandomAccessStream journalStream =
getJournalDataStream(infoBlock);
final JournalHeader jh = getJournalHeader(infoBlock, journalStream);
final long start = jh.getRawStart();
final long end = jh.getRawEnd();
final long size = jh.getRawSize();
final int blockListHeaderSize = jh.getRawBlhdrSize();
if(start < 0)
throw new RuntimeException("'start' overflows.");
if(end < 0)
throw new RuntimeException("'end' overflows.");
if(size < 0)
throw new RuntimeException("'size' overflows.");
if(blockListHeaderSize < 0)
throw new RuntimeException("'blockListHeaderSize' overflows.");
if(start == end) {
return new Transaction[0];
}
final LinkedList<Transaction> pendingTransactionList =
new LinkedList<Transaction>();
final LinkedList<BlockList> curBlockListList =
new LinkedList<BlockList>();
final LinkedList<BlockInfo> curBlockInfoList =
new LinkedList<BlockInfo>();
ObjectContainer<Boolean> wrappedAround =
new ObjectContainer<Boolean>(false);
byte[] tmpData = new byte[Math.max(BlockListHeader.length(),
BlockInfo.length())];
journalStream.seek(start);
for(long i = start; i != end;) {
long curBytesRead = 0;
i = wrappedReadFully(journalStream, i, tmpData, 0,
BlockListHeader.length(), wrappedAround);
BlockListHeader curHeader =
new BlockListHeader(tmpData, 0, jh.isLittleEndian());
if(curHeader.getNumBlocks() < 1) {
throw new RuntimeException("Empty block list makes no sense.");
}
else if((curHeader.getMaxBlocks() * 16 + 16) != blockListHeaderSize)
{
throw new RuntimeException("Unexpected value for maxBlocks " +
"member of BlockListHeader: " +
curHeader.getMaxBlocks());
}
curBytesRead += BlockListHeader.length();
curBlockInfoList.clear();
for(int j = 0; j < curHeader.getNumBlocks(); ++j) {
i = wrappedReadFully(journalStream, i, tmpData, 0,
BlockInfo.length(), wrappedAround);
curBlockInfoList.add(new BlockInfo(tmpData, 0,
jh.isLittleEndian()));
curBytesRead += BlockInfo.length();
}
if(curHeader.calculateChecksum(curBlockInfoList.getFirst()) !=
curHeader.getRawChecksum())
{
throw new RuntimeException("Checksum mismatch for header "
+ "(expected: 0x" +
Util.toHexStringBE(curHeader.getRawChecksum()) + " "
+ "actual: 0x" +
Util.toHexStringBE(curHeader.calculateChecksum(
curBlockInfoList.getFirst())) + ")");
}
byte[] curReserved =
new byte[(int) (blockListHeaderSize - curBytesRead)];
i = wrappedReadFully(journalStream, i, curReserved, wrappedAround);
curBytesRead += curReserved.length;
LinkedList<byte[]> curBlockDataList = new LinkedList<byte[]>();
for(Iterator<BlockInfo> it = curBlockInfoList.iterator();
it.hasNext();)
{
final BlockInfo bi = it.next();
if(curBlockDataList.size() < 1) {
/* Skip first BlockInfo because it's not actually
* referencing any data. */
curBlockDataList.add(new byte[0]);
continue;
}
final int bsize = bi.getRawBsize();
if(bsize > Integer.MAX_VALUE) {
throw new RuntimeException("'int' overflow in 'bsize' (" +
bi.getBsize() + ").");
}
final byte[] data = new byte[bsize];
i = wrappedReadFully(journalStream, i, data, wrappedAround);
curBytesRead += data.length;
curBlockDataList.add(data);
}
BlockList curBlockList = new BlockList(curHeader,
curBlockInfoList.toArray(
new BlockInfo[curBlockInfoList.size()]),
curReserved,
curBlockDataList.toArray(
new byte[curBlockDataList.size()][]));
curBlockListList.add(curBlockList);
if(curBlockList.getBlockInfo(0).getNext() == 0) {
pendingTransactionList.add(new Transaction(
curBlockListList.toArray(
new BlockList[curBlockListList.size()])));
curBlockListList.clear();
}
}
if(curBlockListList.size() != 0) {
pendingTransactionList.add(new Transaction(curBlockListList.toArray(
new BlockList[curBlockListList.size()])));
}
return pendingTransactionList.toArray(
new Transaction[pendingTransactionList.size()]);
}
}