Upgraded ntfsrecover to support log files 2.0
When the fast restart mode of Windows 8 (or later) is activated, the log file format is different (version 2.0 instead of 1.1), having 32 temporaty blocks instead of 2. This patch upgrades ntfsrecover to take the new format into account.pull/2/head
parent
ba810877ca
commit
1797ab5ecd
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Process log data from an NTFS partition
|
||||
*
|
||||
* Copyright (c) 2012-2016 Jean-Pierre Andre
|
||||
* Copyright (c) 2012-2017 Jean-Pierre Andre
|
||||
*
|
||||
* This program examines the Windows log file of an ntfs partition
|
||||
* and plays the committed transactions in order to restore the
|
||||
|
@ -43,6 +43,7 @@
|
|||
*/
|
||||
|
||||
#define BASEBLKS 4 /* number of special blocks (always shown) */
|
||||
#define BASEBLKS2 34 /* number of special blocks when version >= 2.0 */
|
||||
#define RSTBLKS 2 /* number of restart blocks */
|
||||
#define BUFFERCNT 64 /* number of block buffers - a power of 2 */
|
||||
#define NTFSBLKLTH 512 /* usa block size */
|
||||
|
@ -122,6 +123,7 @@ u32 clustersz = 0;
|
|||
int clusterbits;
|
||||
u32 blocksz;
|
||||
int blockbits;
|
||||
int log_major;
|
||||
u16 bytespersect;
|
||||
u64 mftlcn;
|
||||
u32 mftrecsz;
|
||||
|
@ -136,6 +138,7 @@ u64 committed_lsn;
|
|||
u64 synced_lsn;
|
||||
u64 latest_lsn;
|
||||
u64 restart_lsn;
|
||||
u64 offset_mask; /* block number in an lsn */
|
||||
unsigned long firstblk; /* first block to dump (option -r) */
|
||||
unsigned long lastblk; /* last block to dump (option -r) */
|
||||
u64 firstlcn; /* first block to dump (option -c) */
|
||||
|
@ -164,6 +167,7 @@ unsigned int playedactions; // change the name
|
|||
unsigned int redocount;
|
||||
unsigned int undocount;
|
||||
struct BUFFER *buffer_table[BASEBLKS + BUFFERCNT];
|
||||
unsigned int redirect[BASEBLKS2];
|
||||
|
||||
static const le16 SDS[4] = {
|
||||
const_cpu_to_le16('$'), const_cpu_to_le16('S'),
|
||||
|
@ -319,18 +323,22 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num)
|
|||
{
|
||||
struct BUFFER *buffer;
|
||||
BOOL got;
|
||||
int k;
|
||||
unsigned int rnum;
|
||||
|
||||
/*
|
||||
* The first four blocks are stored apart, to make
|
||||
* sure pages 2 and 3 and the page which is logically
|
||||
* before them can be accessed at the same time.
|
||||
* (Only two blocks are stored apart if version >= 2.0)
|
||||
* Also, block 0 is smaller because it has to be read
|
||||
* before the block size is known.
|
||||
* Note : the last block is supposed to have an odd
|
||||
* number, and cannot be overwritten by block 4 which
|
||||
* follows logically.
|
||||
* number, and cannot be overwritten by block 4 (or 34
|
||||
* if version >= 2.0) which follows logically.
|
||||
*/
|
||||
if (num < BASEBLKS)
|
||||
if ((num < RSTBLKS)
|
||||
|| ((log_major < 2) && (num < BASEBLKS)))
|
||||
buffer = buffer_table[num + BUFFERCNT];
|
||||
else
|
||||
buffer = buffer_table[num & (BUFFERCNT - 1)];
|
||||
|
@ -342,20 +350,27 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num)
|
|||
buffer = (struct BUFFER*)
|
||||
malloc(sizeof(struct BUFFER) + blocksz);
|
||||
buffer->size = blocksz;
|
||||
buffer->num = num + 1; /* forced to being read */
|
||||
buffer->rnum = num + 1; /* forced to being read */
|
||||
buffer->safe = FALSE;
|
||||
if (num < BASEBLKS)
|
||||
buffer_table[num + BUFFERCNT] = buffer;
|
||||
else
|
||||
buffer_table[num & (BUFFERCNT - 1)] = buffer;
|
||||
}
|
||||
if (buffer && (buffer->num != num)) {
|
||||
rnum = num;
|
||||
if (log_major >= 2) {
|
||||
for (k=RSTBLKS; k<BASEBLKS2; k++)
|
||||
if (redirect[k] == num)
|
||||
rnum = k;
|
||||
}
|
||||
if (buffer && (buffer->rnum != rnum)) {
|
||||
buffer->num = num;
|
||||
buffer->rnum = rnum;
|
||||
if (ctx->vol)
|
||||
got = (ntfs_attr_pread(log_na,(u64)num << blockbits,
|
||||
got = (ntfs_attr_pread(log_na,(u64)rnum << blockbits,
|
||||
blocksz, buffer->block.data) == blocksz);
|
||||
else
|
||||
got = !fseek(ctx->file, loclogblk(ctx, num), 0)
|
||||
got = !fseek(ctx->file, loclogblk(ctx, rnum), 0)
|
||||
&& (fread(buffer->block.data, blocksz,
|
||||
1, ctx->file) == 1);
|
||||
if (got) {
|
||||
|
@ -365,7 +380,7 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num)
|
|||
buffer->safe = !replaceusa(buffer, blocksz);
|
||||
} else {
|
||||
buffer->safe = FALSE;
|
||||
fprintf(stderr,"** Could not read block %d\n", num);
|
||||
fprintf(stderr,"** Could not read block %d\n", rnum);
|
||||
}
|
||||
}
|
||||
return (buffer && buffer->safe ? buffer : (const struct BUFFER*)NULL);
|
||||
|
@ -1096,22 +1111,30 @@ static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf)
|
|||
skipped = 0;
|
||||
do {
|
||||
prevmiddle = FALSE;
|
||||
if (prevblk > BASEBLKS)
|
||||
if (prevblk > (log_major < 2 ? BASEBLKS : BASEBLKS2))
|
||||
prevblk--;
|
||||
else
|
||||
if (prevblk == BASEBLKS)
|
||||
if (prevblk == (log_major < 2 ? BASEBLKS : BASEBLKS2))
|
||||
prevblk = (logfilesz >> blockbits) - 1;
|
||||
else {
|
||||
rph = &buf->block.record;
|
||||
prevblk = (sle64_to_cpu(rph->copy.file_offset)
|
||||
if (log_major < 2)
|
||||
prevblk = (sle64_to_cpu(
|
||||
rph->copy.file_offset)
|
||||
>> blockbits) - 1;
|
||||
else
|
||||
prevblk = (sle64_to_cpu(
|
||||
rph->copy.last_lsn)
|
||||
& offset_mask)
|
||||
>> (blockbits - 3);
|
||||
/*
|
||||
* If an initial block leads to block 4, it
|
||||
* can mean the last block or no previous
|
||||
* block at all. Using the last block is safer,
|
||||
* its lsn will indicate whether it is stale.
|
||||
*/
|
||||
if (prevblk < BASEBLKS)
|
||||
if (prevblk
|
||||
< (log_major < 2 ? BASEBLKS : BASEBLKS2))
|
||||
prevblk = (logfilesz >> blockbits) - 1;
|
||||
}
|
||||
/* No previous block if the log only consists of block 2 or 3 */
|
||||
|
@ -2706,7 +2729,7 @@ static void showrest(const RESTART_PAGE_HEADER *rest)
|
|||
(long)le32_to_cpu(rest->system_page_size));
|
||||
printf("log_page_size %08lx\n",
|
||||
(long)le32_to_cpu(rest->log_page_size));
|
||||
printf("restart_area_offset %04x\n",
|
||||
printf("restart_area_offset %04x\n",
|
||||
(int)le16_to_cpu(rest->restart_area_offset));
|
||||
printf("minor_vers %d\n",
|
||||
(int)sle16_to_cpu(rest->minor_ver));
|
||||
|
@ -2876,6 +2899,8 @@ static BOOL dorest(CONTEXT *ctx, unsigned long blk,
|
|||
}
|
||||
}
|
||||
restart_lsn = synced_lsn;
|
||||
offset_mask = ((u64)1 << (64 - le32_to_cpu(restart.seq_number_bits)))
|
||||
- (1 << (blockbits - 3));
|
||||
return (dirty);
|
||||
}
|
||||
|
||||
|
@ -2895,9 +2920,13 @@ static const struct BUFFER *read_restart(CONTEXT *ctx)
|
|||
{
|
||||
const struct BUFFER *buf;
|
||||
BOOL bad;
|
||||
int blk;
|
||||
int major, minor;
|
||||
|
||||
bad = FALSE;
|
||||
for (blk=0; blk<BASEBLKS2; blk++)
|
||||
redirect[blk] = 0;
|
||||
log_major = 0; /* needed for reading into a buffer */
|
||||
if (ctx->vol) {
|
||||
RESTART_PAGE_HEADER *rph;
|
||||
|
||||
|
@ -2961,6 +2990,7 @@ static const struct BUFFER *read_restart(CONTEXT *ctx)
|
|||
major, minor);
|
||||
bad = TRUE;
|
||||
}
|
||||
log_major = major;
|
||||
if (bad) {
|
||||
buf = (const struct BUFFER*)NULL;
|
||||
}
|
||||
|
@ -3343,7 +3373,8 @@ static TRISTATE backoverlap(CONTEXT *ctx, int blk,
|
|||
mblk = blk + 1;
|
||||
while (total < size) {
|
||||
if (mblk >= (logfilesz >> blockbits))
|
||||
mblk = BASEBLKS;
|
||||
mblk = (log_major < 2 ? BASEBLKS
|
||||
: BASEBLKS2);
|
||||
more = size - total;
|
||||
if (more > nextspace)
|
||||
more = nextspace;
|
||||
|
@ -3427,9 +3458,15 @@ static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped,
|
|||
if (optv) {
|
||||
if (optv >= 2)
|
||||
hexdump(data,blocksz);
|
||||
printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n",
|
||||
(long)blk,(long)blk,
|
||||
(long long)loclogblk(ctx, blk));
|
||||
if (buf->rnum != blk)
|
||||
printf("* RCRD for block %ld 0x%lx"
|
||||
" in block %ld (addr 0x%llx)\n",
|
||||
(long)blk,(long)blk,(long)buf->rnum,
|
||||
(long long)loclogblk(ctx, blk));
|
||||
else
|
||||
printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n",
|
||||
(long)blk,(long)blk,
|
||||
(long long)loclogblk(ctx, blk));
|
||||
} else {
|
||||
if (optt)
|
||||
printf("block %ld\n",(long)blk);
|
||||
|
@ -3551,9 +3588,15 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
|
|||
u32 stopblk;
|
||||
TRISTATE state;
|
||||
|
||||
if (optv)
|
||||
printf("\n* block %d at 0x%llx\n",(int)blk,
|
||||
if (optv) {
|
||||
if ((log_major >= 2) && (buf->rnum != blk))
|
||||
printf("\n* block %d for block %d at 0x%llx\n",
|
||||
(int)buf->rnum,(int)blk,
|
||||
(long long)loclogblk(ctx, buf->rnum));
|
||||
else
|
||||
printf("\n* block %d at 0x%llx\n",(int)blk,
|
||||
(long long)loclogblk(ctx, blk));
|
||||
}
|
||||
ctx->firstaction = (struct ACTION_RECORD*)NULL;
|
||||
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
||||
nextbuf = (const struct BUFFER*)NULL;
|
||||
|
@ -3576,7 +3619,9 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
|
|||
skipped = blk - prevblk - 1;
|
||||
else
|
||||
skipped = blk - prevblk - 1
|
||||
+ (logfilesz >> blockbits) - BASEBLKS;
|
||||
+ (logfilesz >> blockbits)
|
||||
- (log_major < 2 ? BASEBLKS
|
||||
: BASEBLKS2);
|
||||
magic = prevbuf->block.record.magic;
|
||||
switch (magic) {
|
||||
case magic_RCRD :
|
||||
|
@ -3599,9 +3644,18 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
|
|||
(long long)loclogblk(ctx, blk),
|
||||
(long)prevblk);
|
||||
else
|
||||
printf("\n* block %ld at 0x%llx\n",
|
||||
(long)blk,
|
||||
(long long)loclogblk(ctx, blk));
|
||||
if ((log_major >= 2)
|
||||
&& (buf->rnum != blk))
|
||||
printf("\n* block %ld for block %ld at 0x%llx\n",
|
||||
(long)buf->rnum,
|
||||
(long)blk,
|
||||
(long long)loclogblk(
|
||||
ctx,buf->rnum));
|
||||
else
|
||||
printf("\n* block %ld at 0x%llx\n",
|
||||
(long)blk,
|
||||
(long long)loclogblk(
|
||||
ctx, blk));
|
||||
}
|
||||
state = backward_rcrd(ctx, blk, skipped,
|
||||
buf, prevbuf, nextbuf);
|
||||
|
@ -3632,6 +3686,98 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
|
|||
return (state == T_ERR ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the sequencing of blocks (when version >= 2.0)
|
||||
*
|
||||
* Blocks 2..17 and 18..33 are temporary blocks being filled until
|
||||
* they are copied to their target locations, so there are three
|
||||
* possible location for recent blocks.
|
||||
*
|
||||
* Returns the latest target block number
|
||||
*/
|
||||
|
||||
static int block_sequence(CONTEXT *ctx)
|
||||
{
|
||||
const struct BUFFER *buf;
|
||||
int blk;
|
||||
int k;
|
||||
int target_blk;
|
||||
int latest_blk;
|
||||
s64 final_lsn;
|
||||
s64 last_lsn;
|
||||
s64 last_lsn12;
|
||||
s64 last_lsn1, last_lsn2;
|
||||
|
||||
final_lsn = 0;
|
||||
for (blk=RSTBLKS; 2*blk<(RSTBLKS+BASEBLKS2); blk++) {
|
||||
/* First temporary block */
|
||||
last_lsn1 = 0;
|
||||
buf = read_buffer(ctx, blk);
|
||||
if (buf && (buf->block.record.magic == magic_RCRD)) {
|
||||
last_lsn1 = le64_to_cpu(
|
||||
buf->block.record.copy.last_lsn);
|
||||
if (!final_lsn
|
||||
|| ((s64)(last_lsn1 - final_lsn) > 0))
|
||||
final_lsn = last_lsn1;
|
||||
}
|
||||
/* Second temporary block */
|
||||
buf = read_buffer(ctx, blk + (BASEBLKS2 - RSTBLKS)/2);
|
||||
last_lsn2 = 0;
|
||||
if (buf && (buf->block.record.magic == magic_RCRD)) {
|
||||
last_lsn2 = le64_to_cpu(
|
||||
buf->block.record.copy.last_lsn);
|
||||
if (!final_lsn
|
||||
|| ((s64)(last_lsn2 - final_lsn) > 0))
|
||||
final_lsn = last_lsn2;
|
||||
}
|
||||
/* the latest last_lsn defines the target block */
|
||||
last_lsn12 = 0;
|
||||
latest_blk = 0;
|
||||
if (last_lsn1 || last_lsn2) {
|
||||
if (!last_lsn2
|
||||
|| ((s64)(last_lsn1 - last_lsn2) > 0)) {
|
||||
last_lsn12 = last_lsn1;
|
||||
latest_blk = blk;
|
||||
}
|
||||
if (!last_lsn1
|
||||
|| ((s64)(last_lsn1 - last_lsn2) <= 0)) {
|
||||
last_lsn12 = last_lsn2;
|
||||
latest_blk = blk + (BASEBLKS2 - RSTBLKS)/2;
|
||||
}
|
||||
}
|
||||
last_lsn = 0;
|
||||
target_blk = 0;
|
||||
if (last_lsn12) {
|
||||
target_blk = (last_lsn12 & offset_mask)
|
||||
>> (blockbits - 3);
|
||||
buf = read_buffer(ctx, target_blk);
|
||||
if (buf && (buf->block.record.magic == magic_RCRD)) {
|
||||
last_lsn = le64_to_cpu(
|
||||
buf->block.record.copy.last_lsn);
|
||||
if (!final_lsn
|
||||
|| ((s64)(last_lsn - final_lsn) > 0))
|
||||
final_lsn = last_lsn;
|
||||
}
|
||||
}
|
||||
/* redirect to the latest block */
|
||||
if (latest_blk
|
||||
&& (!last_lsn || ((s64)(last_lsn - last_lsn12) < 0)))
|
||||
redirect[latest_blk] = target_blk;
|
||||
}
|
||||
if (optv) {
|
||||
printf("\n Blocks redirected :\n");
|
||||
for (k=RSTBLKS; k<BASEBLKS2; k++)
|
||||
if (redirect[k])
|
||||
printf("* block %d to block %d\n",
|
||||
(int)redirect[k],(int)k);
|
||||
}
|
||||
latest_lsn = final_lsn;
|
||||
blk = (final_lsn & offset_mask) >> (blockbits - 3);
|
||||
if (optv > 1)
|
||||
printf("final lsn %llx in blk %d\n",(long long)final_lsn,blk);
|
||||
return (blk);
|
||||
}
|
||||
|
||||
static int walk(CONTEXT *ctx)
|
||||
{
|
||||
const struct BUFFER *buf;
|
||||
|
@ -3644,6 +3790,7 @@ static int walk(CONTEXT *ctx)
|
|||
u32 blk;
|
||||
u32 nextblk;
|
||||
u32 prevblk;
|
||||
u32 finalblk;
|
||||
int err;
|
||||
u16 blkheadsz;
|
||||
u16 pos;
|
||||
|
@ -3657,6 +3804,7 @@ static int walk(CONTEXT *ctx)
|
|||
}
|
||||
done = FALSE;
|
||||
dirty = TRUE;
|
||||
finalblk = 0;
|
||||
err = 0;
|
||||
blk = 0;
|
||||
pos = 0;
|
||||
|
@ -3675,7 +3823,8 @@ static int walk(CONTEXT *ctx)
|
|||
while (!done) {
|
||||
/* next block is needed to process the current one */
|
||||
if ((nextblk >= (logfilesz >> blockbits)) && (optr || optf))
|
||||
nextbuf = read_buffer(ctx, BASEBLKS);
|
||||
nextbuf = read_buffer(ctx,
|
||||
(log_major < 2 ? BASEBLKS : BASEBLKS2));
|
||||
else
|
||||
nextbuf = read_buffer(ctx,nextblk);
|
||||
if (nextbuf) {
|
||||
|
@ -3741,17 +3890,30 @@ static int walk(CONTEXT *ctx)
|
|||
}
|
||||
blk = nextblk;
|
||||
nextblk++;
|
||||
|
||||
if (!optr && (log_major >= 2) && (nextblk == RSTBLKS)) {
|
||||
finalblk = block_sequence(ctx);
|
||||
if (!finalblk) {
|
||||
done = TRUE;
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optr) { /* Only selected range */
|
||||
if ((nextblk == BASEBLKS) && (nextblk < firstblk))
|
||||
u32 endblk;
|
||||
|
||||
endblk = (log_major < 2 ? BASEBLKS : RSTBLKS);
|
||||
if ((nextblk == endblk) && (nextblk < firstblk))
|
||||
nextblk = firstblk;
|
||||
if ((blk >= BASEBLKS) && (blk > lastblk))
|
||||
if ((blk >= endblk) && (blk > lastblk))
|
||||
done = TRUE;
|
||||
} else
|
||||
if (optf) { /* Full log, forward */
|
||||
if (blk*blocksz >= logfilesz)
|
||||
done = TRUE;
|
||||
} else
|
||||
if (optb || optp || optu || opts) {
|
||||
if (optb || optp || optu || opts
|
||||
|| (log_major >= 2)) {
|
||||
/* Restart blocks only (2 blocks) */
|
||||
if (blk >= RSTBLKS)
|
||||
done = TRUE;
|
||||
|
@ -3782,16 +3944,18 @@ static int walk(CONTEXT *ctx)
|
|||
}
|
||||
if (optv && opts && !dirty)
|
||||
printf("* Volume is clean, nothing to do\n");
|
||||
if (optb || optp || optu
|
||||
|| (opts && dirty)) {
|
||||
if (log_major >= 2)
|
||||
blk = finalblk;
|
||||
if (!err
|
||||
&& (optb || optp || optu || (opts && dirty))) {
|
||||
playedactions = 0;
|
||||
ctx->firstaction = (struct ACTION_RECORD*)NULL;
|
||||
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
||||
buf = nextbuf;
|
||||
nextbuf = read_buffer(ctx, blk+1);
|
||||
startbuf = best_start(buf,nextbuf);
|
||||
if (startbuf) {
|
||||
if (startbuf == nextbuf) {
|
||||
if (log_major < 2) {
|
||||
buf = nextbuf;
|
||||
nextbuf = read_buffer(ctx, blk+1);
|
||||
startbuf = best_start(buf,nextbuf);
|
||||
if (startbuf && (startbuf == nextbuf)) {
|
||||
/* nextbuf is better, show blk */
|
||||
if (optv && buf) {
|
||||
printf("* Ignored block %d at 0x%llx\n",
|
||||
|
@ -3818,6 +3982,11 @@ static int walk(CONTEXT *ctx)
|
|||
&nextbuf->block.record);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf = startbuf = read_buffer(ctx, blk);
|
||||
nextbuf = (const struct BUFFER*)NULL;
|
||||
}
|
||||
if (startbuf) {
|
||||
/* The latest buf may be more recent than restart */
|
||||
rph = &buf->block.record;
|
||||
if ((s64)(sle64_to_cpu(rph->last_end_lsn)
|
||||
|
|
|
@ -74,6 +74,7 @@ enum ACTIONS {
|
|||
|
||||
struct BUFFER {
|
||||
unsigned int num;
|
||||
unsigned int rnum;
|
||||
unsigned int size;
|
||||
unsigned int headsz;
|
||||
BOOL safe;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Redo or undo a list of logged actions
|
||||
*
|
||||
* Copyright (c) 2014-2016 Jean-Pierre Andre
|
||||
* Copyright (c) 2014-2017 Jean-Pierre Andre
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -229,7 +229,7 @@ static int sanity_indx_list(const char *buffer, u32 k, u32 end)
|
|||
|
||||
err = 0;
|
||||
done = FALSE;
|
||||
while ((k <= end) && !done) {
|
||||
while ((k <= end) && !done && !err) {
|
||||
lth = getle16(buffer,k+8);
|
||||
if (optv > 1)
|
||||
/* Usual indexes can be determined from size */
|
||||
|
@ -270,9 +270,20 @@ static int sanity_indx_list(const char *buffer, u32 k, u32 end)
|
|||
(long long)getle64(buffer,k),
|
||||
(int)lth,
|
||||
(int)getle16(buffer,k+12),(int)k);
|
||||
if ((lth < 80) || (lth & 7)) {
|
||||
printf("** Invalid index record"
|
||||
" length %d\n",lth);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth;
|
||||
k += lth;
|
||||
if (lth & 7) {
|
||||
if (optv <= 1) /* Do not repeat the warning */
|
||||
printf("** Invalid index record length %d\n",
|
||||
lth);
|
||||
err = 1;
|
||||
} else
|
||||
k += lth;
|
||||
}
|
||||
if (k != end) {
|
||||
printf("** Bad index record length %ld (computed %ld)\n",
|
||||
|
|
Loading…
Reference in New Issue