#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <sys/param.h>
#include "../fsck_hfs.h"
#include "fsck_journal.h"
extern char debug;
#include <hfs/hfs_format.h>
#include <libkern/OSByteOrder.h>
typedef struct SwapType {
const char *name;
uint16_t (^swap16)(uint16_t);
uint32_t (^swap32)(uint32_t);
uint64_t (^swap64)(uint64_t);
} swapper_t;
static swapper_t nativeEndian = {
"native endian",
^(uint16_t x) { return x; },
^(uint32_t x) { return x; },
^(uint64_t x) { return x; }
};
static swapper_t swappedEndian = {
"swapped endian",
^(uint16_t x) { return OSSwapInt16(x); },
^(uint32_t x) { return OSSwapInt32(x); },
^(uint64_t x) { return OSSwapInt64(x); }
};
typedef int (^journal_write_block_t)(off_t, void *, size_t);
static uint32_t
calc_checksum(char *ptr, int len)
{
int i;
uint32_t cksum = 0;
for(i = 0; i < len; i++, ptr++) {
cksum = (cksum << 8) ^ (cksum + *(unsigned char *)ptr);
}
return (~cksum);
}
typedef struct JournalIOInfo {
int jfd; int wrapCount; size_t bSize; uint64_t base; uint64_t size; uint64_t end; uint64_t current; } JournalIOInfo_t;
static ssize_t
journalRead(JournalIOInfo_t *info, uint8_t *buffer, size_t length)
{
size_t nread = 0;
uint8_t *ptr = buffer;
if (info->wrapCount > 1) {
fplog(stderr, "%s(%p, %p, %zu): journal buffer wrap count = %d\n", __FUNCTION__, info, buffer, length, info->wrapCount);
return -1;
}
while (nread < length) {
off_t end;
size_t amt;
ssize_t n;
if (info->end < info->current) {
end = info->base + info->size;
} else {
end = info->end;
}
amt = MIN((length - nread), (end - info->current));
if (amt == 0) {
if (debug) {
fplog(stderr, "Journal read amount is 0, is that right?\n");
}
goto done;
}
n = pread(info->jfd, ptr, amt, info->current);
if (n == -1) {
warn("pread(%d, %p, %zu, %llu)", info->jfd, ptr, amt, info->current);
goto done;
}
if (n != amt) {
if (debug) {
fplog(stderr, "%s(%d): Wanted to read %zu, but only read %zd\n", __FUNCTION__, __LINE__, amt, n);
}
}
nread += n;
ptr += n;
info->current += n;
if (info->current == (info->base + info->size)) {
info->current = info->base;
info->wrapCount++;
}
}
done:
return nread;
}
static block_list_header *
getJournalTransaction(JournalIOInfo_t *jinfo, swapper_t *swap)
{
block_list_header *retval = NULL;
uint8_t block[jinfo->bSize];
block_list_header *hdr = (void*)█
ssize_t nread;
ssize_t amt;
memset(block, 0, sizeof(block));
nread = journalRead(jinfo, block, sizeof(block));
if (nread == -1 ||
(size_t)nread != sizeof(block)) {
if (debug)
plog("%s: wanted %zd, got %zd\n", __FUNCTION__, sizeof(block), nread);
return NULL;
}
if (swap->swap32(hdr->num_blocks) == 0) {
if (debug)
fplog(stderr, "%s(%d): hdr->num_blocks == 0\n", __FUNCTION__, __LINE__);
return NULL;
}
uint32_t tmpChecksum = swap->swap32(hdr->checksum);
uint32_t compChecksum;
hdr->checksum = 0;
compChecksum = calc_checksum((void*)hdr, sizeof(*hdr));
hdr->checksum = swap->swap32(tmpChecksum);
if (compChecksum != tmpChecksum) {
if (debug)
fplog(stderr, "%s(%d): hdr has bad checksum, returning NULL\n", __FUNCTION__, __LINE__);
return NULL;
}
if (swap->swap32(hdr->bytes_used) < sizeof(block)) {
if (debug) {
fplog(stderr, "%s(%d): hdr has bytes_used (%u) less than sizeof block (%zd)\n",
__FUNCTION__, __LINE__, swap->swap32(hdr->bytes_used), sizeof(block));
}
return NULL;
}
retval = malloc(swap->swap32(hdr->bytes_used));
if (retval == NULL)
return NULL;
memset(retval, 0, swap->swap32(hdr->bytes_used));
memcpy(retval, block, sizeof(block));
amt = swap->swap32(hdr->bytes_used) - sizeof(block);
nread = journalRead(jinfo, ((uint8_t*)retval) + sizeof(block), amt);
if (nread != amt) {
free(retval);
return NULL;
}
return retval;
}
static int
replayTransaction(block_list_header *txn, size_t blSize, size_t blkSize, swapper_t *swap, journal_write_block_t writer)
{
uint32_t i;
uint8_t *endPtr = ((uint8_t*)txn) + swap->swap32(txn->bytes_used);
uint8_t *dataPtr = ((uint8_t*)txn) + blSize;
int retval = -1;
for (i = 1; i < swap->swap32(txn->num_blocks); i++) {
if (debug)
plog("\tBlock %d: blkNum %llu, size %u, data offset = %zd\n", i, swap->swap64(txn->binfo[i].bnum), swap->swap32(txn->binfo[i].bsize), dataPtr - (uint8_t*)txn);
if (dataPtr > endPtr) {
if (debug)
plog("\tData out of range for block_list_header\n");
return retval;
}
if ((endPtr - dataPtr) < swap->swap32(txn->binfo[i].bsize)) {
if (debug)
plog("\tData size for block %d out of range for block_list_header\n", i);
return retval;
}
if ((dataPtr + swap->swap32(txn->binfo[i].bsize)) > endPtr) {
if (debug)
plog("\tData end out of range for block_list_header\n");
return retval;
}
if (debug) {
if (swap->swap64(txn->binfo[i].bnum) == 2) {
HFSPlusVolumeHeader *vp = (void*)dataPtr;
plog("vp->signature = %#x, version = %#x\n", vp->signature, vp->version);
}
}
if (swap->swap64(txn->binfo[i].bnum) == ~(uint64_t)0) {
if (debug)
plog("\tSkipping this block due to magic skip number\n");
} else {
if (writer) {
if ((writer)(swap->swap64(txn->binfo[i].bnum) * blkSize, dataPtr, swap->swap32(txn->binfo[i].bsize)) == -1)
return retval;
}
}
dataPtr += swap->swap32(txn->binfo[i].bsize);
retval = -2;
}
return 0;
}
static int
loadJournalHeader(int jfd, off_t offset, size_t blockSize, journal_header *jhp)
{
uint8_t buffer[blockSize];
ssize_t nread;
nread = pread(jfd, buffer, sizeof(buffer), offset);
if (nread == -1 ||
(size_t)nread != sizeof(buffer)) {
warn("tried to read %zu for journal header buffer, got %zd", sizeof(buffer), nread);
return -1;
}
*jhp = *(journal_header*)buffer;
return 0;
}
int
journal_open(int jfd,
off_t offset, off_t journal_size, size_t min_fs_blksize, uint32_t flags __unused, const char *jdev_name, int (^do_write_b)(off_t, void*, size_t))
{
journal_header jhdr = { 0 };
swapper_t *jnlSwap; uint32_t tempCksum; uint32_t jBlkSize = 0;
if (ioctl(jfd, DKIOCGETBLOCKSIZE, &jBlkSize) == -1) {
jBlkSize = min_fs_blksize;
} else {
if (jBlkSize < min_fs_blksize) {
fplog(stderr, "%s: journal block size %u < min block size %zu for %s\n", __FUNCTION__, jBlkSize, min_fs_blksize, jdev_name);
return -1;
}
if ((jBlkSize % min_fs_blksize) != 0) {
fplog(stderr, "%s: journal block size %u is not a multiple of fs block size %zu for %s\n", __FUNCTION__, jBlkSize, min_fs_blksize, jdev_name);
return -1;
}
}
if (loadJournalHeader(jfd, offset, jBlkSize, &jhdr) != 0) {
fplog(stderr, "%s: unable to load journal header from %s\n", __FUNCTION__, jdev_name);
return -1;
}
if (jhdr.endian == ENDIAN_MAGIC) {
jnlSwap = &nativeEndian;
} else if (OSSwapInt32(jhdr.endian) == ENDIAN_MAGIC) {
jnlSwap = &swappedEndian;
} else {
fplog(stderr, "%s: Unknown journal endian magic number %#x from %s\n", __FUNCTION__, jhdr.endian, jdev_name);
return -1;
}
if (jnlSwap->swap32(jhdr.magic) != JOURNAL_HEADER_MAGIC &&
jnlSwap->swap32(jhdr.magic) != OLD_JOURNAL_HEADER_MAGIC) {
fplog(stderr, "%s: Unknown journal header magic number %#x from %s\n", __FUNCTION__, jhdr.magic, jdev_name);
return -1;
}
tempCksum = jnlSwap->swap32(jhdr.checksum);
jhdr.checksum = 0;
if (jnlSwap->swap32(jhdr.magic) == JOURNAL_HEADER_MAGIC &&
(calc_checksum((void*)&jhdr, JOURNAL_HEADER_CKSUM_SIZE) != tempCksum)) {
fplog(stderr, "%s: Invalid journal checksum from %s\n", __FUNCTION__, jdev_name);
return -1;
}
jhdr.checksum = jnlSwap->swap32(tempCksum);
off_t startOffset = jnlSwap->swap64(jhdr.start);
off_t endOffset =jnlSwap->swap64(jhdr.end);
off_t journalStart = offset + jnlSwap->swap32(jhdr.jhdr_size);
int into_the_weeds = 0;
uint32_t last_sequence_number = 0;
JournalIOInfo_t jinfo = { 0 };
if (debug)
plog("Journal start sequence number = %u\n", jnlSwap->swap32(jhdr.sequence_num));
jinfo.jfd = jfd;
jinfo.bSize = jnlSwap->swap32(jhdr.jhdr_size);
jinfo.base = journalStart;
jinfo.size = journal_size - jinfo.bSize;
jinfo.end = offset + endOffset;
jinfo.current = offset + startOffset;
const char *state = "";
int bad_journal = 0;
block_list_header *txn = NULL;
while (1) {
int rv;
if (jinfo.current == jinfo.end && into_the_weeds == 0) {
if (jhdr.sequence_num == 0) {
plog("Journal sequence number is 0, is going into the end okay?\n");
}
into_the_weeds = 1;
if (debug)
plog("Attempting to read past stated end of journal\n");
state = "tentative ";
jinfo.end = (jinfo.base + startOffset - jinfo.bSize);
continue;
}
if (debug)
plog("Before getting %stransaction: jinfo.current = %llu\n", state, jinfo.current);
txn = getJournalTransaction(&jinfo, jnlSwap);
if (txn == NULL) {
if (debug)
plog("txn is NULL, jinfo.current = %llu\n", jinfo.current);
if (into_the_weeds) {
if (debug)
plog("\tBut we do not care, since it is past the end of the journal\n");
} else {
bad_journal = 1;
}
break;
}
if (debug) {
plog("After getting %stransaction: jinfo.current = %llu\n", state, jinfo.current);
plog("%stxn = { %u max_blocks, %u num_blocks, %u bytes_used, binfo[0].next = %u }\n", state, jnlSwap->swap32(txn->max_blocks), jnlSwap->swap32(txn->num_blocks), jnlSwap->swap32(txn->bytes_used), jnlSwap->swap32(txn->binfo[0].next));
}
if (into_the_weeds) {
if (last_sequence_number != 0 &&
txn->binfo[0].next != 0 &&
jnlSwap->swap32(txn->binfo[0].next) != last_sequence_number &&
jnlSwap->swap32(txn->binfo[0].next) != (last_sequence_number + 1)) {
if (debug)
plog("\tTentative txn sequence %u is not expected %u, stopping journal replay\n", jnlSwap->swap32(txn->binfo[0].next), last_sequence_number + 1);
break;
}
}
rv = replayTransaction(txn,
jnlSwap->swap32(jhdr.blhdr_size),
jnlSwap->swap32(jhdr.jhdr_size),
jnlSwap,
do_write_b);
if (rv < 0) {
if (debug)
plog("\tTransaction replay failed, returned %d\n", rv);
if (into_the_weeds) {
if (debug)
plog("\t\tAnd we don't care\n");
} else {
bad_journal = 1;
}
break;
}
last_sequence_number = jnlSwap->swap32(txn->binfo[0].next);
free(txn);
txn = NULL;
}
if (txn)
free(txn);
if (bad_journal) {
if (debug)
plog("Journal was bad, stopped replaying\n");
return -1;
}
return 0;
}