/*
* recovery.c - NILFS recovery logic
*
* Copyright (C) 2005-2008 Nippon Telegraph and Telephone Corporation.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Written by Ryusuke Konishi <ryusuke@osrg.net>
*/
#include <linux/buffer_head.h>
#include <linux/blkdev.h>
#include <linux/swap.h>
#include <linux/slab.h>
#include <linux/crc32.h>
#include "nilfs.h"
#include "segment.h"
#include "sufile.h"
#include "page.h"
#include "segbuf.h"
/*
* Segment check result
*/
enum {
NILFS_SEG_VALID,
NILFS_SEG_NO_SUPER_ROOT,
NILFS_SEG_FAIL_IO,
NILFS_SEG_FAIL_MAGIC,
NILFS_SEG_FAIL_SEQ,
NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT,
NILFS_SEG_FAIL_CHECKSUM_FULL,
NILFS_SEG_FAIL_CONSISTENCY,
};
/* work structure for recovery */
struct nilfs_recovery_block {
ino_t ino; /* Inode number of the file that this block
belongs to */
sector_t blocknr; /* block number */
__u64 vblocknr; /* virtual block number */
unsigned long blkoff; /* File offset of the data block (per block) */
struct list_head list;
};
static int nilfs_warn_segment_error(int err)
{
switch (err) {
case NILFS_SEG_FAIL_IO:
printk(KERN_WARNING
"NILFS warning: I/O error on loading last segment\n");
return -EIO;
case NILFS_SEG_FAIL_MAGIC:
printk(KERN_WARNING
"NILFS warning: Segment magic number invalid\n");
break;
case NILFS_SEG_FAIL_SEQ:
printk(KERN_WARNING
"NILFS warning: Sequence number mismatch\n");
break;
case NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT:
printk(KERN_WARNING
"NILFS warning: Checksum error in super root\n");
break;
case NILFS_SEG_FAIL_CHECKSUM_FULL:
printk(KERN_WARNING
"NILFS warning: Checksum error in segment payload\n");
break;
case NILFS_SEG_FAIL_CONSISTENCY:
printk(KERN_WARNING
"NILFS warning: Inconsistent segment\n");
break;
case NILFS_SEG_NO_SUPER_ROOT:
printk(KERN_WARNING
"NILFS warning: No super root in the last segment\n");
break;
}
return -EINVAL;
}
/**
* nilfs_compute_checksum - compute checksum of blocks continuously
* @nilfs: nilfs object
* @bhs: buffer head of start block
* @sum: place to store result
* @offset: offset bytes in the first block
* @check_bytes: number of bytes to be checked
* @start: DBN of start block
* @nblock: number of blocks to be checked
*/
static int nilfs_compute_checksum(struct the_nilfs *nilfs,
struct buffer_head *bhs, u32 *sum,
unsigned long offset, u64 check_bytes,
sector_t start, unsigned long nblock)
{
unsigned int blocksize = nilfs->ns_blocksize;
unsigned long size;
u32 crc;
BUG_ON(offset >= blocksize);
check_bytes -= offset;
size = min_t(u64, check_bytes, blocksize - offset);
crc = crc32_le(nilfs->ns_crc_seed,
(unsigned char *)bhs->b_data + offset, size);
if (--nblock > 0) {
do {
struct buffer_head *bh;
bh = __bread(nilfs->ns_bdev, ++start, blocksize);
if (!bh)
return -EIO;
check_bytes -= size;
size = min_t(u64, check_bytes, blocksize);
crc = crc32_le(crc, bh->b_data, size);
brelse(bh);
} while (--nblock > 0);
}
*sum = crc;
return 0;
}
/**
* nilfs_read_super_root_block - read super root block
* @nilfs: nilfs object
* @sr_block: disk block number of the super root block
* @pbh: address of a buffer_head pointer to return super root buffer
* @check: CRC check flag
*/
int nilfs_read_super_root_block(struct the_nilfs *nilfs, sector_t sr_block,
struct buffer_head **pbh, int check)
{
struct buffer_head *bh_sr;
struct nilfs_super_root *sr;
u32 crc;
int ret;
*pbh = NULL;
bh_sr = __bread(nilfs->ns_bdev, sr_block, nilfs->ns_blocksize);
if (unlikely(!bh_sr)) {
ret = NILFS_SEG_FAIL_IO;
goto failed;
}
sr = (struct nilfs_super_root *)bh_sr->b_data;
if (check) {
unsigned bytes = le16_to_cpu(sr->sr_bytes);
if (bytes == 0 || bytes > nilfs->ns_blocksize) {
ret = NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT;
goto failed_bh;
}
if (nilfs_compute_checksum(
nilfs, bh_sr, &crc, sizeof(sr->sr_sum), bytes,
sr_block, 1)) {
ret = NILFS_SEG_FAIL_IO;
goto failed_bh;
}
if (crc != le32_to_cpu(sr->sr_sum)) {
ret = NILFS_SEG_FAIL_CHECKSUM_SUPER_ROOT;
goto failed_bh;
}
}
*pbh = bh_sr;
return 0;