7391 lines
204 KiB
C
7391 lines
204 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/ext4/super.c
|
|
*
|
|
* Copyright (C) 1992, 1993, 1994, 1995
|
|
* Remy Card (card@masi.ibp.fr)
|
|
* Laboratoire MASI - Institut Blaise Pascal
|
|
* Universite Pierre et Marie Curie (Paris VI)
|
|
*
|
|
* from
|
|
*
|
|
* linux/fs/minix/inode.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*
|
|
* Big-endian to little-endian byte-swapping/bitmaps by
|
|
* David S. Miller (davem@caip.rutgers.edu), 1995
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/time.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/backing-dev.h>
|
|
#include <linux/parser.h>
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/exportfs.h>
|
|
#include <linux/vfs.h>
|
|
#include <linux/random.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/quotaops.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/log2.h>
|
|
#include <linux/crc16.h>
|
|
#include <linux/dax.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/iversion.h>
|
|
#include <linux/unicode.h>
|
|
#include <linux/part_stat.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/fsnotify.h>
|
|
#include <linux/fs_context.h>
|
|
#include <linux/fs_parser.h>
|
|
|
|
#include "ext4.h"
|
|
#include "ext4_extents.h" /* Needed for trace points definition */
|
|
#include "ext4_jbd2.h"
|
|
#include "xattr.h"
|
|
#include "acl.h"
|
|
#include "mballoc.h"
|
|
#include "fsmap.h"
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include <trace/events/ext4.h>
|
|
|
|
static struct ext4_lazy_init *ext4_li_info;
|
|
static DEFINE_MUTEX(ext4_li_mtx);
|
|
static struct ratelimit_state ext4_mount_msg_ratelimit;
|
|
|
|
static int ext4_load_journal(struct super_block *, struct ext4_super_block *,
|
|
unsigned long journal_devnum);
|
|
static int ext4_show_options(struct seq_file *seq, struct dentry *root);
|
|
static void ext4_update_super(struct super_block *sb);
|
|
static int ext4_commit_super(struct super_block *sb);
|
|
static int ext4_mark_recovery_complete(struct super_block *sb,
|
|
struct ext4_super_block *es);
|
|
static int ext4_clear_journal_err(struct super_block *sb,
|
|
struct ext4_super_block *es);
|
|
static int ext4_sync_fs(struct super_block *sb, int wait);
|
|
static int ext4_statfs(struct dentry *dentry, struct kstatfs *buf);
|
|
static int ext4_unfreeze(struct super_block *sb);
|
|
static int ext4_freeze(struct super_block *sb);
|
|
static inline int ext2_feature_set_ok(struct super_block *sb);
|
|
static inline int ext3_feature_set_ok(struct super_block *sb);
|
|
static void ext4_destroy_lazyinit_thread(void);
|
|
static void ext4_unregister_li_request(struct super_block *sb);
|
|
static void ext4_clear_request_list(void);
|
|
static struct inode *ext4_get_journal_inode(struct super_block *sb,
|
|
unsigned int journal_inum);
|
|
static int ext4_validate_options(struct fs_context *fc);
|
|
static int ext4_check_opt_consistency(struct fs_context *fc,
|
|
struct super_block *sb);
|
|
static void ext4_apply_options(struct fs_context *fc, struct super_block *sb);
|
|
static int ext4_parse_param(struct fs_context *fc, struct fs_parameter *param);
|
|
static int ext4_get_tree(struct fs_context *fc);
|
|
static int ext4_reconfigure(struct fs_context *fc);
|
|
static void ext4_fc_free(struct fs_context *fc);
|
|
static int ext4_init_fs_context(struct fs_context *fc);
|
|
static const struct fs_parameter_spec ext4_param_specs[];
|
|
|
|
/*
|
|
* Lock ordering
|
|
*
|
|
* page fault path:
|
|
* mmap_lock -> sb_start_pagefault -> invalidate_lock (r) -> transaction start
|
|
* -> page lock -> i_data_sem (rw)
|
|
*
|
|
* buffered write path:
|
|
* sb_start_write -> i_mutex -> mmap_lock
|
|
* sb_start_write -> i_mutex -> transaction start -> page lock ->
|
|
* i_data_sem (rw)
|
|
*
|
|
* truncate:
|
|
* sb_start_write -> i_mutex -> invalidate_lock (w) -> i_mmap_rwsem (w) ->
|
|
* page lock
|
|
* sb_start_write -> i_mutex -> invalidate_lock (w) -> transaction start ->
|
|
* i_data_sem (rw)
|
|
*
|
|
* direct IO:
|
|
* sb_start_write -> i_mutex -> mmap_lock
|
|
* sb_start_write -> i_mutex -> transaction start -> i_data_sem (rw)
|
|
*
|
|
* writepages:
|
|
* transaction start -> page lock(s) -> i_data_sem (rw)
|
|
*/
|
|
|
|
static const struct fs_context_operations ext4_context_ops = {
|
|
.parse_param = ext4_parse_param,
|
|
.get_tree = ext4_get_tree,
|
|
.reconfigure = ext4_reconfigure,
|
|
.free = ext4_fc_free,
|
|
};
|
|
|
|
|
|
#if !defined(CONFIG_EXT2_FS) && !defined(CONFIG_EXT2_FS_MODULE) && defined(CONFIG_EXT4_USE_FOR_EXT2)
|
|
static struct file_system_type ext2_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = "ext2",
|
|
.init_fs_context = ext4_init_fs_context,
|
|
.parameters = ext4_param_specs,
|
|
.kill_sb = kill_block_super,
|
|
.fs_flags = FS_REQUIRES_DEV,
|
|
};
|
|
MODULE_ALIAS_FS("ext2");
|
|
MODULE_ALIAS("ext2");
|
|
#define IS_EXT2_SB(sb) ((sb)->s_bdev->bd_holder == &ext2_fs_type)
|
|
#else
|
|
#define IS_EXT2_SB(sb) (0)
|
|
#endif
|
|
|
|
|
|
static struct file_system_type ext3_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = "ext3",
|
|
.init_fs_context = ext4_init_fs_context,
|
|
.parameters = ext4_param_specs,
|
|
.kill_sb = kill_block_super,
|
|
.fs_flags = FS_REQUIRES_DEV,
|
|
};
|
|
MODULE_ALIAS_FS("ext3");
|
|
MODULE_ALIAS("ext3");
|
|
#define IS_EXT3_SB(sb) ((sb)->s_bdev->bd_holder == &ext3_fs_type)
|
|
|
|
|
|
static inline void __ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
|
|
bh_end_io_t *end_io)
|
|
{
|
|
/*
|
|
* buffer's verified bit is no longer valid after reading from
|
|
* disk again due to write out error, clear it to make sure we
|
|
* recheck the buffer contents.
|
|
*/
|
|
clear_buffer_verified(bh);
|
|
|
|
bh->b_end_io = end_io ? end_io : end_buffer_read_sync;
|
|
get_bh(bh);
|
|
submit_bh(REQ_OP_READ | op_flags, bh);
|
|
}
|
|
|
|
void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
|
|
bh_end_io_t *end_io)
|
|
{
|
|
BUG_ON(!buffer_locked(bh));
|
|
|
|
if (ext4_buffer_uptodate(bh)) {
|
|
unlock_buffer(bh);
|
|
return;
|
|
}
|
|
__ext4_read_bh(bh, op_flags, end_io);
|
|
}
|
|
|
|
int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags, bh_end_io_t *end_io)
|
|
{
|
|
BUG_ON(!buffer_locked(bh));
|
|
|
|
if (ext4_buffer_uptodate(bh)) {
|
|
unlock_buffer(bh);
|
|
return 0;
|
|
}
|
|
|
|
__ext4_read_bh(bh, op_flags, end_io);
|
|
|
|
wait_on_buffer(bh);
|
|
if (buffer_uptodate(bh))
|
|
return 0;
|
|
return -EIO;
|
|
}
|
|
|
|
int ext4_read_bh_lock(struct buffer_head *bh, blk_opf_t op_flags, bool wait)
|
|
{
|
|
lock_buffer(bh);
|
|
if (!wait) {
|
|
ext4_read_bh_nowait(bh, op_flags, NULL);
|
|
return 0;
|
|
}
|
|
return ext4_read_bh(bh, op_flags, NULL);
|
|
}
|
|
|
|
/*
|
|
* This works like __bread_gfp() except it uses ERR_PTR for error
|
|
* returns. Currently with sb_bread it's impossible to distinguish
|
|
* between ENOMEM and EIO situations (since both result in a NULL
|
|
* return.
|
|
*/
|
|
static struct buffer_head *__ext4_sb_bread_gfp(struct super_block *sb,
|
|
sector_t block,
|
|
blk_opf_t op_flags, gfp_t gfp)
|
|
{
|
|
struct buffer_head *bh;
|
|
int ret;
|
|
|
|
bh = sb_getblk_gfp(sb, block, gfp);
|
|
if (bh == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
if (ext4_buffer_uptodate(bh))
|
|
return bh;
|
|
|
|
ret = ext4_read_bh_lock(bh, REQ_META | op_flags, true);
|
|
if (ret) {
|
|
put_bh(bh);
|
|
return ERR_PTR(ret);
|
|
}
|
|
return bh;
|
|
}
|
|
|
|
struct buffer_head *ext4_sb_bread(struct super_block *sb, sector_t block,
|
|
blk_opf_t op_flags)
|
|
{
|
|
return __ext4_sb_bread_gfp(sb, block, op_flags, __GFP_MOVABLE);
|
|
}
|
|
|
|
struct buffer_head *ext4_sb_bread_unmovable(struct super_block *sb,
|
|
sector_t block)
|
|
{
|
|
return __ext4_sb_bread_gfp(sb, block, 0, 0);
|
|
}
|
|
|
|
void ext4_sb_breadahead_unmovable(struct super_block *sb, sector_t block)
|
|
{
|
|
struct buffer_head *bh = sb_getblk_gfp(sb, block, 0);
|
|
|
|
if (likely(bh)) {
|
|
if (trylock_buffer(bh))
|
|
ext4_read_bh_nowait(bh, REQ_RAHEAD, NULL);
|
|
brelse(bh);
|
|
}
|
|
}
|
|
|
|
static int ext4_verify_csum_type(struct super_block *sb,
|
|
struct ext4_super_block *es)
|
|
{
|
|
if (!ext4_has_feature_metadata_csum(sb))
|
|
return 1;
|
|
|
|
return es->s_checksum_type == EXT4_CRC32C_CHKSUM;
|
|
}
|
|
|
|
__le32 ext4_superblock_csum(struct super_block *sb,
|
|
struct ext4_super_block *es)
|
|
{
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
int offset = offsetof(struct ext4_super_block, s_checksum);
|
|
__u32 csum;
|
|
|
|
csum = ext4_chksum(sbi, ~0, (char *)es, offset);
|
|
|
|
return cpu_to_le32(csum);
|
|
}
|
|
|
|
static int ext4_superblock_csum_verify(struct super_block *sb,
|
|
struct ext4_super_block *es)
|
|
{
|
|
if (!ext4_has_metadata_csum(sb))
|
|
return 1;
|
|
|
|
return es->s_checksum == ext4_superblock_csum(sb, es);
|
|
}
|
|
|
|
void ext4_superblock_csum_set(struct super_block *sb)
|
|
{
|
|
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
|
|
|
|
if (!ext4_has_metadata_csum(sb))
|
|
return;
|
|
|
|
es->s_checksum = ext4_superblock_csum(sb, es);
|
|
}
|
|
|
|
ext4_fsblk_t ext4_block_bitmap(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le32_to_cpu(bg->bg_block_bitmap_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(ext4_fsblk_t)le32_to_cpu(bg->bg_block_bitmap_hi) << 32 : 0);
|
|
}
|
|
|
|
ext4_fsblk_t ext4_inode_bitmap(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le32_to_cpu(bg->bg_inode_bitmap_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(ext4_fsblk_t)le32_to_cpu(bg->bg_inode_bitmap_hi) << 32 : 0);
|
|
}
|
|
|
|
ext4_fsblk_t ext4_inode_table(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le32_to_cpu(bg->bg_inode_table_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(ext4_fsblk_t)le32_to_cpu(bg->bg_inode_table_hi) << 32 : 0);
|
|
}
|
|
|
|
__u32 ext4_free_group_clusters(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le16_to_cpu(bg->bg_free_blocks_count_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(__u32)le16_to_cpu(bg->bg_free_blocks_count_hi) << 16 : 0);
|
|
}
|
|
|
|
__u32 ext4_free_inodes_count(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le16_to_cpu(bg->bg_free_inodes_count_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(__u32)le16_to_cpu(bg->bg_free_inodes_count_hi) << 16 : 0);
|
|
}
|
|
|
|
__u32 ext4_used_dirs_count(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le16_to_cpu(bg->bg_used_dirs_count_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(__u32)le16_to_cpu(bg->bg_used_dirs_count_hi) << 16 : 0);
|
|
}
|
|
|
|
__u32 ext4_itable_unused_count(struct super_block *sb,
|
|
struct ext4_group_desc *bg)
|
|
{
|
|
return le16_to_cpu(bg->bg_itable_unused_lo) |
|
|
(EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
|
|
(__u32)le16_to_cpu(bg->bg_itable_unused_hi) << 16 : 0);
|
|
}
|
|
|
|
void ext4_block_bitmap_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, ext4_fsblk_t blk)
|
|
{
|
|
bg->bg_block_bitmap_lo = cpu_to_le32((u32)blk);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_block_bitmap_hi = cpu_to_le32(blk >> 32);
|
|
}
|
|
|
|
void ext4_inode_bitmap_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, ext4_fsblk_t blk)
|
|
{
|
|
bg->bg_inode_bitmap_lo = cpu_to_le32((u32)blk);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_inode_bitmap_hi = cpu_to_le32(blk >> 32);
|
|
}
|
|
|
|
void ext4_inode_table_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, ext4_fsblk_t blk)
|
|
{
|
|
bg->bg_inode_table_lo = cpu_to_le32((u32)blk);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_inode_table_hi = cpu_to_le32(blk >> 32);
|
|
}
|
|
|
|
void ext4_free_group_clusters_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, __u32 count)
|
|
{
|
|
bg->bg_free_blocks_count_lo = cpu_to_le16((__u16)count);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_free_blocks_count_hi = cpu_to_le16(count >> 16);
|
|
}
|
|
|
|
void ext4_free_inodes_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, __u32 count)
|
|
{
|
|
bg->bg_free_inodes_count_lo = cpu_to_le16((__u16)count);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_free_inodes_count_hi = cpu_to_le16(count >> 16);
|
|
}
|
|
|
|
void ext4_used_dirs_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, __u32 count)
|
|
{
|
|
bg->bg_used_dirs_count_lo = cpu_to_le16((__u16)count);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_used_dirs_count_hi = cpu_to_le16(count >> 16);
|
|
}
|
|
|
|
void ext4_itable_unused_set(struct super_block *sb,
|
|
struct ext4_group_desc *bg, __u32 count)
|
|
{
|
|
bg->bg_itable_unused_lo = cpu_to_le16((__u16)count);
|
|
if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
|
|
bg->bg_itable_unused_hi = cpu_to_le16(count >> 16);
|
|
}
|
|
|
|
static void __ext4_update_tstamp(__le32 *lo, __u8 *hi, time64_t now)
|
|
{
|
|
now = clamp_val(now, 0, (1ull << 40) - 1);
|
|
|
|
*lo = cpu_to_le32(lower_32_bits(now));
|
|
*hi = upper_32_bits(now);
|
|
}
|
|
|
|
static time64_t __ext4_get_tstamp(__le32 *lo, __u8 *hi)
|
|
{
|
|
return ((time64_t)(*hi) << 32) + le32_to_cpu(*lo);
|
|
}
|
|
#define ext4_update_tstamp(es, tstamp) \
|
|
__ext4_update_tstamp(&(es)->tstamp, &(es)->tstamp ## _hi, \
|
|
ktime_get_real_seconds())
|
|
#define ext4_get_tstamp(es, tstamp) \
|
|
__ext4_get_tstamp(&(es)->tstamp, &(es)->tstamp ## _hi)
|
|
|
|
/*
|
|
* The del_gendisk() function uninitializes the disk-specific data
|
|
* structures, including the bdi structure, without telling anyone
|
|
* else. Once this happens, any attempt to call mark_buffer_dirty()
|
|
* (for example, by ext4_commit_super), will cause a kernel OOPS.
|
|
* This is a kludge to prevent these oops until we can put in a proper
|
|
* hook in del_gendisk() to inform the VFS and file system layers.
|
|
*/
|
|
static int block_device_ejected(struct super_block *sb)
|
|
{
|
|
struct inode *bd_inode = sb->s_bdev->bd_inode;
|
|
struct backing_dev_info *bdi = inode_to_bdi(bd_inode);
|
|
|
|
return bdi->dev == NULL;
|
|
}
|
|
|
|
static void ext4_journal_commit_callback(journal_t *journal, transaction_t *txn)
|
|
{
|
|
struct super_block *sb = journal->j_private;
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
int error = is_journal_aborted(journal);
|
|
struct ext4_journal_cb_entry *jce;
|
|
|
|
BUG_ON(txn->t_state == T_FINISHED);
|
|
|
|
ext4_process_freed_data(sb, txn->t_tid);
|
|
|
|
spin_lock(&sbi->s_md_lock);
|
|
while (!list_empty(&txn->t_private_list)) {
|
|
jce = list_entry(txn->t_private_list.next,
|
|
struct ext4_journal_cb_entry, jce_list);
|
|
list_del_init(&jce->jce_list);
|
|
spin_unlock(&sbi->s_md_lock);
|
|
jce->jce_func(sb, jce, error);
|
|
spin_lock(&sbi->s_md_lock);
|
|
}
|
|
spin_unlock(&sbi->s_md_lock);
|
|
}
|
|
|
|
/*
|
|
* This writepage callback for write_cache_pages()
|
|
* takes care of a few cases after page cleaning.
|
|
*
|
|
* write_cache_pages() already checks for dirty pages
|
|
* and calls clear_page_dirty_for_io(), which we want,
|
|
* to write protect the pages.
|
|
*
|
|
* However, we may have to redirty a page (see below.)
|
|
*/
|
|
static int ext4_journalled_writepage_callback(struct folio *folio,
|
|
struct writeback_control *wbc,
|
|
void *data)
|
|
{
|
|
transaction_t *transaction = (transaction_t *) data;
|
|
struct buffer_head *bh, *head;
|
|
struct journal_head *jh;
|
|
|
|
bh = head = folio_buffers(folio);
|
|
do {
|
|
/*
|
|
* We have to redirty a page in these cases:
|
|
* 1) If buffer is dirty, it means the page was dirty because it
|
|
* contains a buffer that needs checkpointing. So the dirty bit
|
|
* needs to be preserved so that checkpointing writes the buffer
|
|
* properly.
|
|
* 2) If buffer is not part of the committing transaction
|
|
* (we may have just accidentally come across this buffer because
|
|
* inode range tracking is not exact) or if the currently running
|
|
* transaction already contains this buffer as well, dirty bit
|
|
* needs to be preserved so that the buffer gets writeprotected
|
|
* properly on running transaction's commit.
|
|
*/
|
|
jh = bh2jh(bh);
|
|
if (buffer_dirty(bh) ||
|
|
(jh && (jh->b_transaction != transaction ||
|
|
jh->b_next_transaction))) {
|
|
folio_redirty_for_writepage(wbc, folio);
|
|
goto out;
|
|
}
|
|
} while ((bh = bh->b_this_page) != head);
|
|
|
|
out:
|
|
return AOP_WRITEPAGE_ACTIVATE;
|
|
}
|
|
|
|
static int ext4_journalled_submit_inode_data_buffers(struct jbd2_inode *jinode)
|
|
{
|
|
struct address_space *mapping = jinode->i_vfs_inode->i_mapping;
|
|
struct writeback_control wbc = {
|
|
.sync_mode = WB_SYNC_ALL,
|
|
.nr_to_write = LONG_MAX,
|
|
.range_start = jinode->i_dirty_start,
|
|
.range_end = jinode->i_dirty_end,
|
|
};
|
|
|
|
return write_cache_pages(mapping, &wbc,
|
|
ext4_journalled_writepage_callback,
|
|
jinode->i_transaction);
|
|
}
|
|
|
|
static int ext4_journal_submit_inode_data_buffers(struct jbd2_inode *jinode)
|
|
{
|
|
int ret;
|
|
|
|
if (ext4_should_journal_data(jinode->i_vfs_inode))
|
|
ret = ext4_journalled_submit_inode_data_buffers(jinode);
|
|
else
|
|
ret = ext4_normal_submit_inode_data_buffers(jinode);
|
|
return ret;
|
|
}
|
|
|
|
static int ext4_journal_finish_inode_data_buffers(struct jbd2_inode *jinode)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!ext4_should_journal_data(jinode->i_vfs_inode))
|
|
ret = jbd2_journal_finish_inode_data_buffers(jinode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool system_going_down(void)
|
|
{
|
|
return system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF
|
|
|| system_state == SYSTEM_RESTART;
|
|
}
|
|
|
|
struct ext4_err_translation {
|
|
int code;
|
|
int errno;
|
|
};
|
|
|
|
#define EXT4_ERR_TRANSLATE(err) { .code = EXT4_ERR_##err, .errno = err }
|
|
|
|
static struct ext4_err_translation err_translation[] = {
|
|
EXT4_ERR_TRANSLATE(EIO),
|
|
EXT4_ERR_TRANSLATE(ENOMEM),
|
|
EXT4_ERR_TRANSLATE(EFSBADCRC),
|
|
EXT4_ERR_TRANSLATE(EFSCORRUPTED),
|
|
EXT4_ERR_TRANSLATE(ENOSPC),
|
|
EXT4_ERR_TRANSLATE(ENOKEY),
|
|
EXT4_ERR_TRANSLATE(EROFS),
|
|
EXT4_ERR_TRANSLATE(EFBIG),
|
|
EXT4_ERR_TRANSLATE(EEXIST),
|
|
EXT4_ERR_TRANSLATE(ERANGE),
|
|
EXT4_ERR_TRANSLATE(EOVERFLOW),
|
|
EXT4_ERR_TRANSLATE(EBUSY),
|
|
EXT4_ERR_TRANSLATE(ENOTDIR),
|
|
EXT4_ERR_TRANSLATE(ENOTEMPTY),
|
|
EXT4_ERR_TRANSLATE(ESHUTDOWN),
|
|
EXT4_ERR_TRANSLATE(EFAULT),
|
|
};
|
|
|
|
static int ext4_errno_to_code(int errno)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(err_translation); i++)
|
|
if (err_translation[i].errno == errno)
|
|
return err_translation[i].code;
|
|
return EXT4_ERR_UNKNOWN;
|
|
}
|
|
|
|
static void save_error_info(struct super_block *sb, int error,
|
|
__u32 ino, __u64 block,
|
|
const char *func, unsigned int line)
|
|
{
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
|
|
/* We default to EFSCORRUPTED error... */
|
|
if (error == 0)
|
|
error = EFSCORRUPTED;
|
|
|
|
spin_lock(&sbi->s_error_lock);
|
|
sbi->s_add_error_count++;
|
|
sbi->s_last_error_code = error;
|
|
sbi->s_last_error_line = line;
|
|
sbi->s_last_error_ino = ino;
|
|
sbi->s_last_error_block = block;
|
|
sbi->s_last_error_func = func;
|
|
sbi->s_last_error_time = ktime_get_real_seconds();
|
|
if (!sbi->s_first_error_time) {
|
|
sbi->s_first_error_code = error;
|
|
sbi->s_first_error_line = line;
|
|
sbi->s_first_error_ino = ino;
|
|
sbi->s_first_error_block = block;
|
|
sbi->s_first_error_func = func;
|
|
sbi->s_first_error_time = sbi->s_last_error_time;
|
|
}
|
|
spin_unlock(&sbi->s_error_lock);
|
|
}
|
|
|
|
/* Deal with the reporting of failure conditions on a filesystem such as
|
|
* inconsistencies detected or read IO failures.
|
|
*
|
|
* On ext2, we can store the error state of the filesystem in the
|
|
* superblock. That is not possible on ext4, because we may have other
|
|
* write ordering constraints on the superblock which prevent us from
|
|
* writing it out straight away; and given that the journal is about to
|
|
* be aborted, we can't rely on the current, or future, transactions to
|
|
* write out the superblock safely.
|
|
*
|
|
* We'll just use the jbd2_journal_abort() error code to record an error in
|
|
* the journal instead. On recovery, the journal will complain about
|
|
* that error until we've noted it down and cleared it.
|
|
*
|
|
* If force_ro is set, we unconditionally force the filesystem into an
|
|
* ABORT|READONLY state, unless the error response on the fs has been set to
|
|
* panic in which case we take the easy way out and panic immediately. This is
|
|
* used to deal with unrecoverable failures such as journal IO errors or ENOMEM
|
|
* at a critical moment in log management.
|
|
*/
|
|
static void ext4_handle_error(struct super_block *sb, bool force_ro, int error,
|
|
__u32 ino, __u64 block,
|
|
const char *func, unsigned int line)
|
|
{
|
|
journal_t *journal = EXT4_SB(sb)->s_journal;
|
|
bool continue_fs = !force_ro && test_opt(sb, ERRORS_CONT);
|
|
|
|
EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
|
|
if (test_opt(sb, WARN_ON_ERROR))
|
|
WARN_ON_ONCE(1);
|
|
|
|
if (!continue_fs && !sb_rdonly(sb)) {
|
|
ext4_set_mount_flag(sb, EXT4_MF_FS_ABORTED);
|
|
if (journal)
|
|
jbd2_journal_abort(journal, -EIO);
|
|
}
|
|
|
|
if (!bdev_read_only(sb->s_bdev)) {
|
|
save_error_info(sb, error, ino, block, func, line);
|
|
/*
|
|
* In case the fs should keep running, we need to writeout
|
|
* superblock through the journal. Due to lock ordering
|
|
* constraints, it may not be safe to do it right here so we
|
|
* defer superblock flushing to a workqueue.
|
|
*/
|
|
if (continue_fs && journal)
|
|
schedule_work(&EXT4_SB(sb)->s_error_work);
|
|
else
|
|
ext4_commit_super(sb);
|
|
}
|
|
|
|
/*
|
|
* We force ERRORS_RO behavior when system is rebooting. Otherwise we
|
|
* could panic during 'reboot -f' as the underlying device got already
|
|
* disabled.
|
|
*/
|
|
if (test_opt(sb, ERRORS_PANIC) && !system_going_down()) {
|
|
panic("EXT4-fs (device %s): panic forced after error\n",
|
|
sb->s_id);
|
|
}
|
|
|
|
if (sb_rdonly(sb) || continue_fs)
|
|
return;
|
|
|
|
ext4_msg(sb, KERN_CRIT, "Remounting filesystem read-only");
|
|
/*
|
|
* Make sure updated value of ->s_mount_flags will be visible before
|
|
* ->s_flags update
|
|
*/
|
|
smp_wmb();
|
|
sb->s_flags |= SB_RDONLY;
|
|
}
|
|
|
|
static void flush_stashed_error_work(struct work_struct *work)
|
|
{
|
|
struct ext4_sb_info *sbi = container_of(work, struct ext4_sb_info,
|
|
s_error_work);
|
|
journal_t *journal = sbi->s_journal;
|
|
handle_t *handle;
|
|
|
|
/*
|
|
* If the journal is still running, we have to write out superblock
|
|
* through the journal to avoid collisions of other journalled sb
|
|
* updates.
|
|
*
|
|
* We use directly jbd2 functions here to avoid recursing back into
|
|
* ext4 error handling code during handling of previous errors.
|
|
*/
|
|
if (!sb_rdonly(sbi->s_sb) && journal) {
|
|
struct buffer_head *sbh = sbi->s_sbh;
|
|
handle = jbd2_journal_start(journal, 1);
|
|
if (IS_ERR(handle))
|
|
goto write_directly;
|
|
if (jbd2_journal_get_write_access(handle, sbh)) {
|
|
jbd2_journal_stop(handle);
|
|
goto write_directly;
|
|
}
|
|
ext4_update_super(sbi->s_sb);
|
|
if (buffer_write_io_error(sbh) || !buffer_uptodate(sbh)) {
|
|
ext4_msg(sbi->s_sb, KERN_ERR, "previous I/O error to "
|
|
"superblock detected");
|
|
clear_buffer_write_io_error(sbh);
|
|
set_buffer_uptodate(sbh);
|
|
}
|
|
|
|
if (jbd2_journal_dirty_metadata(handle, sbh)) {
|
|
jbd2_journal_stop(handle);
|
|
goto write_directly;
|
|
}
|
|
jbd2_journal_stop(handle);
|
|
ext4_notify_error_sysfs(sbi);
|
|
return;
|
|
}
|
|
write_directly:
|
|
/*
|
|
* Write through journal failed. Write sb directly to get error info
|
|
* out and hope for the best.
|
|
*/
|
|
ext4_commit_super(sbi->s_sb);
|
|
ext4_notify_error_sysfs(sbi);
|
|
}
|
|
|
|
#define ext4_error_ratelimit(sb) \
|
|
___ratelimit(&(EXT4_SB(sb)->s_err_ratelimit_state), \
|
|
"EXT4-fs error")
|
|
|
|
void __ext4_error(struct super_block *sb, const char *function,
|
|
unsigned int line, bool force_ro, int error, __u64 block,
|
|
const char *fmt, ...)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
if (unlikely(ext4_forced_shutdown(EXT4_SB(sb))))
|
|
return;
|
|
|
|
trace_ext4_error(sb, function, line);
|
|
if (ext4_error_ratelimit(sb)) {
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
printk(KERN_CRIT
|
|
"EXT4-fs error (device %s): %s:%d: comm %s: %pV\n",
|
|
sb->s_id, function, line, current->comm, &vaf);
|
|
va_end(args);
|
|
}
|
|
fsnotify_sb_error(sb, NULL, error ? error : EFSCORRUPTED);
|
|
|
|
ext4_handle_error(sb, force_ro, error, 0, block, function, line);
|
|
}
|
|
|
|
void __ext4_error_inode(struct inode *inode, const char *function,
|
|
unsigned int line, ext4_fsblk_t block, int error,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
struct va_format vaf;
|
|
|
|
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
|
|
return;
|
|
|
|
trace_ext4_error(inode->i_sb, function, line);
|
|
if (ext4_error_ratelimit(inode->i_sb)) {
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
if (block)
|
|
printk(KERN_CRIT "EXT4-fs error (device %s): %s:%d: "
|
|
"inode #%lu: block %llu: comm %s: %pV\n",
|
|
inode->i_sb->s_id, function, line, inode->i_ino,
|
|
block, current->comm, &vaf);
|
|
else
|
|
printk(KERN_CRIT "EXT4-fs error (device %s): %s:%d: "
|
|
"inode #%lu: comm %s: %pV\n",
|
|
inode->i_sb->s_id, function, line, inode->i_ino,
|
|
current->comm, &vaf);
|
|
va_end(args);
|
|
}
|
|
fsnotify_sb_error(inode->i_sb, inode, error ? error : EFSCORRUPTED);
|
|
|
|
ext4_handle_error(inode->i_sb, false, error, inode->i_ino, block,
|
|
function, line);
|
|
}
|
|
|
|
void __ext4_error_file(struct file *file, const char *function,
|
|
unsigned int line, ext4_fsblk_t block,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
struct va_format vaf;
|
|
struct inode *inode = file_inode(file);
|
|
char pathname[80], *path;
|
|
|
|
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
|
|
return;
|
|
|
|
trace_ext4_error(inode->i_sb, function, line);
|
|
if (ext4_error_ratelimit(inode->i_sb)) {
|
|
path = file_path(file, pathname, sizeof(pathname));
|
|
if (IS_ERR(path))
|
|
path = "(unknown)";
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
if (block)
|
|
printk(KERN_CRIT
|
|
"EXT4-fs error (device %s): %s:%d: inode #%lu: "
|
|
"block %llu: comm %s: path %s: %pV\n",
|
|
inode->i_sb->s_id, function, line, inode->i_ino,
|
|
block, current->comm, path, &vaf);
|
|
else
|
|
printk(KERN_CRIT
|
|
"EXT4-fs error (device %s): %s:%d: inode #%lu: "
|
|
"comm %s: path %s: %pV\n",
|
|
inode->i_sb->s_id, function, line, inode->i_ino,
|
|
current->comm, path, &vaf);
|
|
va_end(args);
|
|
}
|
|
fsnotify_sb_error(inode->i_sb, inode, EFSCORRUPTED);
|
|
|
|
ext4_handle_error(inode->i_sb, false, EFSCORRUPTED, inode->i_ino, block,
|
|
function, line);
|
|
}
|
|
|
|
const char *ext4_decode_error(struct super_block *sb, int errno,
|
|
char nbuf[16])
|
|
{
|
|
char *errstr = NULL;
|
|
|
|
switch (errno) {
|
|
case -EFSCORRUPTED:
|
|
errstr = "Corrupt filesystem";
|
|
break;
|
|
case -EFSBADCRC:
|
|
errstr = "Filesystem failed CRC";
|
|
break;
|
|
case -EIO:
|
|
errstr = "IO failure";
|
|
break;
|
|
case -ENOMEM:
|
|
errstr = "Out of memory";
|
|
break;
|
|
case -EROFS:
|
|
if (!sb || (EXT4_SB(sb)->s_journal &&
|
|
EXT4_SB(sb)->s_journal->j_flags & JBD2_ABORT))
|
|
errstr = "Journal has aborted";
|
|
else
|
|
errstr = "Readonly filesystem";
|
|
break;
|
|
default:
|
|
/* If the caller passed in an extra buffer for unknown
|
|
* errors, textualise them now. Else we just return
|
|
* NULL. */
|
|
if (nbuf) {
|
|
/* Check for truncated error codes... */
|
|
if (snprintf(nbuf, 16, "error %d", -errno) >= 0)
|
|
errstr = nbuf;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return errstr;
|
|
}
|
|
|
|
/* __ext4_std_error decodes expected errors from journaling functions
|
|
* automatically and invokes the appropriate error response. */
|
|
|
|
void __ext4_std_error(struct super_block *sb, const char *function,
|
|
unsigned int line, int errno)
|
|
{
|
|
char nbuf[16];
|
|
const char *errstr;
|
|
|
|
if (unlikely(ext4_forced_shutdown(EXT4_SB(sb))))
|
|
return;
|
|
|
|
/* Special case: if the error is EROFS, and we're not already
|
|
* inside a transaction, then there's really no point in logging
|
|
* an error. */
|
|
if (errno == -EROFS && journal_current_handle() == NULL && sb_rdonly(sb))
|
|
return;
|
|
|
|
if (ext4_error_ratelimit(sb)) {
|
|
errstr = ext4_decode_error(sb, errno, nbuf);
|
|
printk(KERN_CRIT "EXT4-fs error (device %s) in %s:%d: %s\n",
|
|
sb->s_id, function, line, errstr);
|
|
}
|
|
fsnotify_sb_error(sb, NULL, errno ? errno : EFSCORRUPTED);
|
|
|
|
ext4_handle_error(sb, false, -errno, 0, 0, function, line);
|
|
}
|
|
|
|
void __ext4_msg(struct super_block *sb,
|
|
const char *prefix, const char *fmt, ...)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
if (sb) {
|
|
atomic_inc(&EXT4_SB(sb)->s_msg_count);
|
|
if (!___ratelimit(&(EXT4_SB(sb)->s_msg_ratelimit_state),
|
|
"EXT4-fs"))
|
|
return;
|
|
}
|
|
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
if (sb)
|
|
printk("%sEXT4-fs (%s): %pV\n", prefix, sb->s_id, &vaf);
|
|
else
|
|
printk("%sEXT4-fs: %pV\n", prefix, &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
static int ext4_warning_ratelimit(struct super_block *sb)
|
|
{
|
|
atomic_inc(&EXT4_SB(sb)->s_warning_count);
|
|
return ___ratelimit(&(EXT4_SB(sb)->s_warning_ratelimit_state),
|
|
"EXT4-fs warning");
|
|
}
|
|
|
|
void __ext4_warning(struct super_block *sb, const char *function,
|
|
unsigned int line, const char *fmt, ...)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
if (!ext4_warning_ratelimit(sb))
|
|
return;
|
|
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
printk(KERN_WARNING "EXT4-fs warning (device %s): %s:%d: %pV\n",
|
|
sb->s_id, function, line, &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
void __ext4_warning_inode(const struct inode *inode, const char *function,
|
|
unsigned int line, const char *fmt, ...)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
if (!ext4_warning_ratelimit(inode->i_sb))
|
|
return;
|
|
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
printk(KERN_WARNING "EXT4-fs warning (device %s): %s:%d: "
|
|
"inode #%lu: comm %s: %pV\n", inode->i_sb->s_id,
|
|
function, line, inode->i_ino, current->comm, &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
void __ext4_grp_locked_error(const char *function, unsigned int line,
|
|
struct super_block *sb, ext4_group_t grp,
|
|
unsigned long ino, ext4_fsblk_t block,
|
|
const char *fmt, ...)
|
|
__releases(bitlock)
|
|
__acquires(bitlock)
|
|
{
|
|
struct va_format vaf;
|
|
va_list args;
|
|
|
|
if (unlikely(ext4_forced_shutdown(EXT4_SB(sb))))
|
|
return;
|
|
|
|
trace_ext4_error(sb, function, line);
|
|
if (ext4_error_ratelimit(sb)) {
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
printk(KERN_CRIT "EXT4-fs error (device %s): %s:%d: group %u, ",
|
|
sb->s_id, function, line, grp);
|
|
if (ino)
|
|
printk(KERN_CONT "inode %lu: ", ino);
|
|
if (block)
|
|
printk(KERN_CONT "block %llu:",
|
|
(unsigned long long) block);
|
|
printk(KERN_CONT "%pV\n", &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
if (test_opt(sb, ERRORS_CONT)) {
|
|
if (test_opt(sb, WARN_ON_ERROR))
|
|
WARN_ON_ONCE(1);
|
|
EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
|
|
if (!bdev_read_only(sb->s_bdev)) {
|
|
save_error_info(sb, EFSCORRUPTED, ino, block, function,
|
|
line);
|
|
schedule_work(&EXT4_SB(sb)->s_error_work);
|
|
}
|
|
return;
|
|
}
|
|
ext4_unlock_group(sb, grp);
|
|
ext4_handle_error(sb, false, EFSCORRUPTED, ino, block, function, line);
|
|
/*
|
|
* We only get here in the ERRORS_RO case; relocking the group
|
|
* may be dangerous, but nothing bad will happen since the
|
|
* filesystem will have already been marked read/only and the
|
|
* journal has been aborted. We return 1 as a hint to callers
|
|
* who might what to use the return value from
|
|
* ext4_grp_locked_error() to distinguish between the
|
|
* ERRORS_CONT and ERRORS_RO case, and perhaps return more
|
|
* aggressively from the ext4 function in question, with a
|
|
* more appropriate error code.
|
|
*/
|
|
ext4_lock_group(sb, grp);
|
|
return;
|
|
}
|
|
|
|
void ext4_mark_group_bitmap_corrupted(struct super_block *sb,
|
|
ext4_group_t group,
|
|
unsigned int flags)
|
|
{
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
struct ext4_group_info *grp = ext4_get_group_info(sb, group);
|
|
struct ext4_group_desc *gdp = ext4_get_group_desc(sb, group, NULL);
|
|
int ret;
|
|
|
|
if (!grp || !gdp)
|
|
return;
|
|
if (flags & EXT4_GROUP_INFO_BBITMAP_CORRUPT) {
|
|
ret = ext4_test_and_set_bit(EXT4_GROUP_INFO_BBITMAP_CORRUPT_BIT,
|
|
&grp->bb_state);
|
|
if (!ret)
|
|
percpu_counter_sub(&sbi->s_freeclusters_counter,
|
|
grp->bb_free);
|
|
}
|
|
|
|
if (flags & EXT4_GROUP_INFO_IBITMAP_CORRUPT) {
|
|
ret = ext4_test_and_set_bit(EXT4_GROUP_INFO_IBITMAP_CORRUPT_BIT,
|
|
&grp->bb_state);
|
|
if (!ret && gdp) {
|
|
int count;
|
|
|
|
count = ext4_free_inodes_count(sb, gdp);
|
|
percpu_counter_sub(&sbi->s_freeinodes_counter,
|
|
count);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ext4_update_dynamic_rev(struct super_block *sb)
|
|
{
|
|
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
|
|
|
|
if (le32_to_cpu(es->s_rev_level) > EXT4_GOOD_OLD_REV)
|
|
return;
|
|
|
|
ext4_warning(sb,
|
|
"updating to rev %d because of new feature flag, "
|
|
"running e2fsck is recommended",
|
|
EXT4_DYNAMIC_REV);
|
|
|
|
es->s_first_ino = cpu_to_le32(EXT4_GOOD_OLD_FIRST_INO);
|
|
es->s_inode_size = cpu_to_le16(EXT4_GOOD_OLD_INODE_SIZE);
|
|
es->s_rev_level = cpu_to_le32(EXT4_DYNAMIC_REV);
|
|
/* leave es->s_feature_*compat flags alone */
|
|
/* es->s_uuid will be set by e2fsck if empty */
|
|
|
|
/*
|
|
* The rest of the superblock fields should be zero, and if not it
|
|
* means they are likely already in use, so leave them alone. We
|
|
* can leave it up to e2fsck to clean up any inconsistencies there.
|
|
*/
|
|
}
|
|
|
|
static void ext4_bdev_mark_dead(struct block_device *bdev)
|
|
{
|
|
ext4_force_shutdown(bdev->bd_holder, EXT4_GOING_FLAGS_NOLOGFLUSH);
|
|
}
|
|
|
|
static const struct blk_holder_ops ext4_holder_ops = {
|
|
.mark_dead = ext4_bdev_mark_dead,
|
|
};
|
|
|
|
/*
|
|
* Open the external journal device
|
|
*/
|
|
static struct block_device *ext4_blkdev_get(dev_t dev, struct super_block *sb)
|
|
{
|
|
struct block_device *bdev;
|
|
|
|
bdev = blkdev_get_by_dev(dev, BLK_OPEN_READ | BLK_OPEN_WRITE, sb,
|
|
&ext4_holder_ops);
|
|
if (IS_ERR(bdev))
|
|
goto fail;
|
|
return bdev;
|
|
|
|
fail:
|
|
ext4_msg(sb, KERN_ERR,
|
|
"failed to open journal device unknown-block(%u,%u) %ld",
|
|
MAJOR(dev), MINOR(dev), PTR_ERR(bdev));
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Release the journal device
|
|
*/
|
|
static void ext4_blkdev_remove(struct ext4_sb_info *sbi)
|
|
{
|
|
struct block_device *bdev;
|
|
bdev = sbi->s_journal_bdev;
|
|
if (bdev) {
|
|
/*
|
|
* Invalidate the journal device's buffers. We don't want them
|
|
* floating about in memory - the physical journal device may
|
|
* hotswapped, and it breaks the `ro-after' testing code.
|
|
*/
|
|
invalidate_bdev(bdev);
|
|
blkdev_put(bdev, sbi->s_sb);
|
|
sbi->s_journal_bdev = NULL;
|
|
}
|
|
}
|
|
|
|
static inline struct inode *orphan_list_entry(struct list_head *l)
|
|
{
|
|
return &list_entry(l, struct ext4_inode_info, i_orphan)->vfs_inode;
|
|
}
|
|
|
|
static void dump_orphan_list(struct super_block *sb, struct ext4_sb_info *sbi)
|
|
{
|
|
struct list_head *l;
|
|
|
|
ext4_msg(sb, KERN_ERR, "sb orphan head is %d",
|
|
le32_to_cpu(sbi->s_es->s_last_orphan));
|
|
|
|
printk(KERN_ERR "sb_info orphan list:\n");
|
|
list_for_each(l, &sbi->s_orphan) {
|
|
struct inode *inode = orphan_list_entry(l);
|
|
printk(KERN_ERR " "
|
|
"inode %s:%lu at %p: mode %o, nlink %d, next %d\n",
|
|
inode->i_sb->s_id, inode->i_ino, inode,
|
|
inode->i_mode, inode->i_nlink,
|
|
NEXT_ORPHAN(inode));
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_QUOTA
|
|
static int ext4_quota_off(struct super_block *sb, int type);
|
|
|
|
static inline void ext4_quotas_off(struct super_block *sb, int type)
|
|
{
|
|
BUG_ON(type > EXT4_MAXQUOTAS);
|
|
|
|
/* Use our quota_off function to clear inode flags etc. */
|
|
for (type--; type >= 0; type--)
|
|
ext4_quota_off(sb, type);
|
|
}
|
|
|
|
/*
|
|
* This is a helper function which is used in the mount/remount
|
|
* codepaths (which holds s_umount) to fetch the quota file name.
|
|
*/
|
|
static inline char *get_qf_name(struct super_block *sb,
|
|
struct ext4_sb_info *sbi,
|
|
int type)
|
|
{
|
|
return rcu_dereference_protected(sbi->s_qf_names[type],
|
|
lockdep_is_held(&sb->s_umount));
|
|
}
|
|
#else
|
|
static inline void ext4_quotas_off(struct super_block *sb, int type)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int ext4_percpu_param_init(struct ext4_sb_info *sbi)
|
|
{
|
|
ext4_fsblk_t block;
|
|
int err;
|
|
|
|
block = ext4_count_free_clusters(sbi->s_sb);
|
|
ext4_free_blocks_count_set(sbi->s_es, EXT4_C2B(sbi, block));
|
|
err = percpu_counter_init(&sbi->s_freeclusters_counter, block,
|
|
GFP_KERNEL);
|
|
if (!err) {
|
|
unsigned long freei = ext4_count_free_inodes(sbi->s_sb);
|
|
sbi->s_es->s_free_inodes_count = cpu_to_le32(freei);
|
|
err = percpu_counter_init(&sbi->s_freeinodes_counter, freei,
|
|
GFP_KERNEL);
|
|
}
|
|
if (!err)
|
|
err = percpu_counter_init(&sbi->s_dirs_counter,
|
|
ext4_count_dirs(sbi->s_sb), GFP_KERNEL);
|
|
if (!err)
|
|
err = percpu_counter_init(&sbi->s_dirtyclusters_counter, 0,
|
|
GFP_KERNEL);
|
|
if (!err)
|
|
err = percpu_counter_init(&sbi->s_sra_exceeded_retry_limit, 0,
|
|
GFP_KERNEL);
|
|
if (!err)
|
|
err = percpu_init_rwsem(&sbi->s_writepages_rwsem);
|
|
|
|
if (err)
|
|
ext4_msg(sbi->s_sb, KERN_ERR, "insufficient memory");
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ext4_percpu_param_destroy(struct ext4_sb_info *sbi)
|
|
{
|
|
percpu_counter_destroy(&sbi->s_freeclusters_counter);
|
|
percpu_counter_destroy(&sbi->s_freeinodes_counter);
|
|
percpu_counter_destroy(&sbi->s_dirs_counter);
|
|
percpu_counter_destroy(&sbi->s_dirtyclusters_counter);
|
|
percpu_counter_destroy(&sbi->s_sra_exceeded_retry_limit);
|
|
percpu_free_rwsem(&sbi->s_writepages_rwsem);
|
|
}
|
|
|
|
static void ext4_group_desc_free(struct ext4_sb_info *sbi)
|
|
{
|
|
struct buffer_head **group_desc;
|
|
int i;
|
|
|
|
rcu_read_lock();
|
|
group_desc = rcu_dereference(sbi->s_group_desc);
|
|
for (i = 0; i < sbi->s_gdb_count; i++)
|
|
brelse(group_desc[i]);
|
|
kvfree(group_desc);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static void ext4_flex_groups_free(struct ext4_sb_info *sbi)
|
|
{
|
|
struct flex_groups **flex_groups;
|
|
int i;
|
|
|
|
rcu_read_lock();
|
|
flex_groups = rcu_dereference(sbi->s_flex_groups);
|
|
if (flex_groups) {
|
|
for (i = 0; i < sbi->s_flex_groups_allocated; i++)
|
|
kvfree(flex_groups[i]);
|
|
kvfree(flex_groups);
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static void ext4_put_super(struct super_block *sb)
|
|
{
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
struct ext4_super_block *es = sbi->s_es;
|
|
int aborted = 0;
|
|
int err;
|
|
|
|
/*
|
|
* Unregister sysfs before destroying jbd2 journal.
|
|
* Since we could still access attr_journal_task attribute via sysfs
|
|
* path which could have sbi->s_journal->j_task as NULL
|
|
* Unregister sysfs before flush sbi->s_error_work.
|
|
* Since user may read /proc/fs/ext4/xx/mb_groups during umount, If
|
|
* read metadata verify failed then will queue error work.
|
|
* flush_stashed_error_work will call start_this_handle may trigger
|
|
* BUG_ON.
|
|
*/
|
|
ext4_unregister_sysfs(sb);
|
|
|
|
if (___ratelimit(&ext4_mount_msg_ratelimit, "EXT4-fs unmount"))
|
|
ext4_msg(sb, KERN_INFO, "unmounting filesystem %pU.",
|
|
&sb->s_uuid);
|
|
|
|
ext4_unregister_li_request(sb);
|
|
ext4_quotas_off(sb, EXT4_MAXQUOTAS);
|
|
|
|
flush_work(&sbi->s_error_work);
|
|
destroy_workqueue(sbi->rsv_conversion_wq);
|
|
ext4_release_orphan_info(sb);
|
|
|
|
if (sbi->s_journal) {
|
|
aborted = is_journal_aborted(sbi->s_journal);
|
|
err = jbd2_journal_destroy(sbi->s_journal);
|
|
sbi->s_journal = NULL;
|
|
if ((err < 0) && !aborted) {
|
|
ext4_abort(sb, -err, "Couldn't clean up the journal");
|
|
}
|
|
}
|
|
|
|
ext4_es_unregister_shrinker(sbi);
|
|
timer_shutdown_sync(&sbi->s_err_report);
|
|
ext4_release_system_zone(sb);
|
|
ext4_mb_release(sb);
|
|
ext4_ext_release(sb);
|
|
|
|
if (!sb_rdonly(sb) && !aborted) {
|
|
ext4_clear_feature_journal_needs_recovery(sb);
|
|
ext4_clear_feature_orphan_present(sb);
|
|
es->s_state = cpu_to_le16(sbi->s_mount_state);
|
|
}
|
|
if (!sb_rdonly(sb))
|
|
ext4_commit_super(sb);
|
|
|
|
ext4_group_desc_free(sbi);
|
|
ext4_flex_groups_free(sbi);
|
|
ext4_percpu_param_destroy(sbi);
|
|
#ifdef CONFIG_QUOTA
|
|
for (int i = 0; i < EXT4_MAXQUOTAS; i++)
|
|
kfree(get_qf_name(sb, sbi, i));
|
|
#endif
|
|
|
|
/* Debugging code just in case the in-memory inode orphan list
|
|
* isn't empty. The on-disk one can be non-empty if we've
|
|
* detected an error and taken the fs readonly, but the
|
|
* in-memory list had better be clean by this point. */
|
|
if (!list_empty(&sbi->s_orphan))
|
|
dump_orphan_list(sb, sbi);
|
|
ASSERT(list_empty(&sbi->s_orphan));
|
|
|
|
sync_blockdev(sb->s_bdev);
|
|
invalidate_bdev(sb->s_bdev);
|
|
if (sbi->s_journal_bdev) {
|
|
sync_blockdev(sbi->s_journal_bdev);
|
|
ext4_blkdev_remove(sbi);
|
|
}
|
|
|
|
ext4_xattr_destroy_cache(sbi->s_ea_inode_cache);
|
|
sbi->s_ea_inode_cache = NULL;
|
|
|
|
ext4_xattr_destroy_cache(sbi->s_ea_block_cache);
|
|
sbi->s_ea_block_cache = NULL;
|
|
|
|
ext4_stop_mmpd(sbi);
|
|
|
|
brelse(sbi->s_sbh);
|
|
sb->s_fs_info = NULL;
|
|
/*
|
|
* Now that we are completely done shutting down the
|
|
* superblock, we need to actually destroy the kobject.
|
|
*/
|
|
kobject_put(&sbi->s_kobj);
|
|
wait_for_completion(&sbi->s_kobj_unregister);
|
|
if (sbi->s_chksum_driver)
|
|
crypto_free_shash(sbi->s_chksum_driver);
|
|
kfree(sbi->s_blockgroup_lock);
|
|
fs_put_dax(sbi->s_daxdev, NULL);
|
|
fscrypt_free_dummy_policy(&sbi->s_dummy_enc_policy);
|
|
#if IS_ENABLED(CONFIG_UNICODE)
|
|
utf8_unload(sb->s_encoding);
|
|
#endif
|
|
kfree(sbi);
|
|
}
|
|
|
|
static struct kmem_cache *ext4_inode_cachep;
|
|
|
|
/*
|
|
* Called inside transaction, so use GFP_NOFS
|
|
*/
|
|
static struct inode *ext4_alloc_inode(struct super_block *sb)
|
|
{
|
|
struct ext4_inode_info *ei;
|
|
|
|
ei = alloc_inode_sb(sb, ext4_inode_cachep, GFP_NOFS);
|
|
if (!ei)
|
|
return NULL;
|
|
|
|
inode_set_iversion(&ei->vfs_inode, 1);
|
|
ei->i_flags = 0;
|
|
spin_lock_init(&ei->i_raw_lock);
|
|
ei->i_prealloc_node = RB_ROOT;
|
|
atomic_set(&ei->i_prealloc_active, 0);
|
|
rwlock_init(&ei->i_prealloc_lock);
|
|
ext4_es_init_tree(&ei->i_es_tree);
|
|
rwlock_init(&ei->i_es_lock);
|
|
INIT_LIST_HEAD(&ei->i_es_list);
|
|
ei->i_es_all_nr = 0;
|
|
ei->i_es_shk_nr = 0;
|
|
ei->i_es_shrink_lblk = 0;
|
|
ei->i_reserved_data_blocks = 0;
|
|
spin_lock_init(&(ei->i_block_reservation_lock));
|
|
ext4_init_pending_tree(&ei->i_pending_tree);
|
|
#ifdef CONFIG_QUOTA
|
|
ei->i_reserved_quota = 0;
|
|
memset(&ei->i_dquot, 0, sizeof(ei->i_dquot));
|
|
#endif
|
|
ei->jinode = NULL;
|
|
INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
|
|
spin_lock_init(&ei->i_completed_io_lock);
|
|
ei->i_sync_tid = 0;
|
|
ei->i_datasync_tid = 0;
|
|
atomic_set(&ei->i_unwritten, 0);
|
|
INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
|
|
ext4_fc_init_inode(&ei->vfs_inode);
|
|
mutex_init(&ei->i_fc_lock);
|
|
return &ei->vfs_inode;
|
|
}
|
|
|
|
static int ext4_drop_inode(struct inode *inode)
|
|
{
|
|
int drop = generic_drop_inode(inode);
|
|
|
|
if (!drop)
|
|
drop = fscrypt_drop_inode(inode);
|
|
|
|
trace_ext4_drop_inode(inode, drop);
|
|
return drop;
|
|
}
|
|
|
|
static void ext4_free_in_core_inode(struct inode *inode)
|
|
{
|
|
fscrypt_free_inode(inode);
|
|
if (!list_empty(&(EXT4_I(inode)->i_fc_list))) {
|
|
pr_warn("%s: inode %ld still in fc list",
|
|
__func__, inode->i_ino);
|
|
}
|
|
kmem_cache_free(ext4_inode_cachep, EXT4_I(inode));
|
|
}
|
|
|
|
static void ext4_destroy_inode(struct inode *inode)
|
|
{
|
|
if (!list_empty(&(EXT4_I(inode)->i_orphan))) {
|
|
ext4_msg(inode->i_sb, KERN_ERR,
|
|
"Inode %lu (%p): orphan list check failed!",
|
|
inode->i_ino, EXT4_I(inode));
|
|
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 16, 4,
|
|
EXT4_I(inode), sizeof(struct ext4_inode_info),
|
|
true);
|
|
dump_stack();
|
|
}
|
|
|
|
if (EXT4_I(inode)->i_reserved_data_blocks)
|
|
ext4_msg(inode->i_sb, KERN_ERR,
|
|
"Inode %lu (%p): i_reserved_data_blocks (%u) not cleared!",
|
|
inode->i_ino, EXT4_I(inode),
|
|
EXT4_I(inode)->i_reserved_data_blocks);
|
|
}
|
|
|
|
static void ext4_shutdown(struct super_block *sb)
|
|
{
|
|
ext4_force_shutdown(sb, EXT4_GOING_FLAGS_NOLOGFLUSH);
|
|
}
|
|
|
|
static void init_once(void *foo)
|
|
{
|
|
struct ext4_inode_info *ei = foo;
|
|
|
|
INIT_LIST_HEAD(&ei->i_orphan);
|
|
init_rwsem(&ei->xattr_sem);
|
|
init_rwsem(&ei->i_data_sem);
|
|
inode_init_once(&ei->vfs_inode);
|
|
ext4_fc_init_inode(&ei->vfs_inode);
|
|
}
|
|
|
|
static int __init init_inodecache(void)
|
|
{
|
|
ext4_inode_cachep = kmem_cache_create_usercopy("ext4_inode_cache",
|
|
sizeof(struct ext4_inode_info), 0,
|
|
(SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD|
|
|
SLAB_ACCOUNT),
|
|
offsetof(struct ext4_inode_info, i_data),
|
|
sizeof_field(struct ext4_inode_info, i_data),
|
|
init_once);
|
|
if (ext4_inode_cachep == NULL)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
static void destroy_inodecache(void)
|
|
{
|
|
/*
|
|
* Make sure all delayed rcu free inodes are flushed before we
|
|
* destroy cache.
|
|
*/
|
|
rcu_barrier();
|
|
kmem_cache_destroy(ext4_inode_cachep);
|
|
}
|
|
|
|
void ext4_clear_inode(struct inode *inode)
|
|
{
|
|
ext4_fc_del(inode);
|
|
invalidate_inode_buffers(inode);
|
|
clear_inode(inode);
|
|
ext4_discard_preallocations(inode, 0);
|
|
ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
|
|
dquot_drop(inode);
|
|
if (EXT4_I(inode)->jinode) {
|
|
jbd2_journal_release_jbd_inode(EXT4_JOURNAL(inode),
|
|
EXT4_I(inode)->jinode);
|
|
jbd2_free_inode(EXT4_I(inode)->jinode);
|
|
EXT4_I(inode)->jinode = NULL;
|
|
}
|
|
fscrypt_put_encryption_info(inode);
|
|
fsverity_cleanup_inode(inode);
|
|
}
|
|
|
|
static struct inode *ext4_nfs_get_inode(struct super_block *sb,
|
|
u64 ino, u32 generation)
|
|
{
|
|
struct inode *inode;
|
|
|
|
/*
|
|
* Currently we don't know the generation for parent directory, so
|
|
* a generation of 0 means "accept any"
|
|
*/
|
|
inode = ext4_iget(sb, ino, EXT4_IGET_HANDLE);
|
|
if (IS_ERR(inode))
|
|
return ERR_CAST(inode);
|
|
if (generation && inode->i_generation != generation) {
|
|
iput(inode);
|
|
return ERR_PTR(-ESTALE);
|
|
}
|
|
|
|
return inode;
|
|
}
|
|
|
|
static struct dentry *ext4_fh_to_dentry(struct super_block *sb, struct fid *fid,
|
|
int fh_len, int fh_type)
|
|
{
|
|
return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
|
|
ext4_nfs_get_inode);
|
|
}
|
|
|
|
static struct dentry *ext4_fh_to_parent(struct super_block *sb, struct fid *fid,
|
|
int fh_len, int fh_type)
|
|
{
|
|
return generic_fh_to_parent(sb, fid, fh_len, fh_type,
|
|
ext4_nfs_get_inode);
|
|
}
|
|
|
|
static int ext4_nfs_commit_metadata(struct inode *inode)
|
|
{
|
|
struct writeback_control wbc = {
|
|
.sync_mode = WB_SYNC_ALL
|
|
};
|
|
|
|
trace_ext4_nfs_commit_metadata(inode);
|
|
return ext4_write_inode(inode, &wbc);
|
|
}
|
|
|
|
#ifdef CONFIG_QUOTA
|
|
static const char * const quotatypes[] = INITQFNAMES;
|
|
#define QTYPE2NAME(t) (quotatypes[t])
|
|
|
|
static int ext4_write_dquot(struct dquot *dquot);
|
|
static int ext4_acquire_dquot(struct dquot *dquot);
|
|
static int ext4_release_dquot(struct dquot *dquot);
|
|
static int ext4_mark_dquot_dirty(struct dquot *dquot);
|
|
static int ext4_write_info(struct super_block *sb, int type);
|
|
static int ext4_quota_on(struct super_block *sb, int type, int format_id,
|
|
const struct path *path);
|
|
static ssize_t ext4_quota_read(struct super_block *sb, int type, char *data,
|
|
size_t len, loff_t off);
|
|
static ssize_t ext4_quota_write(struct super_block *sb, int type,
|
|
const char *data, size_t len, loff_t off);
|
|
static int ext4_quota_enable(struct super_block *sb, int type, int format_id,
|
|
unsigned int flags);
|
|
|
|
static struct dquot **ext4_get_dquots(struct inode *inode)
|
|
{
|
|
return EXT4_I(inode)->i_dquot;
|
|
}
|
|
|
|
static const struct dquot_operations ext4_quota_operations = {
|
|
.get_reserved_space = ext4_get_reserved_space,
|
|
.write_dquot = ext4_write_dquot,
|
|
.acquire_dquot = ext4_acquire_dquot,
|
|
.release_dquot = ext4_release_dquot,
|
|
.mark_dirty = ext4_mark_dquot_dirty,
|
|
.write_info = ext4_write_info,
|
|
.alloc_dquot = dquot_alloc,
|
|
.destroy_dquot = dquot_destroy,
|
|
.get_projid = ext4_get_projid,
|
|
.get_inode_usage = ext4_get_inode_usage,
|
|
.get_next_id = dquot_get_next_id,
|
|
};
|
|
|
|
static const struct quotactl_ops ext4_qctl_operations = {
|
|
.quota_on = ext4_quota_on,
|
|
.quota_off = ext4_quota_off,
|
|
.quota_sync = dquot_quota_sync,
|
|
.get_state = dquot_get_state,
|
|
.set_info = dquot_set_dqinfo,
|
|
.get_dqblk = dquot_get_dqblk,
|
|
.set_dqblk = dquot_set_dqblk,
|
|
.get_nextdqblk = dquot_get_next_dqblk,
|
|
};
|
|
#endif
|
|
|
|
static const struct super_operations ext4_sops = {
|
|
.alloc_inode = ext4_alloc_inode,
|
|
.free_inode = ext4_free_in_core_inode,
|
|
.destroy_inode = ext4_destroy_inode,
|
|
.write_inode = ext4_write_inode,
|
|
.dirty_inode = ext4_dirty_inode,
|
|
.drop_inode = ext4_drop_inode,
|
|
.evict_inode = ext4_evict_inode,
|
|
.put_super = ext4_put_super,
|
|
.sync_fs = ext4_sync_fs,
|
|
.freeze_fs = ext4_freeze,
|
|
.unfreeze_fs = ext4_unfreeze,
|
|
.statfs = ext4_statfs,
|
|
.show_options = ext4_show_options,
|
|
.shutdown = ext4_shutdown,
|
|
#ifdef CONFIG_QUOTA
|
|
.quota_read = ext4_quota_read,
|
|
.quota_write = ext4_quota_write,
|
|
.get_dquots = ext4_get_dquots,
|
|
#endif
|
|
};
|
|
|
|
static const struct export_operations ext4_export_ops = {
|
|
.fh_to_dentry = ext4_fh_to_dentry,
|
|
.fh_to_parent = ext4_fh_to_parent,
|
|
.get_parent = ext4_get_parent,
|
|
.commit_metadata = ext4_nfs_commit_metadata,
|
|
};
|
|
|
|
enum {
|
|
Opt_bsd_df, Opt_minix_df, Opt_grpid, Opt_nogrpid,
|
|
Opt_resgid, Opt_resuid, Opt_sb,
|
|
Opt_nouid32, Opt_debug, Opt_removed,
|
|
Opt_user_xattr, Opt_acl,
|
|
Opt_auto_da_alloc, Opt_noauto_da_alloc, Opt_noload,
|
|
Opt_commit, Opt_min_batch_time, Opt_max_batch_time, Opt_journal_dev,
|
|
Opt_journal_path, Opt_journal_checksum, Opt_journal_async_commit,
|
|
Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
|
|
Opt_data_err_abort, Opt_data_err_ignore, Opt_test_dummy_encryption,
|
|
Opt_inlinecrypt,
|
|
Opt_usrjquota, Opt_grpjquota, Opt_quota,
|
|
Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err,
|
|
Opt_usrquota, Opt_grpquota, Opt_prjquota,
|
|
Opt_dax, Opt_dax_always, Opt_dax_inode, Opt_dax_never,
|
|
Opt_stripe, Opt_delalloc, Opt_nodelalloc, Opt_warn_on_error,
|
|
Opt_nowarn_on_error, Opt_mblk_io_submit, Opt_debug_want_extra_isize,
|
|
Opt_nomblk_io_submit, Opt_block_validity, Opt_noblock_validity,
|
|
Opt_inode_readahead_blks, Opt_journal_ioprio,
|
|
Opt_dioread_nolock, Opt_dioread_lock,
|
|
Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable,
|
|
Opt_max_dir_size_kb, Opt_nojournal_checksum, Opt_nombcache,
|
|
Opt_no_prefetch_block_bitmaps, Opt_mb_optimize_scan,
|
|
Opt_errors, Opt_data, Opt_data_err, Opt_jqfmt, Opt_dax_type,
|
|
#ifdef CONFIG_EXT4_DEBUG
|
|
Opt_fc_debug_max_replay, Opt_fc_debug_force
|
|
#endif
|
|
};
|
|
|
|
static const struct constant_table ext4_param_errors[] = {
|
|
{"continue", EXT4_MOUNT_ERRORS_CONT},
|
|
{"panic", EXT4_MOUNT_ERRORS_PANIC},
|
|
{"remount-ro", EXT4_MOUNT_ERRORS_RO},
|
|
{}
|
|
};
|
|
|
|
static const struct constant_table ext4_param_data[] = {
|
|
{"journal", EXT4_MOUNT_JOURNAL_DATA},
|
|
{"ordered", EXT4_MOUNT_ORDERED_DATA},
|
|
{"writeback", EXT4_MOUNT_WRITEBACK_DATA},
|
|
{}
|
|
};
|
|
|
|
static const struct constant_table ext4_param_data_err[] = {
|
|
{"abort", Opt_data_err_abort},
|
|
{"ignore", Opt_data_err_ignore},
|
|
{}
|
|
};
|
|
|
|
static const struct constant_table ext4_param_jqfmt[] = {
|
|
{"vfsold", QFMT_VFS_OLD},
|
|
{"vfsv0", QFMT_VFS_V0},
|
|
{"vfsv1", QFMT_VFS_V1},
|
|
{}
|
|
};
|
|
|
|
static const struct constant_table ext4_param_dax[] = {
|
|
{"always", Opt_dax_always},
|
|
{"inode", Opt_dax_inode},
|
|
{"never", Opt_dax_never},
|
|
{}
|
|
};
|
|
|
|
/* String parameter that allows empty argument */
|
|
#define fsparam_string_empty(NAME, OPT) \
|
|
__fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL)
|
|
|
|
/*
|
|
* Mount option specification
|
|
* We don't use fsparam_flag_no because of the way we set the
|
|
* options and the way we show them in _ext4_show_options(). To
|
|
* keep the changes to a minimum, let's keep the negative options
|
|
* separate for now.
|
|
*/
|
|
static const struct fs_parameter_spec ext4_param_specs[] = {
|
|
fsparam_flag ("bsddf", Opt_bsd_df),
|
|
fsparam_flag ("minixdf", Opt_minix_df),
|
|
fsparam_flag ("grpid", Opt_grpid),
|
|
fsparam_flag ("bsdgroups", Opt_grpid),
|
|
fsparam_flag ("nogrpid", Opt_nogrpid),
|
|
fsparam_flag ("sysvgroups", Opt_nogrpid),
|
|
fsparam_u32 ("resgid", Opt_resgid),
|
|
fsparam_u32 ("resuid", Opt_resuid),
|
|
fsparam_u32 ("sb", Opt_sb),
|
|
fsparam_enum ("errors", Opt_errors, ext4_param_errors),
|
|
fsparam_flag ("nouid32", Opt_nouid32),
|
|
fsparam_flag ("debug", Opt_debug),
|
|
fsparam_flag ("oldalloc", Opt_removed),
|
|
fsparam_flag ("orlov", Opt_removed),
|
|
fsparam_flag ("user_xattr", Opt_user_xattr),
|
|
fsparam_flag ("acl", Opt_acl),
|
|
fsparam_flag ("norecovery", Opt_noload),
|
|
fsparam_flag ("noload", Opt_noload),
|
|
fsparam_flag ("bh", Opt_removed),
|
|
fsparam_flag ("nobh", Opt_removed),
|
|
fsparam_u32 ("commit", Opt_commit),
|
|
fsparam_u32 ("min_batch_time", Opt_min_batch_time),
|
|
fsparam_u32 ("max_batch_time", Opt_max_batch_time),
|
|
fsparam_u32 ("journal_dev", Opt_journal_dev),
|
|
fsparam_bdev ("journal_path", Opt_journal_path),
|
|
fsparam_flag ("journal_checksum", Opt_journal_checksum),
|
|
fsparam_flag ("nojournal_checksum", Opt_nojournal_checksum),
|
|
fsparam_flag ("journal_async_commit",Opt_journal_async_commit),
|
|
fsparam_flag ("abort", Opt_abort),
|
|
fsparam_enum ("data", Opt_data, ext4_param_data),
|
|
fsparam_enum ("data_err", Opt_data_err,
|
|
ext4_param_data_err),
|
|
fsparam_string_empty
|
|
("usrjquota", Opt_usrjquota),
|
|
fsparam_string_empty
|
|
("grpjquota", Opt_grpjquota),
|
|
fsparam_enum ("jqfmt", Opt_jqfmt, ext4_param_jqfmt),
|
|
fsparam_flag ("grpquota", Opt_grpquota),
|
|
fsparam_flag ("quota", Opt_quota),
|
|
fsparam_flag ("noquota", Opt_noquota),
|
|
fsparam_flag ("usrquota", Opt_usrquota),
|
|
fsparam_flag ("prjquota", Opt_prjquota),
|
|
fsparam_flag ("barrier", Opt_barrier),
|
|
fsparam_u32 ("barrier", Opt_barrier),
|
|
fsparam_flag ("nobarrier", Opt_nobarrier),
|
|
fsparam_flag ("i_version", Opt_removed),
|
|
fsparam_flag ("dax", Opt_dax),
|
|
fsparam_enum ("dax", Opt_dax_type, ext4_param_dax),
|
|
fsparam_u32 ("stripe", Opt_stripe),
|
|
fsparam_flag ("delalloc", Opt_delalloc),
|
|
fsparam_flag ("nodelalloc", Opt_nodelalloc),
|
|
fsparam_flag ("warn_on_error", Opt_warn_on_error),
|
|
fsparam_flag ("nowarn_on_error", Opt_nowarn_on_error),
|
|
fsparam_u32 ("debug_want_extra_isize",
|
|
Opt_debug_want_extra_isize),
|
|
fsparam_flag ("mblk_io_submit", Opt_removed),
|
|
fsparam_flag ("nomblk_io_submit", Opt_removed),
|
|
fsparam_flag ("block_validity", Opt_block_validity),
|
|
fsparam_flag ("noblock_validity", Opt_noblock_validity),
|
|
fsparam_u32 ("inode_readahead_blks",
|
|
Opt_inode_readahead_blks),
|
|
fsparam_u32 ("journal_ioprio", Opt_journal_ioprio),
|
|
fsparam_u32 ("auto_da_alloc", Opt_auto_da_alloc),
|
|
fsparam_flag ("auto_da_alloc", Opt_auto_da_alloc),
|
|
fsparam_flag ("noauto_da_alloc", Opt_noauto_da_alloc),
|
|
fsparam_flag ("dioread_nolock", Opt_dioread_nolock),
|
|
fsparam_flag ("nodioread_nolock", Opt_dioread_lock),
|
|
fsparam_flag ("dioread_lock", Opt_dioread_lock),
|
|
fsparam_flag ("discard", Opt_discard),
|
|
fsparam_flag ("nodiscard", Opt_nodiscard),
|
|
fsparam_u32 ("init_itable", Opt_init_itable),
|
|
fsparam_flag ("init_itable", Opt_init_itable),
|
|
fsparam_flag ("noinit_itable", Opt_noinit_itable),
|
|
#ifdef CONFIG_EXT4_DEBUG
|
|
fsparam_flag ("fc_debug_force", Opt_fc_debug_force),
|
|
fsparam_u32 ("fc_debug_max_replay", Opt_fc_debug_max_replay),
|
|
#endif
|
|
fsparam_u32 ("max_dir_size_kb", Opt_max_dir_size_kb),
|
|
fsparam_flag ("test_dummy_encryption",
|
|
Opt_test_dummy_encryption),
|
|
fsparam_string ("test_dummy_encryption",
|
|
Opt_test_dummy_encryption),
|
|
fsparam_flag ("inlinecrypt", Opt_inlinecrypt),
|
|
fsparam_flag ("nombcache", Opt_nombcache),
|
|
fsparam_flag ("no_mbcache", Opt_nombcache), /* for backward compatibility */
|
|
fsparam_flag ("prefetch_block_bitmaps",
|
|
Opt_removed),
|
|
fsparam_flag ("no_prefetch_block_bitmaps",
|
|
Opt_no_prefetch_block_bitmaps),
|
|
fsparam_s32 ("mb_optimize_scan", Opt_mb_optimize_scan),
|
|
fsparam_string ("check", Opt_removed), /* mount option from ext2/3 */
|
|
fsparam_flag ("nocheck", Opt_removed), /* mount option from ext2/3 */
|
|
fsparam_flag ("reservation", Opt_removed), /* mount option from ext2/3 */
|
|
fsparam_flag ("noreservation", Opt_removed), /* mount option from ext2/3 */
|
|
fsparam_u32 ("journal", Opt_removed), /* mount option from ext2/3 */
|
|
{}
|
|
};
|
|
|
|
#define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3))
|
|
|
|
#define MOPT_SET 0x0001
|
|
#define MOPT_CLEAR 0x0002
|
|
#define MOPT_NOSUPPORT 0x0004
|
|
#define MOPT_EXPLICIT 0x0008
|
|
#ifdef CONFIG_QUOTA
|
|
#define MOPT_Q 0
|
|
#define MOPT_QFMT 0x0010
|
|
#else
|
|
#define MOPT_Q MOPT_NOSUPPORT
|
|
#define MOPT_QFMT MOPT_NOSUPPORT
|
|
#endif
|
|
#define MOPT_NO_EXT2 0x0020
|
|
#define MOPT_NO_EXT3 0x0040
|
|
#define MOPT_EXT4_ONLY (MOPT_NO_EXT2 | MOPT_NO_EXT3)
|
|
#define MOPT_SKIP 0x0080
|
|
#define MOPT_2 0x0100
|
|
|
|
static const struct mount_opts {
|
|
int token;
|
|
int mount_opt;
|
|
int flags;
|
|
} ext4_mount_opts[] = {
|
|
{Opt_minix_df, EXT4_MOUNT_MINIX_DF, MOPT_SET},
|
|
{Opt_bsd_df, EXT4_MOUNT_MINIX_DF, MOPT_CLEAR},
|
|
{Opt_grpid, EXT4_MOUNT_GRPID, MOPT_SET},
|
|
{Opt_nogrpid, EXT4_MOUNT_GRPID, MOPT_CLEAR},
|
|
{Opt_block_validity, EXT4_MOUNT_BLOCK_VALIDITY, MOPT_SET},
|
|
{Opt_noblock_validity, EXT4_MOUNT_BLOCK_VALIDITY, MOPT_CLEAR},
|
|
{Opt_dioread_nolock, EXT4_MOUNT_DIOREAD_NOLOCK,
|
|
MOPT_EXT4_ONLY | MOPT_SET},
|
|
{Opt_dioread_lock, EXT4_MOUNT_DIOREAD_NOLOCK,
|
|
MOPT_EXT4_ONLY | MOPT_CLEAR},
|
|
{Opt_discard, EXT4_MOUNT_DISCARD, MOPT_SET},
|
|
{Opt_nodiscard, EXT4_MOUNT_DISCARD, MOPT_CLEAR},
|
|
{Opt_delalloc, EXT4_MOUNT_DELALLOC,
|
|
MOPT_EXT4_ONLY | MOPT_SET | MOPT_EXPLICIT},
|
|
{Opt_nodelalloc, EXT4_MOUNT_DELALLOC,
|
|
MOPT_EXT4_ONLY | MOPT_CLEAR},
|
|
{Opt_warn_on_error, EXT4_MOUNT_WARN_ON_ERROR, MOPT_SET},
|
|
{Opt_nowarn_on_error, EXT4_MOUNT_WARN_ON_ERROR, MOPT_CLEAR},
|
|
{Opt_commit, 0, MOPT_NO_EXT2},
|
|
{Opt_nojournal_checksum, EXT4_MOUNT_JOURNAL_CHECKSUM,
|
|
MOPT_EXT4_ONLY | MOPT_CLEAR},
|
|
{Opt_journal_checksum, EXT4_MOUNT_JOURNAL_CHECKSUM,
|
|
MOPT_EXT4_ONLY | MOPT_SET | MOPT_EXPLICIT},
|
|
{Opt_journal_async_commit, (EXT4_MOUNT_JOURNAL_ASYNC_COMMIT |
|
|
EXT4_MOUNT_JOURNAL_CHECKSUM),
|
|
MOPT_EXT4_ONLY | MOPT_SET | MOPT_EXPLICIT},
|
|
{Opt_noload, EXT4_MOUNT_NOLOAD, MOPT_NO_EXT2 | MOPT_SET},
|
|
{Opt_data_err, EXT4_MOUNT_DATA_ERR_ABORT, MOPT_NO_EXT2},
|
|
{Opt_barrier, EXT4_MOUNT_BARRIER, MOPT_SET},
|
|
{Opt_nobarrier, EXT4_MOUNT_BARRIER, MOPT_CLEAR},
|
|
{Opt_noauto_da_alloc, EXT4_MOUNT_NO_AUTO_DA_ALLOC, MOPT_SET},
|
|
{Opt_auto_da_alloc, EXT4_MOUNT_NO_AUTO_DA_ALLOC, MOPT_CLEAR},
|
|
{Opt_noinit_itable, EXT4_MOUNT_INIT_INODE_TABLE, MOPT_CLEAR},
|
|
{Opt_dax_type, 0, MOPT_EXT4_ONLY},
|
|
{Opt_journal_dev, 0, MOPT_NO_EXT2},
|
|
{Opt_journal_path, 0, MOPT_NO_EXT2},
|
|
{Opt_journal_ioprio, 0, MOPT_NO_EXT2},
|
|
{Opt_data, 0, MOPT_NO_EXT2},
|
|
{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},
|
|
#ifdef CONFIG_EXT4_FS_POSIX_ACL
|
|
{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},
|
|
#else
|
|
{Opt_acl, 0, MOPT_NOSUPPORT},
|
|
#endif
|
|
{Opt_nouid32, EXT4_MOUNT_NO_UID32, MOPT_SET},
|
|
{Opt_debug, EXT4_MOUNT_DEBUG, MOPT_SET},
|
|
{Opt_quota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_USRQUOTA, MOPT_SET | MOPT_Q},
|
|
{Opt_usrquota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_USRQUOTA,
|
|
MOPT_SET | MOPT_Q},
|
|
{Opt_grpquota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_GRPQUOTA,
|
|
MOPT_SET | MOPT_Q},
|
|
{Opt_prjquota, EXT4_MOUNT_QUOTA | EXT4_MOUNT_PRJQUOTA,
|
|
MOPT_SET | MOPT_Q},
|
|
{Opt_noquota, (EXT4_MOUNT_QUOTA | EXT4_MOUNT_USRQUOTA |
|
|
EXT4_MOUNT_GRPQUOTA | EXT4_MOUNT_PRJQUOTA),
|
|
MOPT_CLEAR | MOPT_Q},
|
|
{Opt_usrjquota, 0, MOPT_Q},
|
|
{Opt_grpjquota, 0, MOPT_Q},
|
|
{Opt_jqfmt, 0, MOPT_QFMT},
|
|
{Opt_nombcache, EXT4_MOUNT_NO_MBCACHE, MOPT_SET},
|
|
{Opt_no_prefetch_block_bitmaps, EXT4_MOUNT_NO_PREFETCH_BLOCK_BITMAPS,
|
|
MOPT_SET},
|
|
#ifdef CONFIG_EXT4_DEBUG
|
|
{Opt_fc_debug_force, EXT4_MOUNT2_JOURNAL_FAST_COMMIT,
|
|
MOPT_SET | MOPT_2 | MOPT_EXT4_ONLY},
|
|
#endif
|
|
{Opt_err, 0, 0}
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_UNICODE)
|
|
static const struct ext4_sb_encodings {
|
|
__u16 magic;
|
|
char *name;
|
|
unsigned int version;
|
|
} ext4_sb_encoding_map[] = {
|
|
{EXT4_ENC_UTF8_12_1, "utf8", UNICODE_AGE(12, 1, 0)},
|
|
};
|
|
|
|
static const struct ext4_sb_encodings *
|
|
ext4_sb_read_encoding(const struct ext4_super_block *es)
|
|
{
|
|
__u16 magic = le16_to_cpu(es->s_encoding);
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ext4_sb_encoding_map); i++)
|
|
if (magic == ext4_sb_encoding_map[i].magic)
|
|
return &ext4_sb_encoding_map[i];
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#define EXT4_SPEC_JQUOTA (1 << 0)
|
|
#define EXT4_SPEC_JQFMT (1 << 1)
|
|
#define EXT4_SPEC_DATAJ (1 << 2)
|
|
#define EXT4_SPEC_SB_BLOCK (1 << 3)
|
|
#define EXT4_SPEC_JOURNAL_DEV (1 << 4)
|
|
#define EXT4_SPEC_JOURNAL_IOPRIO (1 << 5)
|
|
#define EXT4_SPEC_s_want_extra_isize (1 << 7)
|
|
#define EXT4_SPEC_s_max_batch_time (1 << 8)
|
|
#define EXT4_SPEC_s_min_batch_time (1 << 9)
|
|
#define EXT4_SPEC_s_inode_readahead_blks (1 << 10)
|
|
#define EXT4_SPEC_s_li_wait_mult (1 << 11)
|
|
#define EXT4_SPEC_s_max_dir_size_kb (1 << 12)
|
|
#define EXT4_SPEC_s_stripe (1 << 13)
|
|
#define EXT4_SPEC_s_resuid (1 << 14)
|
|
#define EXT4_SPEC_s_resgid (1 << 15)
|
|
#define EXT4_SPEC_s_commit_interval (1 << 16)
|
|
#define EXT4_SPEC_s_fc_debug_max_replay (1 << 17)
|
|
#define EXT4_SPEC_s_sb_block (1 << 18)
|
|
#define EXT4_SPEC_mb_optimize_scan (1 << 19)
|
|
|
|
struct ext4_fs_context {
|
|
char *s_qf_names[EXT4_MAXQUOTAS];
|
|
struct fscrypt_dummy_policy dummy_enc_policy;
|
|
int s_jquota_fmt; /* Format of quota to use */
|
|
#ifdef CONFIG_EXT4_DEBUG
|
|
int s_fc_debug_max_replay;
|
|
#endif
|
|
unsigned short qname_spec;
|
|
unsigned long vals_s_flags; /* Bits to set in s_flags */
|
|
unsigned long mask_s_flags; /* Bits changed in s_flags */
|
|
unsigned long journal_devnum;
|
|
unsigned long s_commit_interval;
|
|
unsigned long s_stripe;
|
|
unsigned int s_inode_readahead_blks;
|
|
unsigned int s_want_extra_isize;
|
|
unsigned int s_li_wait_mult;
|
|
unsigned int s_max_dir_size_kb;
|
|
unsigned int journal_ioprio;
|
|
unsigned int vals_s_mount_opt;
|
|
unsigned int mask_s_mount_opt;
|
|
unsigned int vals_s_mount_opt2;
|
|
unsigned int mask_s_mount_opt2;
|
|
unsigned long vals_s_mount_flags;
|
|
unsigned long mask_s_mount_flags;
|
|
unsigned int opt_flags; /* MOPT flags */
|
|
unsigned int spec;
|
|
u32 s_max_batch_time;
|
|
u32 s_min_batch_time;
|
|
kuid_t s_resuid;
|
|
kgid_t s_resgid;
|
|
ext4_fsblk_t s_sb_block;
|
|
};
|
|
|
|
static void ext4_fc_free(struct fs_context *fc)
|
|
{
|
|
struct ext4_fs_context *ctx = fc->fs_private;
|
|
int i;
|
|
|
|
if (!ctx)
|
|
return;
|
|
|
|
for (i = 0; i < EXT4_MAXQUOTAS; i++)
|
|
kfree(ctx->s_qf_names[i]);
|
|
|
|
fscrypt_free_dummy_policy(&ctx->dummy_enc_policy);
|
|
kfree(ctx);
|
|
}
|
|
|
|
int ext4_init_fs_context(struct fs_context *fc)
|
|
{
|
|
struct ext4_fs_context *ctx;
|
|
|
|
ctx = kzalloc(sizeof(struct ext4_fs_context), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
fc->fs_private = ctx;
|
|
fc->ops = &ext4_context_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_QUOTA
|
|
/*
|
|
* Note the name of the specified quota file.
|
|
*/
|
|
static int note_qf_name(struct fs_context *fc, int qtype,
|
|
struct fs_parameter *param)
|
|
{
|
|
struct ext4_fs_context *ctx = fc->fs_private;
|
|
char *qname;
|
|
|
|
if (param->size < 1) {
|
|
ext4_msg(NULL, KERN_ERR, "Missing quota name");
|
|
return -EINVAL;
|
|
}
|
|
if (strchr(param->string, '/')) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"quotafile must be on filesystem root");
|
|
return -EINVAL;
|
|
}
|
|
if (ctx->s_qf_names[qtype]) {
|
|
if (strcmp(ctx->s_qf_names[qtype], param->string) != 0) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"%s quota file already specified",
|
|
QTYPE2NAME(qtype));
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
qname = kmemdup_nul(param->string, param->size, GFP_KERNEL);
|
|
if (!qname) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"Not enough memory for storing quotafile name");
|
|
return -ENOMEM;
|
|
}
|
|
ctx->s_qf_names[qtype] = qname;
|
|
ctx->qname_spec |= 1 << qtype;
|
|
ctx->spec |= EXT4_SPEC_JQUOTA;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clear the name of the specified quota file.
|
|
*/
|
|
static int unnote_qf_name(struct fs_context *fc, int qtype)
|
|
{
|
|
struct ext4_fs_context *ctx = fc->fs_private;
|
|
|
|
if (ctx->s_qf_names[qtype])
|
|
kfree(ctx->s_qf_names[qtype]);
|
|
|
|
ctx->s_qf_names[qtype] = NULL;
|
|
ctx->qname_spec |= 1 << qtype;
|
|
ctx->spec |= EXT4_SPEC_JQUOTA;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int ext4_parse_test_dummy_encryption(const struct fs_parameter *param,
|
|
struct ext4_fs_context *ctx)
|
|
{
|
|
int err;
|
|
|
|
if (!IS_ENABLED(CONFIG_FS_ENCRYPTION)) {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"test_dummy_encryption option not supported");
|
|
return -EINVAL;
|
|
}
|
|
err = fscrypt_parse_test_dummy_encryption(param,
|
|
&ctx->dummy_enc_policy);
|
|
if (err == -EINVAL) {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"Value of option \"%s\" is unrecognized", param->key);
|
|
} else if (err == -EEXIST) {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"Conflicting test_dummy_encryption options");
|
|
return -EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#define EXT4_SET_CTX(name) \
|
|
static inline void ctx_set_##name(struct ext4_fs_context *ctx, \
|
|
unsigned long flag) \
|
|
{ \
|
|
ctx->mask_s_##name |= flag; \
|
|
ctx->vals_s_##name |= flag; \
|
|
}
|
|
|
|
#define EXT4_CLEAR_CTX(name) \
|
|
static inline void ctx_clear_##name(struct ext4_fs_context *ctx, \
|
|
unsigned long flag) \
|
|
{ \
|
|
ctx->mask_s_##name |= flag; \
|
|
ctx->vals_s_##name &= ~flag; \
|
|
}
|
|
|
|
#define EXT4_TEST_CTX(name) \
|
|
static inline unsigned long \
|
|
ctx_test_##name(struct ext4_fs_context *ctx, unsigned long flag) \
|
|
{ \
|
|
return (ctx->vals_s_##name & flag); \
|
|
}
|
|
|
|
EXT4_SET_CTX(flags); /* set only */
|
|
EXT4_SET_CTX(mount_opt);
|
|
EXT4_CLEAR_CTX(mount_opt);
|
|
EXT4_TEST_CTX(mount_opt);
|
|
EXT4_SET_CTX(mount_opt2);
|
|
EXT4_CLEAR_CTX(mount_opt2);
|
|
EXT4_TEST_CTX(mount_opt2);
|
|
|
|
static inline void ctx_set_mount_flag(struct ext4_fs_context *ctx, int bit)
|
|
{
|
|
set_bit(bit, &ctx->mask_s_mount_flags);
|
|
set_bit(bit, &ctx->vals_s_mount_flags);
|
|
}
|
|
|
|
static int ext4_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
|
{
|
|
struct ext4_fs_context *ctx = fc->fs_private;
|
|
struct fs_parse_result result;
|
|
const struct mount_opts *m;
|
|
int is_remount;
|
|
kuid_t uid;
|
|
kgid_t gid;
|
|
int token;
|
|
|
|
token = fs_parse(fc, ext4_param_specs, param, &result);
|
|
if (token < 0)
|
|
return token;
|
|
is_remount = fc->purpose == FS_CONTEXT_FOR_RECONFIGURE;
|
|
|
|
for (m = ext4_mount_opts; m->token != Opt_err; m++)
|
|
if (token == m->token)
|
|
break;
|
|
|
|
ctx->opt_flags |= m->flags;
|
|
|
|
if (m->flags & MOPT_EXPLICIT) {
|
|
if (m->mount_opt & EXT4_MOUNT_DELALLOC) {
|
|
ctx_set_mount_opt2(ctx, EXT4_MOUNT2_EXPLICIT_DELALLOC);
|
|
} else if (m->mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) {
|
|
ctx_set_mount_opt2(ctx,
|
|
EXT4_MOUNT2_EXPLICIT_JOURNAL_CHECKSUM);
|
|
} else
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (m->flags & MOPT_NOSUPPORT) {
|
|
ext4_msg(NULL, KERN_ERR, "%s option not supported",
|
|
param->key);
|
|
return 0;
|
|
}
|
|
|
|
switch (token) {
|
|
#ifdef CONFIG_QUOTA
|
|
case Opt_usrjquota:
|
|
if (!*param->string)
|
|
return unnote_qf_name(fc, USRQUOTA);
|
|
else
|
|
return note_qf_name(fc, USRQUOTA, param);
|
|
case Opt_grpjquota:
|
|
if (!*param->string)
|
|
return unnote_qf_name(fc, GRPQUOTA);
|
|
else
|
|
return note_qf_name(fc, GRPQUOTA, param);
|
|
#endif
|
|
case Opt_sb:
|
|
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"Ignoring %s option on remount", param->key);
|
|
} else {
|
|
ctx->s_sb_block = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_sb_block;
|
|
}
|
|
return 0;
|
|
case Opt_removed:
|
|
ext4_msg(NULL, KERN_WARNING, "Ignoring removed %s option",
|
|
param->key);
|
|
return 0;
|
|
case Opt_abort:
|
|
ctx_set_mount_flag(ctx, EXT4_MF_FS_ABORTED);
|
|
return 0;
|
|
case Opt_inlinecrypt:
|
|
#ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
|
|
ctx_set_flags(ctx, SB_INLINECRYPT);
|
|
#else
|
|
ext4_msg(NULL, KERN_ERR, "inline encryption not supported");
|
|
#endif
|
|
return 0;
|
|
case Opt_errors:
|
|
ctx_clear_mount_opt(ctx, EXT4_MOUNT_ERRORS_MASK);
|
|
ctx_set_mount_opt(ctx, result.uint_32);
|
|
return 0;
|
|
#ifdef CONFIG_QUOTA
|
|
case Opt_jqfmt:
|
|
ctx->s_jquota_fmt = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_JQFMT;
|
|
return 0;
|
|
#endif
|
|
case Opt_data:
|
|
ctx_clear_mount_opt(ctx, EXT4_MOUNT_DATA_FLAGS);
|
|
ctx_set_mount_opt(ctx, result.uint_32);
|
|
ctx->spec |= EXT4_SPEC_DATAJ;
|
|
return 0;
|
|
case Opt_commit:
|
|
if (result.uint_32 == 0)
|
|
result.uint_32 = JBD2_DEFAULT_MAX_COMMIT_AGE;
|
|
else if (result.uint_32 > INT_MAX / HZ) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"Invalid commit interval %d, "
|
|
"must be smaller than %d",
|
|
result.uint_32, INT_MAX / HZ);
|
|
return -EINVAL;
|
|
}
|
|
ctx->s_commit_interval = HZ * result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_commit_interval;
|
|
return 0;
|
|
case Opt_debug_want_extra_isize:
|
|
if ((result.uint_32 & 1) || (result.uint_32 < 4)) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"Invalid want_extra_isize %d", result.uint_32);
|
|
return -EINVAL;
|
|
}
|
|
ctx->s_want_extra_isize = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_want_extra_isize;
|
|
return 0;
|
|
case Opt_max_batch_time:
|
|
ctx->s_max_batch_time = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_max_batch_time;
|
|
return 0;
|
|
case Opt_min_batch_time:
|
|
ctx->s_min_batch_time = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_min_batch_time;
|
|
return 0;
|
|
case Opt_inode_readahead_blks:
|
|
if (result.uint_32 &&
|
|
(result.uint_32 > (1 << 30) ||
|
|
!is_power_of_2(result.uint_32))) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"EXT4-fs: inode_readahead_blks must be "
|
|
"0 or a power of 2 smaller than 2^31");
|
|
return -EINVAL;
|
|
}
|
|
ctx->s_inode_readahead_blks = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_inode_readahead_blks;
|
|
return 0;
|
|
case Opt_init_itable:
|
|
ctx_set_mount_opt(ctx, EXT4_MOUNT_INIT_INODE_TABLE);
|
|
ctx->s_li_wait_mult = EXT4_DEF_LI_WAIT_MULT;
|
|
if (param->type == fs_value_is_string)
|
|
ctx->s_li_wait_mult = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_li_wait_mult;
|
|
return 0;
|
|
case Opt_max_dir_size_kb:
|
|
ctx->s_max_dir_size_kb = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_max_dir_size_kb;
|
|
return 0;
|
|
#ifdef CONFIG_EXT4_DEBUG
|
|
case Opt_fc_debug_max_replay:
|
|
ctx->s_fc_debug_max_replay = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_fc_debug_max_replay;
|
|
return 0;
|
|
#endif
|
|
case Opt_stripe:
|
|
ctx->s_stripe = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_s_stripe;
|
|
return 0;
|
|
case Opt_resuid:
|
|
uid = make_kuid(current_user_ns(), result.uint_32);
|
|
if (!uid_valid(uid)) {
|
|
ext4_msg(NULL, KERN_ERR, "Invalid uid value %d",
|
|
result.uint_32);
|
|
return -EINVAL;
|
|
}
|
|
ctx->s_resuid = uid;
|
|
ctx->spec |= EXT4_SPEC_s_resuid;
|
|
return 0;
|
|
case Opt_resgid:
|
|
gid = make_kgid(current_user_ns(), result.uint_32);
|
|
if (!gid_valid(gid)) {
|
|
ext4_msg(NULL, KERN_ERR, "Invalid gid value %d",
|
|
result.uint_32);
|
|
return -EINVAL;
|
|
}
|
|
ctx->s_resgid = gid;
|
|
ctx->spec |= EXT4_SPEC_s_resgid;
|
|
return 0;
|
|
case Opt_journal_dev:
|
|
if (is_remount) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"Cannot specify journal on remount");
|
|
return -EINVAL;
|
|
}
|
|
ctx->journal_devnum = result.uint_32;
|
|
ctx->spec |= EXT4_SPEC_JOURNAL_DEV;
|
|
return 0;
|
|
case Opt_journal_path:
|
|
{
|
|
struct inode *journal_inode;
|
|
struct path path;
|
|
int error;
|
|
|
|
if (is_remount) {
|
|
ext4_msg(NULL, KERN_ERR,
|
|
"Cannot specify journal on remount");
|
|
return -EINVAL;
|
|
}
|
|
|
|
error = fs_lookup_param(fc, param, 1, LOOKUP_FOLLOW, &path);
|
|
if (error) {
|
|
ext4_msg(NULL, KERN_ERR, "error: could not find "
|
|
"journal device path");
|
|
return -EINVAL;
|
|
}
|
|
|
|
journal_inode = d_inode(path.dentry);
|
|
ctx->journal_devnum = new_encode_dev(journal_inode->i_rdev);
|
|
ctx->spec |= EXT4_SPEC_JOURNAL_DEV;
|
|
path_put(&path);
|
|
return 0;
|
|
}
|
|
case Opt_journal_ioprio:
|
|
if (result.uint_32 > 7) {
|
|
ext4_msg(NULL, KERN_ERR, "Invalid journal IO priority"
|
|
" (must be 0-7)");
|
|
return -EINVAL;
|
|
}
|
|
ctx->journal_ioprio =
|
|
IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, result.uint_32);
|
|
ctx->spec |= EXT4_SPEC_JOURNAL_IOPRIO;
|
|
return 0;
|
|
case Opt_test_dummy_encryption:
|
|
return ext4_parse_test_dummy_encryption(param, ctx);
|
|
case Opt_dax:
|
|
case Opt_dax_type:
|
|
#ifdef CONFIG_FS_DAX
|
|
{
|
|
int type = (token == Opt_dax) ?
|
|
Opt_dax : result.uint_32;
|
|
|
|
switch (type) {
|
|
case Opt_dax:
|
|
case Opt_dax_always:
|
|
ctx_set_mount_opt(ctx, EXT4_MOUNT_DAX_ALWAYS);
|
|
ctx_clear_mount_opt2(ctx, EXT4_MOUNT2_DAX_NEVER);
|
|
break;
|
|
case Opt_dax_never:
|
|
ctx_set_mount_opt2(ctx, EXT4_MOUNT2_DAX_NEVER);
|
|
ctx_clear_mount_opt(ctx, EXT4_MOUNT_DAX_ALWAYS);
|
|
break;
|
|
case Opt_dax_inode:
|
|
ctx_clear_mount_opt(ctx, EXT4_MOUNT_DAX_ALWAYS);
|
|
ctx_clear_mount_opt2(ctx, EXT4_MOUNT2_DAX_NEVER);
|
|
/* Strictly for printing options */
|
|
ctx_set_mount_opt2(ctx, EXT4_MOUNT2_DAX_INODE);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
ext4_msg(NULL, KERN_INFO, "dax option not supported");
|
|
return -EINVAL;
|
|
#endif
|
|
case Opt_data_err:
|
|
if (result.uint_32 == Opt_data_err_abort)
|
|
ctx_set_mount_opt(ctx, m->mount_opt);
|
|
else if (result.uint_32 == Opt_data_err_ignore)
|
|
ctx_clear_mount_opt(ctx, m->mount_opt);
|
|
return 0;
|
|
case Opt_mb_optimize_scan:
|
|
if (result.int_32 == 1) {
|
|
ctx_set_mount_opt2(ctx, EXT4_MOUNT2_MB_OPTIMIZE_SCAN);
|
|
ctx->spec |= EXT4_SPEC_mb_optimize_scan;
|
|
} else if (result.int_32 == 0) {
|
|
ctx_clear_mount_opt2(ctx, EXT4_MOUNT2_MB_OPTIMIZE_SCAN);
|
|
ctx->spec |= EXT4_SPEC_mb_optimize_scan;
|
|
} else {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"mb_optimize_scan should be set to 0 or 1.");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* At this point we should only be getting options requiring MOPT_SET,
|
|
* or MOPT_CLEAR. Anything else is a bug
|
|
*/
|
|
if (m->token == Opt_err) {
|
|
ext4_msg(NULL, KERN_WARNING, "buggy handling of option %s",
|
|
param->key);
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
else {
|
|
unsigned int set = 0;
|
|
|
|
if ((param->type == fs_value_is_flag) ||
|
|
result.uint_32 > 0)
|
|
set = 1;
|
|
|
|
if (m->flags & MOPT_CLEAR)
|
|
set = !set;
|
|
else if (unlikely(!(m->flags & MOPT_SET))) {
|
|
ext4_msg(NULL, KERN_WARNING,
|
|
"buggy handling of option %s",
|
|
param->key);
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
if (m->flags & MOPT_2) {
|
|
if (set != 0)
|
|
ctx_set_mount_opt2(ctx, m->mount_opt);
|
|
else
|
|
ctx_clear_mount_opt2(ctx, m->mount_opt);
|
|
} else {
|
|
if (set != 0)
|
|
ctx_set_mount_opt(ctx, m->mount_opt);
|
|
else
|
|
ctx_clear_mount_opt(ctx, m->mount_opt);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_options(struct fs_context *fc, char *options)
|
|
{
|
|
struct fs_parameter param;
|
|
int ret;
|
|
char *key;
|
|
|
|
if (!options)
|
|
return 0;
|
|
|
|
while ((key = strsep(&options, ",")) != NULL) {
|
|
if (*key) {
|
|
size_t v_len = 0;
|
|
char *value = strchr(key, '=');
|
|
|
|
param.type = fs_value_is_flag;
|
|
param.string = NULL;
|
|
|
|
if (value) {
|
|
if (value == key)
|
|
continue;
|
|
|
|
*value++ = 0;
|
|
v_len = strlen(value);
|
|
param.string = kmemdup_nul(value, v_len,
|
|
GFP_KERNEL);
|
|
if (!param.string)
|
|
return -ENOMEM;
|
|
param.type = fs_value_is_string;
|
|
}
|
|
|
|
param.key = key;
|
|
param.size = v_len;
|
|
|
|
ret = ext4_parse_param(fc, ¶m);
|
|
if (param.string)
|
|
kfree(param.string);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = ext4_validate_options(fc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_apply_sb_mount_options(struct super_block *sb,
|
|
struct ext4_fs_context *m_ctx)
|
|
{
|
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
|
char *s_mount_opts = NULL;
|
|
struct ext4_fs_context *s_ctx = NULL;
|
|
struct fs_context *fc = NULL;
|
|
int ret = -ENOMEM;
|
|
|
|
if (!sbi->s_es->s_mount_opts[0])
|
|
return 0;
|
|
|
|
s_mount_opts = kstrndup(sbi->s_es->s_mount_opts,
|
|
sizeof(sbi->s_es->s_mount_opts),
|
|
GFP_KERNEL);
|
|
if (!s_mount_opts)
|
|
return ret;
|
|
|
|
fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL);
|
|
if (!fc)
|
|
goto out_free;
|
|
|
|
s_ctx = kzalloc(sizeof(struct ext4_fs_context), GFP_KERNEL);
|
|
if (!s_ctx)
|
|
goto out_free;
|
|
|
|
fc->fs_private = s_ctx;
|
|
fc->s_fs_info = sbi;
|
|
|
|
ret = parse_options(fc, s_mount_opts);
|
|
if (ret < 0)
|
|
goto parse_failed;
|
|
|
|
ret = ext4_check_opt_consistency(fc, sb);
|
|
if (ret < 0) {
|
|
parse_failed:
|
|
ext4_msg(sb, KERN_WARNING,
|
|
"failed to parse options in superblock: %s",
|
|
s_mount_opts);
|
|
ret = 0;
|
|
goto out_free;
|
|
}
|
|
|
|
if (s_ctx->spec & EXT4_SPEC_JOURNAL_DEV)
|
|
m_ctx->journal_devnum = s_ctx->journal_devnum;
|
|
if (s_ctx->spec & EXT4_SPEC_JOURNAL_IOPRIO)
|
|
m_ctx->journal_ioprio = s_ctx->journal_ioprio;
|
|
|
|
ext4_apply_options(fc, sb);
|
|
ret = 0;
|
|
|
|
out_free:
|
|
if (fc) {
|
|
ext4_fc_free(fc);
|
|
kfree(fc);
|
|