mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-23 10:04:00 +08:00
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.
This commit is contained in:
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
Block a user