// SPDX-License-Identifier: GPL-2.0
/*
* fs/f2fs/recovery.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*/
#include <linux/unaligned.h>
#include <linux/fs.h>
#include <linux/f2fs_fs.h>
#include <linux/sched/mm.h>
#include "f2fs.h"
#include "node.h"
#include "segment.h"
/*
* 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).
*/
static struct kmem_cache *fsync_entry_slab;
bool f2fs_space_for_roll_forward(struct f2fs_sb_info *sbi)
{
s64 nalloc = percpu_counter_sum_positive(&sbi->alloc_valid_block_count);
if (sbi->last_valid_block_count + nalloc > sbi->user_block_count)
return false;
if (NM_I(sbi)->max_rf_node_blocks &&
percpu_counter_sum_positive(&sbi->rf_node_block_count) >=
NM_I(sbi)->max_rf_node_blocks)
return false;
return true;
}
static struct fsync_inode_entry *get_fsync_inode(struct list_head *head,
nid_t ino)
{
struct fsync_inode_entry *entry;
list_for_each_entry(entry, head, list)
if (entry->inode->i_ino == ino)
return entry;
return NULL;
}
static struct fsync_inode_entry *add_fsync_inode(struct f2fs_sb_info *sbi,
struct list_head *head, nid_t ino, bool quota_inode)
{
struct inode *inode;
struct fsync_inode_entry *entry;
int err;
inode = f2fs_iget_retry(sbi->sb, ino);
if (IS_ERR(inode))
return ERR_CAST(inode);
err = f2fs_dquot_initialize(inode);
if (err)
goto err_out;
if (quota_inode) {
err = dquot_alloc_inode(inode);
if (err)
goto err_out;
}
entry = f2fs_kmem_cache_alloc(fsync_entry_slab,
GFP_F2FS_ZERO, true, NULL);
entry->inode = inode;
list_add_tail(&entry->list, head);
return entry;
err_out:
iput(inode);
return ERR_PTR(err);
}
static void del_fsync_inode(struct fsync_inode_entry *entry, int drop)
{
if (drop) {
/* inode should not be recovered, drop it */
f2fs_inode_synced(entry->inode);
}
iput(entry->inode);
list_del(&entry->list);
kmem_cache_free(fsync_entry_slab, entry);
}
static int init_recovered_filename(const struct inode *dir,
struct f2fs_inode *raw_inode,
struct f2fs_filename *fname,
struct qstr *usr_fname)
{
int err;
memset(fname, 0, sizeof(*fname));
fname->disk_name.len = le32_to_cpu(raw_inode->i_namelen);
fname->disk_name.name = raw_inode->i_name;
if (WARN_ON(fname->disk_name.len > F2FS_NAME_LEN))
return -ENAMETOOLONG;
if (!IS_ENCRYPTED(dir)) {
usr_fname->name = fname->disk_name.name;
usr_fname->len = fname->disk_name.len;
fname->usr_fname = usr_fname;
}
/* Compute the hash of the filename */
if (IS_ENCRYPTED(dir) && IS_CASEFOLDED(dir)) {
/*
* In this case the hash isn't computable without the key, so it
* was saved on-disk.
*/
if (fname->disk_name.len + sizeof(f2fs_hash_t) > F2FS_NAME_LEN)
return -EINVAL;
fname->hash = get_unaligned((f2fs_hash_t *)
&raw_inode->i_name[fname->disk_name.len]);
} else if (IS_CASEFOLDED(dir)) {
err = f2fs_init_casefolded_name(dir, fname);
if (err)
return err;
f2fs_hash_filename(dir, fname);
/* Case-sensitive match is fine for recovery */
f2fs_free_casefolded_name(fname);
} else {
f2fs_hash_filename(dir, fname);
}
return 0;
}
static int recover_dentry(struct inode *inode, struct folio *ifolio,
struct list_head *dir_list)
{
struct f2fs_inode *raw_inode = F2FS_INODE(ifolio);