2012-11-29 05:28:09 +01:00
|
|
|
/*
|
2012-11-02 09:13:32 +01:00
|
|
|
* fs/f2fs/recovery.c
|
|
|
|
*
|
|
|
|
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
|
|
|
* http://www.samsung.com/
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/f2fs_fs.h>
|
|
|
|
#include "f2fs.h"
|
|
|
|
#include "node.h"
|
|
|
|
#include "segment.h"
|
|
|
|
|
2014-09-16 01:46:08 +02:00
|
|
|
/*
|
|
|
|
* Roll forward recovery scenarios.
|
|
|
|
*
|
|
|
|
* [Term] F: fsync_mark, D: dentry_mark
|
|
|
|
*
|
|
|
|
* 1. inode(x) | CP | inode(x) | dnode(F)
|
|
|
|
* -> Update the latest inode(x).
|
|
|
|
*
|
|
|
|
* 2. inode(x) | CP | inode(F) | dnode(F)
|
|
|
|
* -> No problem.
|
|
|
|
*
|
|
|
|
* 3. inode(x) | CP | dnode(F) | inode(x)
|
|
|
|
* -> Recover to the latest dnode(F), and drop the last inode(x)
|
|
|
|
*
|
|
|
|
* 4. inode(x) | CP | dnode(F) | inode(F)
|
|
|
|
* -> No problem.
|
|
|
|
*
|
|
|
|
* 5. CP | inode(x) | dnode(F)
|
|
|
|
* -> The inode(DF) was missing. Should drop this dnode(F).
|
|
|
|
*
|
|
|
|
* 6. CP | inode(DF) | dnode(F)
|
|
|
|
* -> No problem.
|
|
|
|
*
|
|
|
|
* 7. CP | dnode(F) | inode(DF)
|
|
|
|
* -> If f2fs_iget fails, then goto next to find inode(DF).
|
|
|
|
*
|
|
|
|
* 8. CP | dnode(F) | inode(x)
|
|
|
|
* -> If f2fs_iget fails, then goto next to find inode(DF).
|
|
|
|
* But it will fail due to no inode(DF).
|
|
|
|
*/
|
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
static struct kmem_cache *fsync_entry_slab;
|
|
|
|
|
|
|
|
bool space_for_roll_forward(struct f2fs_sb_info *sbi)
|
|
|
|
{
|
|
|
|
if (sbi->last_valid_block_count + sbi->alloc_valid_block_count
|
|
|
|
> sbi->user_block_count)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fsync_inode_entry *get_fsync_inode(struct list_head *head,
|
|
|
|
nid_t ino)
|
|
|
|
{
|
|
|
|
struct fsync_inode_entry *entry;
|
|
|
|
|
2014-03-29 04:33:17 +01:00
|
|
|
list_for_each_entry(entry, head, list)
|
2012-11-02 09:13:32 +01:00
|
|
|
if (entry->inode->i_ino == ino)
|
|
|
|
return entry;
|
2014-03-29 04:33:17 +01:00
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-09-11 23:29:06 +02:00
|
|
|
static int recover_dentry(struct inode *inode, struct page *ipage)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
2013-12-26 08:30:41 +01:00
|
|
|
struct f2fs_inode *raw_inode = F2FS_INODE(ipage);
|
2013-05-15 09:40:02 +02:00
|
|
|
nid_t pino = le32_to_cpu(raw_inode->i_pino);
|
2013-05-28 02:19:22 +02:00
|
|
|
struct f2fs_dir_entry *de;
|
2013-01-25 22:15:43 +01:00
|
|
|
struct qstr name;
|
2012-11-02 09:13:32 +01:00
|
|
|
struct page *page;
|
2013-05-28 02:19:22 +02:00
|
|
|
struct inode *dir, *einode;
|
2012-11-02 09:13:32 +01:00
|
|
|
int err = 0;
|
|
|
|
|
2014-04-15 04:19:28 +02:00
|
|
|
dir = f2fs_iget(inode->i_sb, pino);
|
|
|
|
if (IS_ERR(dir)) {
|
|
|
|
err = PTR_ERR(dir);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-04-30 02:02:18 +02:00
|
|
|
if (file_enc_name(inode)) {
|
|
|
|
iput(dir);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-25 22:15:43 +01:00
|
|
|
name.len = le32_to_cpu(raw_inode->i_namelen);
|
|
|
|
name.name = raw_inode->i_name;
|
2013-12-23 04:12:21 +01:00
|
|
|
|
|
|
|
if (unlikely(name.len > F2FS_NAME_LEN)) {
|
|
|
|
WARN_ON(1);
|
|
|
|
err = -ENAMETOOLONG;
|
2014-06-06 20:05:03 +02:00
|
|
|
goto out_err;
|
2013-12-23 04:12:21 +01:00
|
|
|
}
|
2013-05-28 02:19:22 +02:00
|
|
|
retry:
|
|
|
|
de = f2fs_find_entry(dir, &name, &page);
|
2015-04-01 03:03:29 +02:00
|
|
|
if (de && inode->i_ino == le32_to_cpu(de->ino))
|
2013-09-24 16:40:57 +02:00
|
|
|
goto out_unmap_put;
|
2015-04-01 03:03:29 +02:00
|
|
|
|
2013-05-28 02:19:22 +02:00
|
|
|
if (de) {
|
|
|
|
einode = f2fs_iget(inode->i_sb, le32_to_cpu(de->ino));
|
|
|
|
if (IS_ERR(einode)) {
|
|
|
|
WARN_ON(1);
|
2014-04-28 11:58:34 +02:00
|
|
|
err = PTR_ERR(einode);
|
|
|
|
if (err == -ENOENT)
|
2013-05-28 02:19:22 +02:00
|
|
|
err = -EEXIST;
|
2013-09-24 16:40:57 +02:00
|
|
|
goto out_unmap_put;
|
|
|
|
}
|
2014-09-03 00:31:18 +02:00
|
|
|
err = acquire_orphan_inode(F2FS_I_SB(inode));
|
2013-09-24 16:40:57 +02:00
|
|
|
if (err) {
|
|
|
|
iput(einode);
|
|
|
|
goto out_unmap_put;
|
2013-05-28 02:19:22 +02:00
|
|
|
}
|
2014-09-24 12:17:04 +02:00
|
|
|
f2fs_delete_entry(de, page, dir, einode);
|
2013-05-28 02:19:22 +02:00
|
|
|
iput(einode);
|
|
|
|
goto retry;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2015-03-31 00:07:16 +02:00
|
|
|
err = __f2fs_add_link(dir, &name, inode, inode->i_ino, inode->i_mode);
|
2014-06-06 20:05:03 +02:00
|
|
|
if (err)
|
|
|
|
goto out_err;
|
|
|
|
|
|
|
|
if (is_inode_flag_set(F2FS_I(dir), FI_DELAY_IPUT)) {
|
|
|
|
iput(dir);
|
|
|
|
} else {
|
|
|
|
add_dirty_dir_inode(dir);
|
|
|
|
set_inode_flag(F2FS_I(dir), FI_DELAY_IPUT);
|
|
|
|
}
|
|
|
|
|
2013-09-24 16:40:57 +02:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
out_unmap_put:
|
2014-11-22 01:36:28 +01:00
|
|
|
f2fs_dentry_kunmap(dir, page);
|
2013-09-24 16:40:57 +02:00
|
|
|
f2fs_put_page(page, 0);
|
2014-06-06 20:05:03 +02:00
|
|
|
out_err:
|
|
|
|
iput(dir);
|
2012-11-02 09:13:32 +01:00
|
|
|
out:
|
2014-01-17 21:44:39 +01:00
|
|
|
f2fs_msg(inode->i_sb, KERN_NOTICE,
|
|
|
|
"%s: ino = %x, name = %s, dir = %lx, err = %d",
|
|
|
|
__func__, ino_of_node(ipage), raw_inode->i_name,
|
2013-05-23 12:02:13 +02:00
|
|
|
IS_ERR(dir) ? 0 : dir->i_ino, err);
|
2012-11-02 09:13:32 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2014-09-11 23:29:06 +02:00
|
|
|
static void recover_inode(struct inode *inode, struct page *page)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
2014-09-16 01:46:08 +02:00
|
|
|
struct f2fs_inode *raw = F2FS_INODE(page);
|
2015-04-30 02:02:18 +02:00
|
|
|
char *name;
|
2014-09-16 01:46:08 +02:00
|
|
|
|
|
|
|
inode->i_mode = le16_to_cpu(raw->i_mode);
|
|
|
|
i_size_write(inode, le64_to_cpu(raw->i_size));
|
|
|
|
inode->i_atime.tv_sec = le64_to_cpu(raw->i_mtime);
|
|
|
|
inode->i_ctime.tv_sec = le64_to_cpu(raw->i_ctime);
|
|
|
|
inode->i_mtime.tv_sec = le64_to_cpu(raw->i_mtime);
|
|
|
|
inode->i_atime.tv_nsec = le32_to_cpu(raw->i_mtime_nsec);
|
|
|
|
inode->i_ctime.tv_nsec = le32_to_cpu(raw->i_ctime_nsec);
|
|
|
|
inode->i_mtime.tv_nsec = le32_to_cpu(raw->i_mtime_nsec);
|
2013-05-16 08:04:49 +02:00
|
|
|
|
2015-04-30 02:02:18 +02:00
|
|
|
if (file_enc_name(inode))
|
|
|
|
name = "<encrypted>";
|
|
|
|
else
|
|
|
|
name = F2FS_INODE(page)->i_name;
|
|
|
|
|
2013-05-16 08:04:49 +02:00
|
|
|
f2fs_msg(inode->i_sb, KERN_NOTICE, "recover_inode: ino = %x, name = %s",
|
2015-04-30 02:02:18 +02:00
|
|
|
ino_of_node(page), name);
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
2015-12-03 23:14:40 +01:00
|
|
|
static bool is_same_inode(struct inode *inode, struct page *ipage)
|
|
|
|
{
|
|
|
|
struct f2fs_inode *ri = F2FS_INODE(ipage);
|
|
|
|
struct timespec disk;
|
|
|
|
|
|
|
|
if (!IS_INODE(ipage))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
disk.tv_sec = le64_to_cpu(ri->i_ctime);
|
|
|
|
disk.tv_nsec = le32_to_cpu(ri->i_ctime_nsec);
|
|
|
|
if (timespec_compare(&inode->i_ctime, &disk) > 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
disk.tv_sec = le64_to_cpu(ri->i_atime);
|
|
|
|
disk.tv_nsec = le32_to_cpu(ri->i_atime_nsec);
|
|
|
|
if (timespec_compare(&inode->i_atime, &disk) > 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
disk.tv_sec = le64_to_cpu(ri->i_mtime);
|
|
|
|
disk.tv_nsec = le32_to_cpu(ri->i_mtime_nsec);
|
|
|
|
if (timespec_compare(&inode->i_mtime, &disk) > 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
static int find_fsync_dnodes(struct f2fs_sb_info *sbi, struct list_head *head)
|
|
|
|
{
|
2013-08-09 08:03:21 +02:00
|
|
|
unsigned long long cp_ver = cur_cp_version(F2FS_CKPT(sbi));
|
2012-11-02 09:13:32 +01:00
|
|
|
struct curseg_info *curseg;
|
2014-09-11 22:49:55 +02:00
|
|
|
struct page *page = NULL;
|
2012-11-02 09:13:32 +01:00
|
|
|
block_t blkaddr;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
/* get node pages in the current segment */
|
|
|
|
curseg = CURSEG_I(sbi, CURSEG_WARM_NODE);
|
2014-02-27 12:52:21 +01:00
|
|
|
blkaddr = NEXT_FREE_BLKADDR(sbi, curseg);
|
2012-11-02 09:13:32 +01:00
|
|
|
|
2015-10-12 11:05:59 +02:00
|
|
|
ra_meta_pages(sbi, blkaddr, 1, META_POR, true);
|
2014-12-08 08:02:52 +01:00
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
while (1) {
|
|
|
|
struct fsync_inode_entry *entry;
|
|
|
|
|
2015-04-18 12:05:36 +02:00
|
|
|
if (!is_valid_blkaddr(sbi, blkaddr, META_POR))
|
2014-09-11 22:49:55 +02:00
|
|
|
return 0;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
2015-10-12 11:04:21 +02:00
|
|
|
page = get_tmp_page(sbi, blkaddr);
|
2013-03-08 13:29:23 +01:00
|
|
|
|
2013-03-20 11:01:06 +01:00
|
|
|
if (cp_ver != cpver_of_node(page))
|
2013-05-16 08:04:49 +02:00
|
|
|
break;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
if (!is_fsync_dnode(page))
|
|
|
|
goto next;
|
|
|
|
|
|
|
|
entry = get_fsync_inode(head, ino_of_node(page));
|
2015-12-03 23:14:40 +01:00
|
|
|
if (entry) {
|
|
|
|
if (!is_same_inode(entry->inode, page))
|
|
|
|
goto next;
|
|
|
|
} else {
|
2012-11-02 09:13:32 +01:00
|
|
|
if (IS_INODE(page) && is_dent_dnode(page)) {
|
2013-03-20 11:01:06 +01:00
|
|
|
err = recover_inode_page(sbi, page);
|
|
|
|
if (err)
|
2013-05-16 08:04:49 +02:00
|
|
|
break;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* add this fsync inode to the list */
|
2014-09-11 23:29:06 +02:00
|
|
|
entry = kmem_cache_alloc(fsync_entry_slab, GFP_F2FS_ZERO);
|
2012-11-02 09:13:32 +01:00
|
|
|
if (!entry) {
|
|
|
|
err = -ENOMEM;
|
2013-05-16 08:04:49 +02:00
|
|
|
break;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2014-09-16 01:46:08 +02:00
|
|
|
/*
|
|
|
|
* CP | dnode(F) | inode(DF)
|
|
|
|
* For this case, we should not give up now.
|
|
|
|
*/
|
2012-11-02 09:13:32 +01:00
|
|
|
entry->inode = f2fs_iget(sbi->sb, ino_of_node(page));
|
|
|
|
if (IS_ERR(entry->inode)) {
|
|
|
|
err = PTR_ERR(entry->inode);
|
2012-12-22 04:10:12 +01:00
|
|
|
kmem_cache_free(fsync_entry_slab, entry);
|
2015-02-25 03:01:46 +01:00
|
|
|
if (err == -ENOENT) {
|
|
|
|
err = 0;
|
2014-09-16 01:46:08 +02:00
|
|
|
goto next;
|
2015-02-25 03:01:46 +01:00
|
|
|
}
|
2013-05-16 08:04:49 +02:00
|
|
|
break;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2012-12-22 04:10:12 +01:00
|
|
|
list_add_tail(&entry->list, head);
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2013-05-15 03:49:13 +02:00
|
|
|
entry->blkaddr = blkaddr;
|
|
|
|
|
2014-09-11 23:29:06 +02:00
|
|
|
if (IS_INODE(page)) {
|
|
|
|
entry->last_inode = blkaddr;
|
|
|
|
if (is_dent_dnode(page))
|
|
|
|
entry->last_dentry = blkaddr;
|
|
|
|
}
|
2012-11-02 09:13:32 +01:00
|
|
|
next:
|
|
|
|
/* check next segment */
|
|
|
|
blkaddr = next_blkaddr_of_node(page);
|
2014-09-11 22:49:55 +02:00
|
|
|
f2fs_put_page(page, 1);
|
2014-12-08 08:02:52 +01:00
|
|
|
|
|
|
|
ra_meta_pages_cond(sbi, blkaddr);
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2014-09-11 22:49:55 +02:00
|
|
|
f2fs_put_page(page, 1);
|
2012-11-02 09:13:32 +01:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-06-27 03:28:54 +02:00
|
|
|
static void destroy_fsync_dnodes(struct list_head *head)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
2013-01-20 16:02:58 +01:00
|
|
|
struct fsync_inode_entry *entry, *tmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(entry, tmp, head, list) {
|
2012-11-02 09:13:32 +01:00
|
|
|
iput(entry->inode);
|
|
|
|
list_del(&entry->list);
|
|
|
|
kmem_cache_free(fsync_entry_slab, entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-22 01:20:01 +02:00
|
|
|
static int check_index_in_prev_nodes(struct f2fs_sb_info *sbi,
|
2013-05-22 01:02:02 +02:00
|
|
|
block_t blkaddr, struct dnode_of_data *dn)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
|
|
|
struct seg_entry *sentry;
|
|
|
|
unsigned int segno = GET_SEGNO(sbi, blkaddr);
|
2014-02-04 05:01:10 +01:00
|
|
|
unsigned short blkoff = GET_BLKOFF_FROM_SEG0(sbi, blkaddr);
|
2014-01-28 06:54:07 +01:00
|
|
|
struct f2fs_summary_block *sum_node;
|
2012-11-02 09:13:32 +01:00
|
|
|
struct f2fs_summary sum;
|
2014-01-28 06:54:07 +01:00
|
|
|
struct page *sum_page, *node_page;
|
2015-03-27 02:46:38 +01:00
|
|
|
struct dnode_of_data tdn = *dn;
|
2013-05-22 01:02:02 +02:00
|
|
|
nid_t ino, nid;
|
2012-11-02 09:13:32 +01:00
|
|
|
struct inode *inode;
|
2013-08-12 14:08:03 +02:00
|
|
|
unsigned int offset;
|
2012-11-02 09:13:32 +01:00
|
|
|
block_t bidx;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sentry = get_seg_entry(sbi, segno);
|
|
|
|
if (!f2fs_test_bit(blkoff, sentry->cur_valid_map))
|
2013-05-22 01:20:01 +02:00
|
|
|
return 0;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
/* Get the previous summary */
|
|
|
|
for (i = CURSEG_WARM_DATA; i <= CURSEG_COLD_DATA; i++) {
|
|
|
|
struct curseg_info *curseg = CURSEG_I(sbi, i);
|
|
|
|
if (curseg->segno == segno) {
|
|
|
|
sum = curseg->sum_blk->entries[blkoff];
|
2014-01-28 06:54:07 +01:00
|
|
|
goto got_it;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-28 06:54:07 +01:00
|
|
|
sum_page = get_sum_page(sbi, segno);
|
|
|
|
sum_node = (struct f2fs_summary_block *)page_address(sum_page);
|
|
|
|
sum = sum_node->entries[blkoff];
|
|
|
|
f2fs_put_page(sum_page, 1);
|
|
|
|
got_it:
|
2013-05-22 01:02:02 +02:00
|
|
|
/* Use the locked dnode page and inode */
|
|
|
|
nid = le32_to_cpu(sum.nid);
|
|
|
|
if (dn->inode->i_ino == nid) {
|
|
|
|
tdn.nid = nid;
|
2015-03-27 02:46:38 +01:00
|
|
|
if (!dn->inode_page_locked)
|
|
|
|
lock_page(dn->inode_page);
|
2013-05-22 01:02:02 +02:00
|
|
|
tdn.node_page = dn->inode_page;
|
2013-06-24 00:47:23 +02:00
|
|
|
tdn.ofs_in_node = le16_to_cpu(sum.ofs_in_node);
|
2015-03-27 02:46:38 +01:00
|
|
|
goto truncate_out;
|
2013-05-22 01:02:02 +02:00
|
|
|
} else if (dn->nid == nid) {
|
2013-06-24 00:47:23 +02:00
|
|
|
tdn.ofs_in_node = le16_to_cpu(sum.ofs_in_node);
|
2015-03-27 02:46:38 +01:00
|
|
|
goto truncate_out;
|
2013-05-22 01:02:02 +02:00
|
|
|
}
|
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
/* Get the node page */
|
2013-05-22 01:02:02 +02:00
|
|
|
node_page = get_node_page(sbi, nid);
|
2013-05-22 01:20:01 +02:00
|
|
|
if (IS_ERR(node_page))
|
|
|
|
return PTR_ERR(node_page);
|
2013-08-12 14:08:03 +02:00
|
|
|
|
|
|
|
offset = ofs_of_node(node_page);
|
2012-11-02 09:13:32 +01:00
|
|
|
ino = ino_of_node(node_page);
|
|
|
|
f2fs_put_page(node_page, 1);
|
|
|
|
|
f2fs: fix double lock for inode page during roll-foward recovery
If the inode is same and its data index are needed to truncate, we can fall into
double lock for its inode page via get_dnode_of_data.
Error case is like this.
1. write data 1, 2, 3, 4, 5 in inode #4.
2. write data 100, 102, 103, 104, 105 in dnode #6 of inode #4.
3. sync
4. update data 100->106 in dnode #6.
5. fsync inode #4.
6. power-cut
-> Then,
1. go back to #3's checkpoint
2. in do_recover_data, get_dnode_of_data() gets inode #4.
3. detect 100->106 in dnode #6.
4. check_index_in_prev_nodes tries to truncate 100 in dnode #6.
5. to trigger truncate_hole, get_dnode_of_data should grab inode #4.
6. detect *kernel hang*
This patch should resolve that bug.
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2014-09-12 17:35:58 +02:00
|
|
|
if (ino != dn->inode->i_ino) {
|
|
|
|
/* Deallocate previous index in the node page */
|
|
|
|
inode = f2fs_iget(sbi->sb, ino);
|
|
|
|
if (IS_ERR(inode))
|
|
|
|
return PTR_ERR(inode);
|
|
|
|
} else {
|
|
|
|
inode = dn->inode;
|
|
|
|
}
|
2012-12-22 04:09:43 +01:00
|
|
|
|
2013-08-12 14:08:03 +02:00
|
|
|
bidx = start_bidx_of_node(offset, F2FS_I(inode)) +
|
f2fs: fix double lock for inode page during roll-foward recovery
If the inode is same and its data index are needed to truncate, we can fall into
double lock for its inode page via get_dnode_of_data.
Error case is like this.
1. write data 1, 2, 3, 4, 5 in inode #4.
2. write data 100, 102, 103, 104, 105 in dnode #6 of inode #4.
3. sync
4. update data 100->106 in dnode #6.
5. fsync inode #4.
6. power-cut
-> Then,
1. go back to #3's checkpoint
2. in do_recover_data, get_dnode_of_data() gets inode #4.
3. detect 100->106 in dnode #6.
4. check_index_in_prev_nodes tries to truncate 100 in dnode #6.
5. to trigger truncate_hole, get_dnode_of_data should grab inode #4.
6. detect *kernel hang*
This patch should resolve that bug.
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2014-09-12 17:35:58 +02:00
|
|
|
le16_to_cpu(sum.ofs_in_node);
|
2013-08-12 14:08:03 +02:00
|
|
|
|
2015-03-27 02:46:38 +01:00
|
|
|
/*
|
|
|
|
* if inode page is locked, unlock temporarily, but its reference
|
|
|
|
* count keeps alive.
|
|
|
|
*/
|
|
|
|
if (ino == dn->inode->i_ino && dn->inode_page_locked)
|
|
|
|
unlock_page(dn->inode_page);
|
|
|
|
|
|
|
|
set_new_dnode(&tdn, inode, NULL, NULL, 0);
|
|
|
|
if (get_dnode_of_data(&tdn, bidx, LOOKUP_NODE))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (tdn.data_blkaddr == blkaddr)
|
|
|
|
truncate_data_blocks_range(&tdn, 1);
|
|
|
|
|
|
|
|
f2fs_put_dnode(&tdn);
|
|
|
|
out:
|
|
|
|
if (ino != dn->inode->i_ino)
|
f2fs: fix double lock for inode page during roll-foward recovery
If the inode is same and its data index are needed to truncate, we can fall into
double lock for its inode page via get_dnode_of_data.
Error case is like this.
1. write data 1, 2, 3, 4, 5 in inode #4.
2. write data 100, 102, 103, 104, 105 in dnode #6 of inode #4.
3. sync
4. update data 100->106 in dnode #6.
5. fsync inode #4.
6. power-cut
-> Then,
1. go back to #3's checkpoint
2. in do_recover_data, get_dnode_of_data() gets inode #4.
3. detect 100->106 in dnode #6.
4. check_index_in_prev_nodes tries to truncate 100 in dnode #6.
5. to trigger truncate_hole, get_dnode_of_data should grab inode #4.
6. detect *kernel hang*
This patch should resolve that bug.
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2014-09-12 17:35:58 +02:00
|
|
|
iput(inode);
|
2015-03-27 02:46:38 +01:00
|
|
|
else if (dn->inode_page_locked)
|
|
|
|
lock_page(dn->inode_page);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
truncate_out:
|
|
|
|
if (datablock_addr(tdn.node_page, tdn.ofs_in_node) == blkaddr)
|
|
|
|
truncate_data_blocks_range(&tdn, 1);
|
|
|
|
if (dn->inode->i_ino == nid && !dn->inode_page_locked)
|
|
|
|
unlock_page(dn->inode_page);
|
2013-05-22 01:20:01 +02:00
|
|
|
return 0;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
2013-03-20 11:01:06 +01:00
|
|
|
static int do_recover_data(struct f2fs_sb_info *sbi, struct inode *inode,
|
2012-11-02 09:13:32 +01:00
|
|
|
struct page *page, block_t blkaddr)
|
|
|
|
{
|
2013-08-12 14:08:03 +02:00
|
|
|
struct f2fs_inode_info *fi = F2FS_I(inode);
|
2012-11-02 09:13:32 +01:00
|
|
|
unsigned int start, end;
|
|
|
|
struct dnode_of_data dn;
|
|
|
|
struct node_info ni;
|
2013-05-16 08:04:49 +02:00
|
|
|
int err = 0, recovered = 0;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
2014-08-08 08:49:17 +02:00
|
|
|
/* step 1: recover xattr */
|
|
|
|
if (IS_INODE(page)) {
|
|
|
|
recover_inline_xattr(inode, page);
|
|
|
|
} else if (f2fs_has_xattr_block(ofs_of_node(page))) {
|
2015-01-22 23:48:28 +01:00
|
|
|
/*
|
|
|
|
* Deprecated; xattr blocks should be found from cold log.
|
|
|
|
* But, we should remain this for backward compatibility.
|
|
|
|
*/
|
2014-08-08 08:49:17 +02:00
|
|
|
recover_xattr_data(inode, page, blkaddr);
|
2013-12-26 04:49:48 +01:00
|
|
|
goto out;
|
2014-08-08 08:49:17 +02:00
|
|
|
}
|
2013-12-26 04:49:48 +01:00
|
|
|
|
2014-08-08 08:49:17 +02:00
|
|
|
/* step 2: recover inline data */
|
|
|
|
if (recover_inline_data(inode, page))
|
2014-01-28 04:25:06 +01:00
|
|
|
goto out;
|
|
|
|
|
2014-08-08 08:49:17 +02:00
|
|
|
/* step 3: recover data indices */
|
2013-08-12 14:08:03 +02:00
|
|
|
start = start_bidx_of_node(ofs_of_node(page), fi);
|
2014-04-26 13:59:52 +02:00
|
|
|
end = start + ADDRS_PER_PAGE(page, fi);
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
set_new_dnode(&dn, inode, NULL, NULL, 0);
|
f2fs: introduce a new global lock scheme
In the previous version, f2fs uses global locks according to the usage types,
such as directory operations, block allocation, block write, and so on.
Reference the following lock types in f2fs.h.
enum lock_type {
RENAME, /* for renaming operations */
DENTRY_OPS, /* for directory operations */
DATA_WRITE, /* for data write */
DATA_NEW, /* for data allocation */
DATA_TRUNC, /* for data truncate */
NODE_NEW, /* for node allocation */
NODE_TRUNC, /* for node truncate */
NODE_WRITE, /* for node write */
NR_LOCK_TYPE,
};
In that case, we lose the performance under the multi-threading environment,
since every types of operations must be conducted one at a time.
In order to address the problem, let's share the locks globally with a mutex
array regardless of any types.
So, let users grab a mutex and perform their jobs in parallel as much as
possbile.
For this, I propose a new global lock scheme as follows.
0. Data structure
- f2fs_sb_info -> mutex_lock[NR_GLOBAL_LOCKS]
- f2fs_sb_info -> node_write
1. mutex_lock_op(sbi)
- try to get an avaiable lock from the array.
- returns the index of the gottern lock variable.
2. mutex_unlock_op(sbi, index of the lock)
- unlock the given index of the lock.
3. mutex_lock_all(sbi)
- grab all the locks in the array before the checkpoint.
4. mutex_unlock_all(sbi)
- release all the locks in the array after checkpoint.
5. block_operations()
- call mutex_lock_all()
- sync_dirty_dir_inodes()
- grab node_write
- sync_node_pages()
Note that,
the pairs of mutex_lock_op()/mutex_unlock_op() and
mutex_lock_all()/mutex_unlock_all() should be used together.
Signed-off-by: Jaegeuk Kim <jaegeuk.kim@samsung.com>
2012-11-22 08:21:29 +01:00
|
|
|
|
2013-03-20 11:01:06 +01:00
|
|
|
err = get_dnode_of_data(&dn, start, ALLOC_NODE);
|
2015-09-25 11:54:56 +02:00
|
|
|
if (err)
|
2013-12-26 04:49:48 +01:00
|
|
|
goto out;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
2014-03-18 05:29:07 +01:00
|
|
|
f2fs_wait_on_page_writeback(dn.node_page, NODE);
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
get_node_info(sbi, dn.nid, &ni);
|
2014-09-03 00:52:58 +02:00
|
|
|
f2fs_bug_on(sbi, ni.ino != ino_of_node(page));
|
|
|
|
f2fs_bug_on(sbi, ofs_of_node(dn.node_page) != ofs_of_node(page));
|
2012-11-02 09:13:32 +01:00
|
|
|
|
f2fs: recover invalid/reserved block address for fsynced file
When testing with generic/101 in xfstests, error message outputed as below:
--- tests/generic/101.out
+++ results//generic/101.out.bad
@@ -10,10 +10,14 @@
File foo content after log replay:
0000000 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
*
-0200000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0200000 bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb
*
0372000
...
(Run 'diff -u tests/generic/101.out results/generic/101.out.bad' to see the entire diff)
The test flow is like below:
1. pwrite foo -S 0xaa 0 64K
2. pwrite foo -S 0xbb 64K 61K
3. sync
4. truncate foo 64K
5. truncate foo 125K
6. fsync foo
7. flakey drop writes
8. umount
After this test, we expect the data of recovered file will have the first
64k of data filling with value 0xaa and the next 61k of data filling with
value 0x00 because we have fsynced it before dropping writes in dm.
In f2fs, during recovering, we will only recover the valid block address
in direct node page if it is marked as a fsynced dnode, but block address
which means invalid/reserved (with value NULL_ADDR/NEW_ADDR) will not be
recovered. So, the file recovered shows its incorrect data 0xbb in range of
[61k, 125k].
In this patch, we fix to recover invalid/reserved block during recover flow.
Signed-off-by: Chao Yu <chao2.yu@samsung.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2015-08-05 11:23:54 +02:00
|
|
|
for (; start < end; start++, dn.ofs_in_node++) {
|
2012-11-02 09:13:32 +01:00
|
|
|
block_t src, dest;
|
|
|
|
|
|
|
|
src = datablock_addr(dn.node_page, dn.ofs_in_node);
|
|
|
|
dest = datablock_addr(page, dn.ofs_in_node);
|
|
|
|
|
f2fs: recover invalid/reserved block address for fsynced file
When testing with generic/101 in xfstests, error message outputed as below:
--- tests/generic/101.out
+++ results//generic/101.out.bad
@@ -10,10 +10,14 @@
File foo content after log replay:
0000000 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
*
-0200000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0200000 bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb
*
0372000
...
(Run 'diff -u tests/generic/101.out results/generic/101.out.bad' to see the entire diff)
The test flow is like below:
1. pwrite foo -S 0xaa 0 64K
2. pwrite foo -S 0xbb 64K 61K
3. sync
4. truncate foo 64K
5. truncate foo 125K
6. fsync foo
7. flakey drop writes
8. umount
After this test, we expect the data of recovered file will have the first
64k of data filling with value 0xaa and the next 61k of data filling with
value 0x00 because we have fsynced it before dropping writes in dm.
In f2fs, during recovering, we will only recover the valid block address
in direct node page if it is marked as a fsynced dnode, but block address
which means invalid/reserved (with value NULL_ADDR/NEW_ADDR) will not be
recovered. So, the file recovered shows its incorrect data 0xbb in range of
[61k, 125k].
In this patch, we fix to recover invalid/reserved block during recover flow.
Signed-off-by: Chao Yu <chao2.yu@samsung.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2015-08-05 11:23:54 +02:00
|
|
|
/* skip recovering if dest is the same as src */
|
|
|
|
if (src == dest)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* dest is invalid, just invalidate src block */
|
|
|
|
if (dest == NULL_ADDR) {
|
|
|
|
truncate_data_blocks_range(&dn, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* dest is reserved block, invalidate src block
|
|
|
|
* and then reserve one new block in dnode page.
|
|
|
|
*/
|
|
|
|
if (dest == NEW_ADDR) {
|
|
|
|
truncate_data_blocks_range(&dn, 1);
|
|
|
|
err = reserve_new_block(&dn);
|
|
|
|
f2fs_bug_on(sbi, err);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* dest is valid block, try to recover from src to dest */
|
|
|
|
if (is_valid_blkaddr(sbi, dest, META_POR)) {
|
2015-04-02 04:38:20 +02:00
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
if (src == NULL_ADDR) {
|
2013-10-29 07:14:54 +01:00
|
|
|
err = reserve_new_block(&dn);
|
2012-11-02 09:13:32 +01:00
|
|
|
/* We should not get -ENOSPC */
|
2014-09-03 00:52:58 +02:00
|
|
|
f2fs_bug_on(sbi, err);
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the previous node page having this index */
|
2013-05-22 01:20:01 +02:00
|
|
|
err = check_index_in_prev_nodes(sbi, dest, &dn);
|
|
|
|
if (err)
|
|
|
|
goto err;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
/* write dummy data page */
|
2015-05-28 13:15:35 +02:00
|
|
|
f2fs_replace_block(sbi, &dn, src, dest,
|
|
|
|
ni.version, false);
|
2013-05-16 08:04:49 +02:00
|
|
|
recovered++;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_INODE(dn.node_page))
|
|
|
|
sync_inode_page(&dn);
|
|
|
|
|
|
|
|
copy_node_footer(dn.node_page, page);
|
|
|
|
fill_node_footer(dn.node_page, dn.nid, ni.ino,
|
|
|
|
ofs_of_node(page), false);
|
|
|
|
set_page_dirty(dn.node_page);
|
2013-05-22 01:20:01 +02:00
|
|
|
err:
|
2012-11-02 09:13:32 +01:00
|
|
|
f2fs_put_dnode(&dn);
|
2013-12-26 04:49:48 +01:00
|
|
|
out:
|
2014-01-17 21:44:39 +01:00
|
|
|
f2fs_msg(sbi->sb, KERN_NOTICE,
|
|
|
|
"recover_data: ino = %lx, recovered = %d blocks, err = %d",
|
|
|
|
inode->i_ino, recovered, err);
|
2013-05-22 01:20:01 +02:00
|
|
|
return err;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
2015-12-01 04:43:59 +01:00
|
|
|
static int recover_data(struct f2fs_sb_info *sbi, struct list_head *head)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
2013-08-09 08:03:21 +02:00
|
|
|
unsigned long long cp_ver = cur_cp_version(F2FS_CKPT(sbi));
|
2012-11-02 09:13:32 +01:00
|
|
|
struct curseg_info *curseg;
|
2014-09-11 22:49:55 +02:00
|
|
|
struct page *page = NULL;
|
2013-03-20 11:01:06 +01:00
|
|
|
int err = 0;
|
2012-11-02 09:13:32 +01:00
|
|
|
block_t blkaddr;
|
|
|
|
|
|
|
|
/* get node pages in the current segment */
|
2015-12-01 04:43:59 +01:00
|
|
|
curseg = CURSEG_I(sbi, CURSEG_WARM_NODE);
|
2012-11-02 09:13:32 +01:00
|
|
|
blkaddr = NEXT_FREE_BLKADDR(sbi, curseg);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
struct fsync_inode_entry *entry;
|
|
|
|
|
2015-04-18 12:05:36 +02:00
|
|
|
if (!is_valid_blkaddr(sbi, blkaddr, META_POR))
|
2014-09-11 22:49:55 +02:00
|
|
|
break;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
2014-12-08 08:02:52 +01:00
|
|
|
ra_meta_pages_cond(sbi, blkaddr);
|
|
|
|
|
2015-10-12 11:04:21 +02:00
|
|
|
page = get_tmp_page(sbi, blkaddr);
|
2013-03-08 13:29:23 +01:00
|
|
|
|
2014-09-11 22:49:55 +02:00
|
|
|
if (cp_ver != cpver_of_node(page)) {
|
|
|
|
f2fs_put_page(page, 1);
|
2013-05-20 03:26:09 +02:00
|
|
|
break;
|
2014-09-11 22:49:55 +02:00
|
|
|
}
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
entry = get_fsync_inode(head, ino_of_node(page));
|
|
|
|
if (!entry)
|
|
|
|
goto next;
|
2014-09-16 01:46:08 +02:00
|
|
|
/*
|
|
|
|
* inode(x) | CP | inode(x) | dnode(F)
|
|
|
|
* In this case, we can lose the latest inode(x).
|
2014-09-11 23:29:06 +02:00
|
|
|
* So, call recover_inode for the inode update.
|
2014-09-16 01:46:08 +02:00
|
|
|
*/
|
2014-09-11 23:29:06 +02:00
|
|
|
if (entry->last_inode == blkaddr)
|
|
|
|
recover_inode(entry->inode, page);
|
|
|
|
if (entry->last_dentry == blkaddr) {
|
|
|
|
err = recover_dentry(entry->inode, page);
|
|
|
|
if (err) {
|
|
|
|
f2fs_put_page(page, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-03-20 11:01:06 +01:00
|
|
|
err = do_recover_data(sbi, entry->inode, page, blkaddr);
|
2014-09-11 22:49:55 +02:00
|
|
|
if (err) {
|
|
|
|
f2fs_put_page(page, 1);
|
2013-05-20 03:26:09 +02:00
|
|
|
break;
|
2014-09-11 22:49:55 +02:00
|
|
|
}
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
if (entry->blkaddr == blkaddr) {
|
|
|
|
iput(entry->inode);
|
|
|
|
list_del(&entry->list);
|
|
|
|
kmem_cache_free(fsync_entry_slab, entry);
|
|
|
|
}
|
|
|
|
next:
|
|
|
|
/* check next segment */
|
|
|
|
blkaddr = next_blkaddr_of_node(page);
|
2014-09-11 22:49:55 +02:00
|
|
|
f2fs_put_page(page, 1);
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
2013-03-20 11:01:06 +01:00
|
|
|
if (!err)
|
|
|
|
allocate_new_segments(sbi);
|
|
|
|
return err;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|
|
|
|
|
2013-03-20 11:01:06 +01:00
|
|
|
int recover_fsync_data(struct f2fs_sb_info *sbi)
|
2012-11-02 09:13:32 +01:00
|
|
|
{
|
2014-07-26 00:47:25 +02:00
|
|
|
struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_WARM_NODE);
|
2012-11-02 09:13:32 +01:00
|
|
|
struct list_head inode_list;
|
2014-07-26 00:47:25 +02:00
|
|
|
block_t blkaddr;
|
2013-03-20 11:01:06 +01:00
|
|
|
int err;
|
2013-10-23 06:39:32 +02:00
|
|
|
bool need_writecp = false;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
fsync_entry_slab = f2fs_kmem_cache_create("f2fs_fsync_inode_entry",
|
2014-03-07 11:43:28 +01:00
|
|
|
sizeof(struct fsync_inode_entry));
|
2013-12-06 07:00:58 +01:00
|
|
|
if (!fsync_entry_slab)
|
2013-03-20 11:01:06 +01:00
|
|
|
return -ENOMEM;
|
2012-11-02 09:13:32 +01:00
|
|
|
|
|
|
|
INIT_LIST_HEAD(&inode_list);
|
|
|
|
|
2014-08-14 01:30:46 +02:00
|
|
|
/* prevent checkpoint */
|
|
|
|
mutex_lock(&sbi->cp_mutex);
|
|
|
|
|
2014-07-26 00:47:25 +02:00
|
|
|
blkaddr = NEXT_FREE_BLKADDR(sbi, curseg);
|
|
|
|
|
2015-08-11 21:45:39 +02:00
|
|
|
/* step #1: find fsynced inode numbers */
|
2013-03-20 11:01:06 +01:00
|
|
|
err = find_fsync_dnodes(sbi, &inode_list);
|
|
|
|
if (err)
|
2012-11-02 09:13:32 +01:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (list_empty(&inode_list))
|
|
|
|
goto out;
|
|
|
|
|
2013-10-23 06:39:32 +02:00
|
|
|
need_writecp = true;
|
2013-09-24 03:26:24 +02:00
|
|
|
|
2012-11-02 09:13:32 +01:00
|
|
|
/* step #2: recover data */
|
2015-12-01 04:43:59 +01:00
|
|
|
err = recover_data(sbi, &inode_list);
|
2014-08-08 19:18:43 +02:00
|
|
|
if (!err)
|
2014-09-03 00:52:58 +02:00
|
|
|
f2fs_bug_on(sbi, !list_empty(&inode_list));
|
2012-11-02 09:13:32 +01:00
|
|
|
out:
|
2013-06-27 03:28:54 +02:00
|
|
|
destroy_fsync_dnodes(&inode_list);
|
2012-11-02 09:13:32 +01:00
|
|
|
kmem_cache_destroy(fsync_entry_slab);
|
2014-07-26 00:47:25 +02:00
|
|
|
|
2014-09-11 22:49:55 +02:00
|
|
|
/* truncate meta pages to be used by the recovery */
|
|
|
|
truncate_inode_pages_range(META_MAPPING(sbi),
|
2015-09-11 08:43:52 +02:00
|
|
|
(loff_t)MAIN_BLKADDR(sbi) << PAGE_CACHE_SHIFT, -1);
|
2014-09-11 22:49:55 +02:00
|
|
|
|
2014-07-26 00:47:25 +02:00
|
|
|
if (err) {
|
|
|
|
truncate_inode_pages_final(NODE_MAPPING(sbi));
|
|
|
|
truncate_inode_pages_final(META_MAPPING(sbi));
|
|
|
|
}
|
|
|
|
|
2015-01-28 10:48:42 +01:00
|
|
|
clear_sbi_flag(sbi, SBI_POR_DOING);
|
2014-07-26 00:47:25 +02:00
|
|
|
if (err) {
|
2015-07-28 12:36:47 +02:00
|
|
|
bool invalidate = false;
|
|
|
|
|
|
|
|
if (discard_next_dnode(sbi, blkaddr))
|
|
|
|
invalidate = true;
|
2014-07-26 00:47:25 +02:00
|
|
|
|
|
|
|
/* Flush all the NAT/SIT pages */
|
|
|
|
while (get_pages(sbi, F2FS_DIRTY_META))
|
|
|
|
sync_meta_pages(sbi, META, LONG_MAX);
|
2015-07-28 12:36:47 +02:00
|
|
|
|
|
|
|
/* invalidate temporary meta page */
|
|
|
|
if (invalidate)
|
|
|
|
invalidate_mapping_pages(META_MAPPING(sbi),
|
|
|
|
blkaddr, blkaddr);
|
|
|
|
|
2014-08-14 01:30:46 +02:00
|
|
|
set_ckpt_flags(sbi->ckpt, CP_ERROR_FLAG);
|
|
|
|
mutex_unlock(&sbi->cp_mutex);
|
2014-07-26 00:47:25 +02:00
|
|
|
} else if (need_writecp) {
|
2014-09-21 06:57:51 +02:00
|
|
|
struct cp_control cpc = {
|
2015-04-10 02:03:53 +02:00
|
|
|
.reason = CP_RECOVERY,
|
2014-09-21 06:57:51 +02:00
|
|
|
};
|
2014-08-14 01:30:46 +02:00
|
|
|
mutex_unlock(&sbi->cp_mutex);
|
2015-12-23 10:50:30 +01:00
|
|
|
err = write_checkpoint(sbi, &cpc);
|
2014-08-14 01:30:46 +02:00
|
|
|
} else {
|
|
|
|
mutex_unlock(&sbi->cp_mutex);
|
2014-07-26 00:47:25 +02:00
|
|
|
}
|
2013-03-20 11:01:06 +01:00
|
|
|
return err;
|
2012-11-02 09:13:32 +01:00
|
|
|
}
|