Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
CONFIG_KUNIT=y
CONFIG_EXT4_FS=y
CONFIG_EXT4_KUNIT_TESTS=y
+92
View File
@@ -0,0 +1,92 @@
# SPDX-License-Identifier: GPL-2.0-only
config EXT4_FS
tristate "The Extended 4 (ext4) filesystem"
select BUFFER_HEAD
select JBD2
select CRC16
select CRC32
select FS_IOMAP
select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
help
This is the next generation of the ext3 filesystem.
Unlike the change from ext2 filesystem to ext3 filesystem,
the on-disk format of ext4 is not forwards compatible with
ext3; it is based on extent maps and it supports 48-bit
physical block numbers. The ext4 filesystem also supports delayed
allocation, persistent preallocation, high resolution time stamps,
and a number of other features to improve performance and speed
up fsck time. For more information, please see the web pages at
http://ext4.wiki.kernel.org.
The ext4 filesystem supports mounting an ext3 filesystem; while there
are some performance gains from the delayed allocation and inode
table readahead, the best performance gains require enabling ext4
features in the filesystem using tune2fs, or formatting a new
filesystem as an ext4 filesystem initially. Without explicit enabling
of ext4 features, the on disk filesystem format stays fully backward
compatible.
To compile this file system support as a module, choose M here. The
module will be called ext4.
If unsure, say N.
config EXT4_USE_FOR_EXT2
bool "Use ext4 for ext2 file systems"
depends on EXT4_FS
depends on EXT2_FS=n
default y
help
Allow the ext4 file system driver code to be used for ext2
file system mounts. This allows users to reduce their
compiled kernel size by using one file system driver for
ext2, ext3, and ext4 file systems.
config EXT4_FS_POSIX_ACL
bool "Ext4 POSIX Access Control Lists"
depends on EXT4_FS
select FS_POSIX_ACL
help
POSIX Access Control Lists (ACLs) support permissions for users and
groups beyond the owner/group/world scheme.
If you don't know what Access Control Lists are, say N
config EXT4_FS_SECURITY
bool "Ext4 Security Labels"
depends on EXT4_FS
help
Security labels support alternative access control models
implemented by security modules like SELinux. This option
enables an extended attribute handler for file security
labels in the ext4 filesystem.
If you are not using a security module that requires using
extended attributes for file security labels, say N.
config EXT4_DEBUG
bool "Ext4 debugging support"
depends on EXT4_FS
help
Enables run-time debugging support for the ext4 filesystem.
If you select Y here, then you will be able to turn on debugging
using dynamic debug control for mb_debug() / ext_debug() msgs.
config EXT4_KUNIT_TESTS
tristate "KUnit tests for ext4" if !KUNIT_ALL_TESTS
depends on EXT4_FS && KUNIT
default KUNIT_ALL_TESTS
help
This builds the ext4 KUnit tests.
KUnit tests run during boot and output the results to the debug log
in TAP format (https://testanything.org/). Only useful for kernel devs
running KUnit test harness and are not for inclusion into a production
build.
For more information on KUnit and unit tests in general please refer
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the linux ext4-filesystem routines.
#
obj-$(CONFIG_EXT4_FS) += ext4.o
ext4-y := balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
extents_status.o file.o fsmap.o fsync.o hash.o ialloc.o \
indirect.o inline.o inode.o ioctl.o mballoc.o migrate.o \
mmp.o move_extent.o namei.o page-io.o readpage.o resize.o \
super.o symlink.o sysfs.o xattr.o xattr_hurd.o xattr_trusted.o \
xattr_user.o fast_commit.o orphan.o
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
ext4-test-objs += inode-test.o mballoc-test.o \
extents-test.o hash-test.o
obj-$(CONFIG_EXT4_KUNIT_TESTS) += ext4-test.o
ext4-$(CONFIG_FS_VERITY) += verity.o
ext4-$(CONFIG_FS_ENCRYPTION) += crypto.o
+304
View File
@@ -0,0 +1,304 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/acl.c
*
* Copyright (C) 2001-2003 Andreas Gruenbacher, <agruen@suse.de>
*/
#include <linux/quotaops.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
#include "acl.h"
/*
* Convert from filesystem to in-memory representation.
*/
static struct posix_acl *
ext4_acl_from_disk(const void *value, size_t size)
{
const char *end = (char *)value + size;
int n, count;
struct posix_acl *acl;
if (!value)
return NULL;
if (size < sizeof(ext4_acl_header))
return ERR_PTR(-EINVAL);
if (((ext4_acl_header *)value)->a_version !=
cpu_to_le32(EXT4_ACL_VERSION))
return ERR_PTR(-EINVAL);
value = (char *)value + sizeof(ext4_acl_header);
count = ext4_acl_count(size);
if (count < 0)
return ERR_PTR(-EINVAL);
if (count == 0)
return NULL;
acl = posix_acl_alloc(count, GFP_NOFS);
if (!acl)
return ERR_PTR(-ENOMEM);
for (n = 0; n < count; n++) {
ext4_acl_entry *entry =
(ext4_acl_entry *)value;
if ((char *)value + sizeof(ext4_acl_entry_short) > end)
goto fail;
acl->a_entries[n].e_tag = le16_to_cpu(entry->e_tag);
acl->a_entries[n].e_perm = le16_to_cpu(entry->e_perm);
switch (acl->a_entries[n].e_tag) {
case ACL_USER_OBJ:
case ACL_GROUP_OBJ:
case ACL_MASK:
case ACL_OTHER:
value = (char *)value +
sizeof(ext4_acl_entry_short);
break;
case ACL_USER:
value = (char *)value + sizeof(ext4_acl_entry);
if ((char *)value > end)
goto fail;
acl->a_entries[n].e_uid =
make_kuid(&init_user_ns,
le32_to_cpu(entry->e_id));
break;
case ACL_GROUP:
value = (char *)value + sizeof(ext4_acl_entry);
if ((char *)value > end)
goto fail;
acl->a_entries[n].e_gid =
make_kgid(&init_user_ns,
le32_to_cpu(entry->e_id));
break;
default:
goto fail;
}
}
if (value != end)
goto fail;
return acl;
fail:
posix_acl_release(acl);
return ERR_PTR(-EINVAL);
}
/*
* Convert from in-memory to filesystem representation.
*/
static void *
ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
{
ext4_acl_header *ext_acl;
char *e;
size_t n;
*size = ext4_acl_size(acl->a_count);
ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count *
sizeof(ext4_acl_entry), GFP_NOFS);
if (!ext_acl)
return ERR_PTR(-ENOMEM);
ext_acl->a_version = cpu_to_le32(EXT4_ACL_VERSION);
e = (char *)ext_acl + sizeof(ext4_acl_header);
for (n = 0; n < acl->a_count; n++) {
const struct posix_acl_entry *acl_e = &acl->a_entries[n];
ext4_acl_entry *entry = (ext4_acl_entry *)e;
entry->e_tag = cpu_to_le16(acl_e->e_tag);
entry->e_perm = cpu_to_le16(acl_e->e_perm);
switch (acl_e->e_tag) {
case ACL_USER:
entry->e_id = cpu_to_le32(
from_kuid(&init_user_ns, acl_e->e_uid));
e += sizeof(ext4_acl_entry);
break;
case ACL_GROUP:
entry->e_id = cpu_to_le32(
from_kgid(&init_user_ns, acl_e->e_gid));
e += sizeof(ext4_acl_entry);
break;
case ACL_USER_OBJ:
case ACL_GROUP_OBJ:
case ACL_MASK:
case ACL_OTHER:
e += sizeof(ext4_acl_entry_short);
break;
default:
goto fail;
}
}
return (char *)ext_acl;
fail:
kfree(ext_acl);
return ERR_PTR(-EINVAL);
}
/*
* Inode operation get_posix_acl().
*
* inode->i_rwsem: don't care
*/
struct posix_acl *
ext4_get_acl(struct inode *inode, int type, bool rcu)
{
int name_index;
char *value = NULL;
struct posix_acl *acl;
int retval;
if (rcu)
return ERR_PTR(-ECHILD);
switch (type) {
case ACL_TYPE_ACCESS:
name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
break;
default:
BUG();
}
retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
if (retval > 0) {
value = kmalloc(retval, GFP_NOFS);
if (!value)
return ERR_PTR(-ENOMEM);
retval = ext4_xattr_get(inode, name_index, "", value, retval);
}
if (retval > 0)
acl = ext4_acl_from_disk(value, retval);
else if (retval == -ENODATA || retval == -ENOSYS)
acl = NULL;
else
acl = ERR_PTR(retval);
kfree(value);
return acl;
}
/*
* Set the access or default ACL of an inode.
*
* inode->i_rwsem: down unless called from ext4_new_inode
*/
static int
__ext4_set_acl(handle_t *handle, struct inode *inode, int type,
struct posix_acl *acl, int xattr_flags)
{
int name_index;
void *value = NULL;
size_t size = 0;
int error;
switch (type) {
case ACL_TYPE_ACCESS:
name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
if (!S_ISDIR(inode->i_mode))
return acl ? -EACCES : 0;
break;
default:
return -EINVAL;
}
if (acl) {
value = ext4_acl_to_disk(acl, &size);
if (IS_ERR(value))
return (int)PTR_ERR(value);
}
error = ext4_xattr_set_handle(handle, inode, name_index, "",
value, size, xattr_flags);
kfree(value);
if (!error)
set_cached_acl(inode, type, acl);
return error;
}
int
ext4_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type)
{
handle_t *handle;
int error, credits, retries = 0;
size_t acl_size = acl ? ext4_acl_size(acl->a_count) : 0;
struct inode *inode = d_inode(dentry);
umode_t mode = inode->i_mode;
int update_mode = 0;
error = dquot_initialize(inode);
if (error)
return error;
retry:
error = ext4_xattr_set_credits(inode, acl_size, false /* is_create */,
&credits);
if (error)
return error;
handle = ext4_journal_start(inode, EXT4_HT_XATTR, credits);
if (IS_ERR(handle))
return PTR_ERR(handle);
if ((type == ACL_TYPE_ACCESS) && acl) {
error = posix_acl_update_mode(idmap, inode, &mode, &acl);
if (error)
goto out_stop;
if (mode != inode->i_mode)
update_mode = 1;
}
error = __ext4_set_acl(handle, inode, type, acl, 0 /* xattr_flags */);
if (!error && update_mode) {
inode->i_mode = mode;
inode_set_ctime_current(inode);
error = ext4_mark_inode_dirty(handle, inode);
}
out_stop:
ext4_journal_stop(handle);
if (error == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry;
return error;
}
/*
* Initialize the ACLs of a new inode. Called from ext4_new_inode.
*
* dir->i_rwsem: down
* inode->i_rwsem: up (access to inode is still exclusive)
*/
int
ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
{
struct posix_acl *default_acl, *acl;
int error;
error = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
if (error)
return error;
if (default_acl) {
error = __ext4_set_acl(handle, inode, ACL_TYPE_DEFAULT,
default_acl, XATTR_CREATE);
posix_acl_release(default_acl);
} else {
inode->i_default_acl = NULL;
}
if (acl) {
if (!error)
error = __ext4_set_acl(handle, inode, ACL_TYPE_ACCESS,
acl, XATTR_CREATE);
posix_acl_release(acl);
} else {
inode->i_acl = NULL;
}
return error;
}
+74
View File
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0
/*
File: fs/ext4/acl.h
(C) 2001 Andreas Gruenbacher, <a.gruenbacher@computer.org>
*/
#include <linux/posix_acl_xattr.h>
#define EXT4_ACL_VERSION 0x0001
typedef struct {
__le16 e_tag;
__le16 e_perm;
__le32 e_id;
} ext4_acl_entry;
typedef struct {
__le16 e_tag;
__le16 e_perm;
} ext4_acl_entry_short;
typedef struct {
__le32 a_version;
} ext4_acl_header;
static inline size_t ext4_acl_size(int count)
{
if (count <= 4) {
return sizeof(ext4_acl_header) +
count * sizeof(ext4_acl_entry_short);
} else {
return sizeof(ext4_acl_header) +
4 * sizeof(ext4_acl_entry_short) +
(count - 4) * sizeof(ext4_acl_entry);
}
}
static inline int ext4_acl_count(size_t size)
{
ssize_t s;
size -= sizeof(ext4_acl_header);
s = size - 4 * sizeof(ext4_acl_entry_short);
if (s < 0) {
if (size % sizeof(ext4_acl_entry_short))
return -1;
return size / sizeof(ext4_acl_entry_short);
} else {
if (s % sizeof(ext4_acl_entry))
return -1;
return s / sizeof(ext4_acl_entry) + 4;
}
}
#ifdef CONFIG_EXT4_FS_POSIX_ACL
/* acl.c */
struct posix_acl *ext4_get_acl(struct inode *inode, int type, bool rcu);
int ext4_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type);
extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);
#else /* CONFIG_EXT4_FS_POSIX_ACL */
#include <linux/sched.h>
#define ext4_get_acl NULL
#define ext4_set_acl NULL
static inline int
ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
{
return 0;
}
#endif /* CONFIG_EXT4_FS_POSIX_ACL */
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/bitmap.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)
*/
#include <linux/buffer_head.h>
#include "ext4.h"
unsigned int ext4_count_free(char *bitmap, unsigned int numchars)
{
return numchars * BITS_PER_BYTE - memweight(bitmap, numchars);
}
int ext4_inode_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
__u32 hi;
__u32 provided, calculated;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int sz;
if (!ext4_has_feature_metadata_csum(sb))
return 1;
sz = EXT4_INODES_PER_GROUP(sb) >> 3;
provided = le16_to_cpu(gdp->bg_inode_bitmap_csum_lo);
calculated = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
if (sbi->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END) {
hi = le16_to_cpu(gdp->bg_inode_bitmap_csum_hi);
provided |= (hi << 16);
} else
calculated &= 0xFFFF;
return provided == calculated;
}
void ext4_inode_bitmap_csum_set(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
__u32 csum;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int sz;
if (!ext4_has_feature_metadata_csum(sb))
return;
sz = EXT4_INODES_PER_GROUP(sb) >> 3;
csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
gdp->bg_inode_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
if (sbi->s_desc_size >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
gdp->bg_inode_bitmap_csum_hi = cpu_to_le16(csum >> 16);
}
int ext4_block_bitmap_csum_verify(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
__u32 hi;
__u32 provided, calculated;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int sz = EXT4_CLUSTERS_PER_GROUP(sb) / 8;
if (!ext4_has_feature_metadata_csum(sb))
return 1;
provided = le16_to_cpu(gdp->bg_block_bitmap_csum_lo);
calculated = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
if (sbi->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END) {
hi = le16_to_cpu(gdp->bg_block_bitmap_csum_hi);
provided |= (hi << 16);
} else
calculated &= 0xFFFF;
return provided == calculated;
}
void ext4_block_bitmap_csum_set(struct super_block *sb,
struct ext4_group_desc *gdp,
struct buffer_head *bh)
{
int sz = EXT4_CLUSTERS_PER_GROUP(sb) / 8;
__u32 csum;
struct ext4_sb_info *sbi = EXT4_SB(sb);
if (!ext4_has_feature_metadata_csum(sb))
return;
csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)bh->b_data, sz);
gdp->bg_block_bitmap_csum_lo = cpu_to_le16(csum & 0xFFFF);
if (sbi->s_desc_size >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
gdp->bg_block_bitmap_csum_hi = cpu_to_le16(csum >> 16);
}
+370
View File
@@ -0,0 +1,370 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/block_validity.c
*
* Copyright (C) 2009
* Theodore Ts'o (tytso@mit.edu)
*
* Track which blocks in the filesystem are metadata blocks that
* should never be used as data blocks by files or directories.
*/
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/quotaops.h>
#include <linux/buffer_head.h>
#include <linux/swap.h>
#include <linux/pagemap.h>
#include <linux/blkdev.h>
#include <linux/slab.h>
#include "ext4.h"
struct ext4_system_zone {
struct rb_node node;
ext4_fsblk_t start_blk;
unsigned int count;
u32 ino;
};
static struct kmem_cache *ext4_system_zone_cachep;
int __init ext4_init_system_zone(void)
{
ext4_system_zone_cachep = KMEM_CACHE(ext4_system_zone, 0);
if (ext4_system_zone_cachep == NULL)
return -ENOMEM;
return 0;
}
void ext4_exit_system_zone(void)
{
rcu_barrier();
kmem_cache_destroy(ext4_system_zone_cachep);
}
static inline int can_merge(struct ext4_system_zone *entry1,
struct ext4_system_zone *entry2)
{
if ((entry1->start_blk + entry1->count) == entry2->start_blk &&
entry1->ino == entry2->ino)
return 1;
return 0;
}
static void release_system_zone(struct ext4_system_blocks *system_blks)
{
struct ext4_system_zone *entry, *n;
rbtree_postorder_for_each_entry_safe(entry, n,
&system_blks->root, node)
kmem_cache_free(ext4_system_zone_cachep, entry);
}
/*
* Mark a range of blocks as belonging to the "system zone" --- that
* is, filesystem metadata blocks which should never be used by
* inodes.
*/
static int add_system_zone(struct ext4_system_blocks *system_blks,
ext4_fsblk_t start_blk,
unsigned int count, u32 ino)
{
struct ext4_system_zone *new_entry, *entry;
struct rb_node **n = &system_blks->root.rb_node, *node;
struct rb_node *parent = NULL, *new_node;
while (*n) {
parent = *n;
entry = rb_entry(parent, struct ext4_system_zone, node);
if (start_blk < entry->start_blk)
n = &(*n)->rb_left;
else if (start_blk >= (entry->start_blk + entry->count))
n = &(*n)->rb_right;
else /* Unexpected overlap of system zones. */
return -EFSCORRUPTED;
}
new_entry = kmem_cache_alloc(ext4_system_zone_cachep,
GFP_KERNEL);
if (!new_entry)
return -ENOMEM;
new_entry->start_blk = start_blk;
new_entry->count = count;
new_entry->ino = ino;
new_node = &new_entry->node;
rb_link_node(new_node, parent, n);
rb_insert_color(new_node, &system_blks->root);
/* Can we merge to the left? */
node = rb_prev(new_node);
if (node) {
entry = rb_entry(node, struct ext4_system_zone, node);
if (can_merge(entry, new_entry)) {
new_entry->start_blk = entry->start_blk;
new_entry->count += entry->count;
rb_erase(node, &system_blks->root);
kmem_cache_free(ext4_system_zone_cachep, entry);
}
}
/* Can we merge to the right? */
node = rb_next(new_node);
if (node) {
entry = rb_entry(node, struct ext4_system_zone, node);
if (can_merge(new_entry, entry)) {
new_entry->count += entry->count;
rb_erase(node, &system_blks->root);
kmem_cache_free(ext4_system_zone_cachep, entry);
}
}
return 0;
}
static void debug_print_tree(struct ext4_sb_info *sbi)
{
struct rb_node *node;
struct ext4_system_zone *entry;
struct ext4_system_blocks *system_blks;
int first = 1;
printk(KERN_INFO "System zones: ");
rcu_read_lock();
system_blks = rcu_dereference(sbi->s_system_blks);
node = rb_first(&system_blks->root);
while (node) {
entry = rb_entry(node, struct ext4_system_zone, node);
printk(KERN_CONT "%s%llu-%llu", first ? "" : ", ",
entry->start_blk, entry->start_blk + entry->count - 1);
first = 0;
node = rb_next(node);
}
rcu_read_unlock();
printk(KERN_CONT "\n");
}
static int ext4_protect_reserved_inode(struct super_block *sb,
struct ext4_system_blocks *system_blks,
u32 ino)
{
struct inode *inode;
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_map_blocks map;
u32 i = 0, num;
int err = 0, n;
if ((ino < EXT4_ROOT_INO) ||
(ino > le32_to_cpu(sbi->s_es->s_inodes_count)))
return -EINVAL;
inode = ext4_iget(sb, ino, EXT4_IGET_SPECIAL);
if (IS_ERR(inode))
return PTR_ERR(inode);
num = (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
while (i < num) {
cond_resched();
map.m_lblk = i;
map.m_len = num - i;
n = ext4_map_blocks(NULL, inode, &map, 0);
if (n < 0) {
err = n;
break;
}
if (n == 0) {
i++;
} else {
err = add_system_zone(system_blks, map.m_pblk, n, ino);
if (err < 0) {
if (err == -EFSCORRUPTED) {
EXT4_ERROR_INODE_ERR(inode, -err,
"blocks %llu-%llu from inode overlap system zone",
map.m_pblk,
map.m_pblk + map.m_len - 1);
}
break;
}
i += n;
}
}
iput(inode);
return err;
}
static void ext4_destroy_system_zone(struct rcu_head *rcu)
{
struct ext4_system_blocks *system_blks;
system_blks = container_of(rcu, struct ext4_system_blocks, rcu);
release_system_zone(system_blks);
kfree(system_blks);
}
/*
* Build system zone rbtree which is used for block validity checking.
*
* The update of system_blks pointer in this function is protected by
* sb->s_umount semaphore. However we have to be careful as we can be
* racing with ext4_inode_block_valid() calls reading system_blks rbtree
* protected only by RCU. That's why we first build the rbtree and then
* swap it in place.
*/
int ext4_setup_system_zone(struct super_block *sb)
{
ext4_group_t ngroups = ext4_get_groups_count(sb);
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_system_blocks *system_blks;
struct ext4_group_desc *gdp;
ext4_group_t i;
int ret;
system_blks = kzalloc_obj(*system_blks);
if (!system_blks)
return -ENOMEM;
for (i=0; i < ngroups; i++) {
unsigned int meta_blks = ext4_num_base_meta_blocks(sb, i);
cond_resched();
if (meta_blks != 0) {
ret = add_system_zone(system_blks,
ext4_group_first_block_no(sb, i),
meta_blks, 0);
if (ret)
goto err;
}
gdp = ext4_get_group_desc(sb, i, NULL);
ret = add_system_zone(system_blks,
ext4_block_bitmap(sb, gdp), 1, 0);
if (ret)
goto err;
ret = add_system_zone(system_blks,
ext4_inode_bitmap(sb, gdp), 1, 0);
if (ret)
goto err;
ret = add_system_zone(system_blks,
ext4_inode_table(sb, gdp),
sbi->s_itb_per_group, 0);
if (ret)
goto err;
}
if (ext4_has_feature_journal(sb) && sbi->s_es->s_journal_inum) {
ret = ext4_protect_reserved_inode(sb, system_blks,
le32_to_cpu(sbi->s_es->s_journal_inum));
if (ret)
goto err;
}
/*
* System blks rbtree complete, announce it once to prevent racing
* with ext4_inode_block_valid() accessing the rbtree at the same
* time.
*/
rcu_assign_pointer(sbi->s_system_blks, system_blks);
if (test_opt(sb, DEBUG))
debug_print_tree(sbi);
return 0;
err:
release_system_zone(system_blks);
kfree(system_blks);
return ret;
}
/*
* Called when the filesystem is unmounted or when remounting it with
* noblock_validity specified.
*
* The update of system_blks pointer in this function is protected by
* sb->s_umount semaphore. However we have to be careful as we can be
* racing with ext4_inode_block_valid() calls reading system_blks rbtree
* protected only by RCU. So we first clear the system_blks pointer and
* then free the rbtree only after RCU grace period expires.
*/
void ext4_release_system_zone(struct super_block *sb)
{
struct ext4_system_blocks *system_blks;
system_blks = rcu_dereference_protected(EXT4_SB(sb)->s_system_blks,
lockdep_is_held(&sb->s_umount));
rcu_assign_pointer(EXT4_SB(sb)->s_system_blks, NULL);
if (system_blks)
call_rcu(&system_blks->rcu, ext4_destroy_system_zone);
}
int ext4_sb_block_valid(struct super_block *sb, struct inode *inode,
ext4_fsblk_t start_blk, unsigned int count)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_system_blocks *system_blks;
struct ext4_system_zone *entry;
struct rb_node *n;
int ret = 1;
if ((start_blk <= le32_to_cpu(sbi->s_es->s_first_data_block)) ||
(start_blk + count < start_blk) ||
(start_blk + count > ext4_blocks_count(sbi->s_es)))
return 0;
/*
* Lock the system zone to prevent it being released concurrently
* when doing a remount which inverse current "[no]block_validity"
* mount option.
*/
rcu_read_lock();
system_blks = rcu_dereference(sbi->s_system_blks);
if (system_blks == NULL)
goto out_rcu;
n = system_blks->root.rb_node;
while (n) {
entry = rb_entry(n, struct ext4_system_zone, node);
if (start_blk + count - 1 < entry->start_blk)
n = n->rb_left;
else if (start_blk >= (entry->start_blk + entry->count))
n = n->rb_right;
else {
ret = 0;
if (inode)
ret = (entry->ino == inode->i_ino);
break;
}
}
out_rcu:
rcu_read_unlock();
return ret;
}
/*
* Returns 1 if the passed-in block region (start_blk,
* start_blk+count) is valid; 0 if some part of the block region
* overlaps with some other filesystem metadata blocks.
*/
int ext4_inode_block_valid(struct inode *inode, ext4_fsblk_t start_blk,
unsigned int count)
{
return ext4_sb_block_valid(inode->i_sb, inode, start_blk, count);
}
int ext4_check_blockref(const char *function, unsigned int line,
struct inode *inode, __le32 *p, unsigned int max)
{
__le32 *bref = p;
unsigned int blk;
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
if (journal && inode == journal->j_inode)
return 0;
while (bref < p+max) {
blk = le32_to_cpu(*bref++);
if (blk &&
unlikely(!ext4_inode_block_valid(inode, blk, 1))) {
ext4_error_inode(inode, function, line, blk,
"invalid block");
return -EFSCORRUPTED;
}
}
return 0;
}
+248
View File
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/quotaops.h>
#include <linux/uuid.h>
#include "ext4.h"
#include "xattr.h"
#include "ext4_jbd2.h"
static void ext4_fname_from_fscrypt_name(struct ext4_filename *dst,
const struct fscrypt_name *src)
{
memset(dst, 0, sizeof(*dst));
dst->usr_fname = src->usr_fname;
dst->disk_name = src->disk_name;
dst->hinfo.hash = src->hash;
dst->hinfo.minor_hash = src->minor_hash;
dst->crypto_buf = src->crypto_buf;
}
int ext4_fname_setup_filename(struct inode *dir, const struct qstr *iname,
int lookup, struct ext4_filename *fname)
{
struct fscrypt_name name;
int err;
err = fscrypt_setup_filename(dir, iname, lookup, &name);
if (err)
return err;
ext4_fname_from_fscrypt_name(fname, &name);
err = ext4_fname_setup_ci_filename(dir, iname, fname);
if (err)
ext4_fname_free_filename(fname);
return err;
}
int ext4_fname_prepare_lookup(struct inode *dir, struct dentry *dentry,
struct ext4_filename *fname)
{
struct fscrypt_name name;
int err;
err = fscrypt_prepare_lookup(dir, dentry, &name);
if (err)
return err;
ext4_fname_from_fscrypt_name(fname, &name);
err = ext4_fname_setup_ci_filename(dir, &dentry->d_name, fname);
if (err)
ext4_fname_free_filename(fname);
return err;
}
void ext4_fname_free_filename(struct ext4_filename *fname)
{
struct fscrypt_name name;
name.crypto_buf = fname->crypto_buf;
fscrypt_free_filename(&name);
fname->crypto_buf.name = NULL;
fname->usr_fname = NULL;
fname->disk_name.name = NULL;
ext4_fname_free_ci_filename(fname);
}
static bool uuid_is_zero(__u8 u[16])
{
int i;
for (i = 0; i < 16; i++)
if (u[i])
return false;
return true;
}
int ext4_ioctl_get_encryption_pwsalt(struct file *filp, void __user *arg)
{
struct super_block *sb = file_inode(filp)->i_sb;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int err, err2;
handle_t *handle;
if (!ext4_has_feature_encrypt(sb))
return -EOPNOTSUPP;
if (uuid_is_zero(sbi->s_es->s_encrypt_pw_salt)) {
err = mnt_want_write_file(filp);
if (err)
return err;
handle = ext4_journal_start_sb(sb, EXT4_HT_MISC, 1);
if (IS_ERR(handle)) {
err = PTR_ERR(handle);
goto pwsalt_err_exit;
}
err = ext4_journal_get_write_access(handle, sb, sbi->s_sbh,
EXT4_JTR_NONE);
if (err)
goto pwsalt_err_journal;
lock_buffer(sbi->s_sbh);
generate_random_uuid(sbi->s_es->s_encrypt_pw_salt);
ext4_superblock_csum_set(sb);
unlock_buffer(sbi->s_sbh);
err = ext4_handle_dirty_metadata(handle, NULL, sbi->s_sbh);
pwsalt_err_journal:
err2 = ext4_journal_stop(handle);
if (err2 && !err)
err = err2;
pwsalt_err_exit:
mnt_drop_write_file(filp);
if (err)
return err;
}
if (copy_to_user(arg, sbi->s_es->s_encrypt_pw_salt, 16))
return -EFAULT;
return 0;
}
static int ext4_get_context(struct inode *inode, void *ctx, size_t len)
{
return ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, ctx, len);
}
static int ext4_set_context(struct inode *inode, const void *ctx, size_t len,
void *fs_data)
{
handle_t *handle = fs_data;
int res, res2, credits, retries = 0;
/*
* Encrypting the root directory is not allowed because e2fsck expects
* lost+found to exist and be unencrypted, and encrypting the root
* directory would imply encrypting the lost+found directory as well as
* the filename "lost+found" itself.
*/
if (inode->i_ino == EXT4_ROOT_INO)
return -EPERM;
/*
* For new encrypted inodes, S_DAX is never set in the first place.
*
* For existing inodes, this is called only on empty directories. ext4
* never sets S_DAX on directories.
*/
if (WARN_ON_ONCE(IS_DAX(inode)))
return -EINVAL;
if (ext4_test_inode_flag(inode, EXT4_INODE_DAX))
return -EOPNOTSUPP;
res = ext4_convert_inline_data(inode);
if (res)
return res;
/*
* If a journal handle was specified, then the encryption context is
* being set on a new inode via inheritance and is part of a larger
* transaction to create the inode. Otherwise the encryption context is
* being set on an existing inode in its own transaction. Only in the
* latter case should the "retry on ENOSPC" logic be used.
*/
if (handle) {
/*
* __ext4_new_inode() should have already set the encrypt flag
* on the inode and avoided enabling inline data.
*/
if (WARN_ON_ONCE(!IS_ENCRYPTED(inode)))
return -EINVAL;
if (WARN_ON_ONCE(ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)))
return -EINVAL;
/*
* Since the inode is new it is ok to pass the
* XATTR_CREATE flag. This is necessary to match the
* remaining journal credits check in the set_handle
* function with the credits allocated for the new
* inode.
*/
return ext4_xattr_set_handle(handle, inode,
EXT4_XATTR_INDEX_ENCRYPTION,
EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
ctx, len, XATTR_CREATE);
}
res = dquot_initialize(inode);
if (res)
return res;
retry:
res = ext4_xattr_set_credits(inode, len, false /* is_create */,
&credits);
if (res)
return res;
handle = ext4_journal_start(inode, EXT4_HT_MISC, credits);
if (IS_ERR(handle))
return PTR_ERR(handle);
res = ext4_xattr_set_handle(handle, inode, EXT4_XATTR_INDEX_ENCRYPTION,
EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
ctx, len, 0);
if (!res) {
ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT);
/* Update inode->i_flags to set S_ENCRYPTED. */
ext4_set_inode_flags(inode, false);
res = ext4_mark_inode_dirty(handle, inode);
if (res)
EXT4_ERROR_INODE(inode, "Failed to mark inode dirty");
}
res2 = ext4_journal_stop(handle);
if (res == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry;
if (!res)
res = res2;
return res;
}
static const union fscrypt_policy *ext4_get_dummy_policy(struct super_block *sb)
{
return EXT4_SB(sb)->s_dummy_enc_policy.policy;
}
static bool ext4_has_stable_inodes(struct super_block *sb)
{
return ext4_has_feature_stable_inodes(sb);
}
const struct fscrypt_operations ext4_cryptops = {
.inode_info_offs = (int)offsetof(struct ext4_inode_info, i_crypt_info) -
(int)offsetof(struct ext4_inode_info, vfs_inode),
.needs_bounce_pages = 1,
.has_32bit_inodes = 1,
.supports_subblock_data_units = 1,
.legacy_key_prefix = "ext4:",
.get_context = ext4_get_context,
.set_context = ext4_set_context,
.get_dummy_policy = ext4_get_dummy_policy,
.empty_dir = ext4_empty_dir,
.has_stable_inodes = ext4_has_stable_inodes,
};
+694
View File
@@ -0,0 +1,694 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/dir.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/dir.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* ext4 directory handling functions
*
* Big-endian to little-endian byte-swapping/bitmaps by
* David S. Miller (davem@caip.rutgers.edu), 1995
*
* Hash Tree Directory indexing (c) 2001 Daniel Phillips
*
*/
#include <linux/fs.h>
#include <linux/buffer_head.h>
#include <linux/filelock.h>
#include <linux/slab.h>
#include <linux/iversion.h>
#include <linux/unicode.h>
#include "ext4.h"
#include "xattr.h"
static int ext4_dx_readdir(struct file *, struct dir_context *);
/**
* is_dx_dir() - check if a directory is using htree indexing
* @inode: directory inode
*
* Check if the given dir-inode refers to an htree-indexed directory
* (or a directory which could potentially get converted to use htree
* indexing).
*
* Return 1 if it is a dx dir, 0 if not
*/
static int is_dx_dir(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
if (ext4_has_feature_dir_index(inode->i_sb) &&
((ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) ||
((inode->i_size >> sb->s_blocksize_bits) == 1) ||
ext4_has_inline_data(inode)))
return 1;
return 0;
}
static bool is_fake_dir_entry(struct ext4_dir_entry_2 *de)
{
/* Check if . or .. , or skip if namelen is 0 */
if ((de->name_len > 0) && (de->name_len <= 2) && (de->name[0] == '.') &&
(de->name[1] == '.' || de->name[1] == '\0'))
return true;
/* Check if this is a csum entry */
if (de->file_type == EXT4_FT_DIR_CSUM)
return true;
return false;
}
/*
* Return 0 if the directory entry is OK, and 1 if there is a problem
*
* Note: this is the opposite of what ext2 and ext3 historically returned...
*
* bh passed here can be an inode block or a dir data block, depending
* on the inode inline data flag.
*/
int __ext4_check_dir_entry(const char *function, unsigned int line,
struct inode *dir, struct file *filp,
struct ext4_dir_entry_2 *de,
struct buffer_head *bh, char *buf, int size,
unsigned int offset)
{
const char *error_msg = NULL;
const int rlen = ext4_rec_len_from_disk(de->rec_len,
dir->i_sb->s_blocksize);
const int next_offset = ((char *) de - buf) + rlen;
bool fake = is_fake_dir_entry(de);
bool has_csum = ext4_has_feature_metadata_csum(dir->i_sb);
if (unlikely(rlen < ext4_dir_rec_len(1, fake ? NULL : dir)))
error_msg = "rec_len is smaller than minimal";
else if (unlikely(rlen % 4 != 0))
error_msg = "rec_len % 4 != 0";
else if (unlikely(rlen < ext4_dir_rec_len(de->name_len,
fake ? NULL : dir)))
error_msg = "rec_len is too small for name_len";
else if (unlikely(next_offset > size))
error_msg = "directory entry overrun";
else if (unlikely(next_offset > size - ext4_dir_rec_len(1,
has_csum ? NULL : dir) &&
next_offset != size))
error_msg = "directory entry too close to block end";
else if (unlikely(le32_to_cpu(de->inode) >
le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
error_msg = "inode out of bounds";
else if (unlikely(next_offset == size && de->name_len == 1 &&
de->name[0] == '.'))
error_msg = "'.' directory cannot be the last in data block";
else
return 0;
if (filp)
ext4_error_file(filp, function, line, bh->b_blocknr,
"bad entry in directory: %s - offset=%u, "
"inode=%u, rec_len=%d, size=%d fake=%d",
error_msg, offset, le32_to_cpu(de->inode),
rlen, size, fake);
else
ext4_error_inode(dir, function, line, bh->b_blocknr,
"bad entry in directory: %s - offset=%u, "
"inode=%u, rec_len=%d, size=%d fake=%d",
error_msg, offset, le32_to_cpu(de->inode),
rlen, size, fake);
return 1;
}
static int ext4_readdir(struct file *file, struct dir_context *ctx)
{
unsigned int offset;
int i;
struct ext4_dir_entry_2 *de;
int err;
struct inode *inode = file_inode(file);
struct super_block *sb = inode->i_sb;
struct buffer_head *bh = NULL;
struct fscrypt_str fstr = FSTR_INIT(NULL, 0);
struct dir_private_info *info = file->private_data;
err = fscrypt_prepare_readdir(inode);
if (err)
return err;
if (is_dx_dir(inode)) {
err = ext4_dx_readdir(file, ctx);
if (err != ERR_BAD_DX_DIR)
return err;
/* Can we just clear INDEX flag to ignore htree information? */
if (!ext4_has_feature_metadata_csum(sb)) {
/*
* We don't set the inode dirty flag since it's not
* critical that it gets flushed back to the disk.
*/
ext4_clear_inode_flag(inode, EXT4_INODE_INDEX);
}
}
if (ext4_has_inline_data(inode)) {
int has_inline_data = 1;
err = ext4_read_inline_dir(file, ctx,
&has_inline_data);
if (has_inline_data)
return err;
}
if (IS_ENCRYPTED(inode)) {
err = fscrypt_fname_alloc_buffer(EXT4_NAME_LEN, &fstr);
if (err < 0)
return err;
}
while (ctx->pos < inode->i_size) {
struct ext4_map_blocks map;
if (fatal_signal_pending(current)) {
err = -ERESTARTSYS;
goto errout;
}
cond_resched();
offset = ctx->pos & (sb->s_blocksize - 1);
map.m_lblk = ctx->pos >> EXT4_BLOCK_SIZE_BITS(sb);
map.m_len = 1;
err = ext4_map_blocks(NULL, inode, &map, 0);
if (err == 0) {
/* m_len should never be zero but let's avoid
* an infinite loop if it somehow is */
if (map.m_len == 0)
map.m_len = 1;
ctx->pos += map.m_len * sb->s_blocksize;
continue;
}
if (err > 0) {
pgoff_t index = map.m_pblk << inode->i_blkbits >>
PAGE_SHIFT;
if (!ra_has_index(&file->f_ra, index))
page_cache_sync_readahead(
sb->s_bdev->bd_mapping,
&file->f_ra, file, index,
1 << EXT4_SB(sb)->s_min_folio_order);
file->f_ra.prev_pos = (loff_t)index << PAGE_SHIFT;
bh = ext4_bread(NULL, inode, map.m_lblk, 0);
if (IS_ERR(bh)) {
err = PTR_ERR(bh);
bh = NULL;
goto errout;
}
}
if (!bh) {
/* corrupt size? Maybe no more blocks to read */
if (ctx->pos > inode->i_blocks << 9)
break;
ctx->pos += sb->s_blocksize - offset;
continue;
}
/* Check the checksum */
if (!buffer_verified(bh) &&
!ext4_dirblock_csum_verify(inode, bh)) {
EXT4_ERROR_FILE(file, 0, "directory fails checksum "
"at offset %llu",
(unsigned long long)ctx->pos);
ctx->pos += sb->s_blocksize - offset;
brelse(bh);
bh = NULL;
continue;
}
set_buffer_verified(bh);
/* If the dir block has changed since the last call to
* readdir(2), then we might be pointing to an invalid
* dirent right now. Scan from the start of the block
* to make sure. */
if (!inode_eq_iversion(inode, info->cookie)) {
for (i = 0; i < sb->s_blocksize && i < offset; ) {
de = (struct ext4_dir_entry_2 *)
(bh->b_data + i);
/* It's too expensive to do a full
* dirent test each time round this
* loop, but we do have to test at
* least that it is non-zero. A
* failure will be detected in the
* dirent test below. */
if (ext4_rec_len_from_disk(de->rec_len,
sb->s_blocksize) < ext4_dir_rec_len(1,
inode))
break;
i += ext4_rec_len_from_disk(de->rec_len,
sb->s_blocksize);
}
offset = i;
ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1))
| offset;
info->cookie = inode_query_iversion(inode);
}
while (ctx->pos < inode->i_size
&& offset < sb->s_blocksize) {
de = (struct ext4_dir_entry_2 *) (bh->b_data + offset);
if (ext4_check_dir_entry(inode, file, de, bh,
bh->b_data, bh->b_size,
offset)) {
/*
* On error, skip to the next block
*/
ctx->pos = (ctx->pos |
(sb->s_blocksize - 1)) + 1;
break;
}
offset += ext4_rec_len_from_disk(de->rec_len,
sb->s_blocksize);
if (le32_to_cpu(de->inode)) {
if (!IS_ENCRYPTED(inode)) {
if (!dir_emit(ctx, de->name,
de->name_len,
le32_to_cpu(de->inode),
get_dtype(sb, de->file_type)))
goto done;
} else {
int save_len = fstr.len;
struct fscrypt_str de_name =
FSTR_INIT(de->name,
de->name_len);
u32 hash;
u32 minor_hash;
if (IS_CASEFOLDED(inode)) {
hash = EXT4_DIRENT_HASH(de);
minor_hash = EXT4_DIRENT_MINOR_HASH(de);
} else {
hash = 0;
minor_hash = 0;
}
/* Directory is encrypted */
err = fscrypt_fname_disk_to_usr(inode,
hash, minor_hash, &de_name, &fstr);
de_name = fstr;
fstr.len = save_len;
if (err)
goto errout;
if (!dir_emit(ctx,
de_name.name, de_name.len,
le32_to_cpu(de->inode),
get_dtype(sb, de->file_type)))
goto done;
}
}
ctx->pos += ext4_rec_len_from_disk(de->rec_len,
sb->s_blocksize);
}
if ((ctx->pos < inode->i_size) && !dir_relax_shared(inode))
goto done;
brelse(bh);
bh = NULL;
}
done:
err = 0;
errout:
fscrypt_fname_free_buffer(&fstr);
brelse(bh);
return err;
}
static inline int is_32bit_api(void)
{
#ifdef CONFIG_COMPAT
return in_compat_syscall();
#else
return (BITS_PER_LONG == 32);
#endif
}
/*
* These functions convert from the major/minor hash to an f_pos
* value for dx directories
*
* Upper layer (for example NFS) should specify FMODE_32BITHASH or
* FMODE_64BITHASH explicitly. On the other hand, we allow ext4 to be mounted
* directly on both 32-bit and 64-bit nodes, under such case, neither
* FMODE_32BITHASH nor FMODE_64BITHASH is specified.
*/
static inline loff_t hash2pos(struct file *filp, __u32 major, __u32 minor)
{
if ((filp->f_mode & FMODE_32BITHASH) ||
(!(filp->f_mode & FMODE_64BITHASH) && is_32bit_api()))
return major >> 1;
else
return ((__u64)(major >> 1) << 32) | (__u64)minor;
}
static inline __u32 pos2maj_hash(struct file *filp, loff_t pos)
{
if ((filp->f_mode & FMODE_32BITHASH) ||
(!(filp->f_mode & FMODE_64BITHASH) && is_32bit_api()))
return (pos << 1) & 0xffffffff;
else
return ((pos >> 32) << 1) & 0xffffffff;
}
static inline __u32 pos2min_hash(struct file *filp, loff_t pos)
{
if ((filp->f_mode & FMODE_32BITHASH) ||
(!(filp->f_mode & FMODE_64BITHASH) && is_32bit_api()))
return 0;
else
return pos & 0xffffffff;
}
/*
* Return 32- or 64-bit end-of-file for dx directories
*/
static inline loff_t ext4_get_htree_eof(struct file *filp)
{
if ((filp->f_mode & FMODE_32BITHASH) ||
(!(filp->f_mode & FMODE_64BITHASH) && is_32bit_api()))
return EXT4_HTREE_EOF_32BIT;
else
return EXT4_HTREE_EOF_64BIT;
}
/*
* ext4_dir_llseek() calls generic_file_llseek_size to handle htree
* directories, where the "offset" is in terms of the filename hash
* value instead of the byte offset.
*
* Because we may return a 64-bit hash that is well beyond offset limits,
* we need to pass the max hash as the maximum allowable offset in
* the htree directory case.
*
* For non-htree, ext4_llseek already chooses the proper max offset.
*/
static loff_t ext4_dir_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
struct dir_private_info *info = file->private_data;
int dx_dir = is_dx_dir(inode);
loff_t ret, htree_max = ext4_get_htree_eof(file);
if (likely(dx_dir))
ret = generic_file_llseek_size(file, offset, whence,
htree_max, htree_max);
else
ret = ext4_llseek(file, offset, whence);
info->cookie = inode_peek_iversion(inode) - 1;
return ret;
}
/*
* This structure holds the nodes of the red-black tree used to store
* the directory entry in hash order.
*/
struct fname {
__u32 hash;
__u32 minor_hash;
struct rb_node rb_hash;
struct fname *next;
__u32 inode;
__u8 name_len;
__u8 file_type;
char name[] __counted_by(name_len);
};
/*
* This function implements a non-recursive way of freeing all of the
* nodes in the red-black tree.
*/
static void free_rb_tree_fname(struct rb_root *root)
{
struct fname *fname, *next;
rbtree_postorder_for_each_entry_safe(fname, next, root, rb_hash)
while (fname) {
struct fname *old = fname;
fname = fname->next;
kfree(old);
}
*root = RB_ROOT;
}
static void ext4_htree_init_dir_info(struct file *filp, loff_t pos)
{
struct dir_private_info *p = filp->private_data;
if (is_dx_dir(file_inode(filp)) && !p->initialized) {
p->curr_hash = pos2maj_hash(filp, pos);
p->curr_minor_hash = pos2min_hash(filp, pos);
p->initialized = true;
}
}
void ext4_htree_free_dir_info(struct dir_private_info *p)
{
free_rb_tree_fname(&p->root);
kfree(p);
}
/*
* Given a directory entry, enter it into the fname rb tree.
*
* When filename encryption is enabled, the dirent will hold the
* encrypted filename, while the htree will hold decrypted filename.
* The decrypted filename is passed in via ent_name. parameter.
*/
int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,
__u32 minor_hash,
struct ext4_dir_entry_2 *dirent,
struct fscrypt_str *ent_name)
{
struct rb_node **p, *parent = NULL;
struct fname *fname, *new_fn;
struct dir_private_info *info;
info = dir_file->private_data;
p = &info->root.rb_node;
/* Create and allocate the fname structure */
new_fn = kzalloc_flex(*new_fn, name, ent_name->len + 1);
if (!new_fn)
return -ENOMEM;
new_fn->hash = hash;
new_fn->minor_hash = minor_hash;
new_fn->inode = le32_to_cpu(dirent->inode);
new_fn->name_len = ent_name->len;
new_fn->file_type = dirent->file_type;
memcpy(new_fn->name, ent_name->name, ent_name->len);
while (*p) {
parent = *p;
fname = rb_entry(parent, struct fname, rb_hash);
/*
* If the hash and minor hash match up, then we put
* them on a linked list. This rarely happens...
*/
if ((new_fn->hash == fname->hash) &&
(new_fn->minor_hash == fname->minor_hash)) {
new_fn->next = fname->next;
fname->next = new_fn;
return 0;
}
if (new_fn->hash < fname->hash)
p = &(*p)->rb_left;
else if (new_fn->hash > fname->hash)
p = &(*p)->rb_right;
else if (new_fn->minor_hash < fname->minor_hash)
p = &(*p)->rb_left;
else /* if (new_fn->minor_hash > fname->minor_hash) */
p = &(*p)->rb_right;
}
rb_link_node(&new_fn->rb_hash, parent, p);
rb_insert_color(&new_fn->rb_hash, &info->root);
return 0;
}
/*
* This is a helper function for ext4_dx_readdir. It calls filldir
* for all entries on the fname linked list. (Normally there is only
* one entry on the linked list, unless there are 62 bit hash collisions.)
*/
static int call_filldir(struct file *file, struct dir_context *ctx,
struct fname *fname)
{
struct dir_private_info *info = file->private_data;
struct inode *inode = file_inode(file);
struct super_block *sb = inode->i_sb;
if (!fname) {
ext4_msg(sb, KERN_ERR, "%s:%d: inode #%llu: comm %s: "
"called with null fname?!?", __func__, __LINE__,
inode->i_ino, current->comm);
return 0;
}
ctx->pos = hash2pos(file, fname->hash, fname->minor_hash);
while (fname) {
if (!dir_emit(ctx, fname->name,
fname->name_len,
fname->inode,
get_dtype(sb, fname->file_type))) {
info->extra_fname = fname;
return 1;
}
fname = fname->next;
}
return 0;
}
static int ext4_dx_readdir(struct file *file, struct dir_context *ctx)
{
struct dir_private_info *info = file->private_data;
struct inode *inode = file_inode(file);
struct fname *fname;
int ret = 0;
ext4_htree_init_dir_info(file, ctx->pos);
if (ctx->pos == ext4_get_htree_eof(file))
return 0; /* EOF */
/* Some one has messed with f_pos; reset the world */
if (info->last_pos != ctx->pos) {
free_rb_tree_fname(&info->root);
info->curr_node = NULL;
info->extra_fname = NULL;
info->curr_hash = pos2maj_hash(file, ctx->pos);
info->curr_minor_hash = pos2min_hash(file, ctx->pos);
}
/*
* If there are any leftover names on the hash collision
* chain, return them first.
*/
if (info->extra_fname) {
if (call_filldir(file, ctx, info->extra_fname))
goto finished;
info->extra_fname = NULL;
goto next_node;
} else if (!info->curr_node)
info->curr_node = rb_first(&info->root);
while (1) {
/*
* Fill the rbtree if we have no more entries,
* or the inode has changed since we last read in the
* cached entries.
*/
if ((!info->curr_node) ||
!inode_eq_iversion(inode, info->cookie)) {
info->curr_node = NULL;
free_rb_tree_fname(&info->root);
info->cookie = inode_query_iversion(inode);
ret = ext4_htree_fill_tree(file, info->curr_hash,
info->curr_minor_hash,
&info->next_hash);
if (ret < 0)
goto finished;
if (ret == 0) {
ctx->pos = ext4_get_htree_eof(file);
break;
}
info->curr_node = rb_first(&info->root);
}
fname = rb_entry(info->curr_node, struct fname, rb_hash);
info->curr_hash = fname->hash;
info->curr_minor_hash = fname->minor_hash;
if (call_filldir(file, ctx, fname))
break;
next_node:
info->curr_node = rb_next(info->curr_node);
if (info->curr_node) {
fname = rb_entry(info->curr_node, struct fname,
rb_hash);
info->curr_hash = fname->hash;
info->curr_minor_hash = fname->minor_hash;
} else {
if (info->next_hash == ~0) {
ctx->pos = ext4_get_htree_eof(file);
break;
}
info->curr_hash = info->next_hash;
info->curr_minor_hash = 0;
}
}
finished:
info->last_pos = ctx->pos;
return ret < 0 ? ret : 0;
}
static int ext4_release_dir(struct inode *inode, struct file *filp)
{
if (filp->private_data)
ext4_htree_free_dir_info(filp->private_data);
return 0;
}
int ext4_check_all_de(struct inode *dir, struct buffer_head *bh, void *buf,
int buf_size)
{
struct ext4_dir_entry_2 *de;
int rlen;
unsigned int offset = 0;
char *top;
de = buf;
top = buf + buf_size;
while ((char *) de < top) {
if (ext4_check_dir_entry(dir, NULL, de, bh,
buf, buf_size, offset))
return -EFSCORRUPTED;
rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
offset += rlen;
}
if ((char *) de > top)
return -EFSCORRUPTED;
return 0;
}
static int ext4_dir_open(struct inode *inode, struct file *file)
{
struct dir_private_info *info;
info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
file->private_data = info;
return 0;
}
const struct file_operations ext4_dir_operations = {
.open = ext4_dir_open,
.llseek = ext4_dir_llseek,
.read = generic_read_dir,
.iterate_shared = ext4_readdir,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.fsync = ext4_sync_file,
.release = ext4_release_dir,
.setlease = generic_setlease,
};
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2003-2006, Cluster File Systems, Inc, info@clusterfs.com
* Written by Alex Tomas <alex@clusterfs.com>
*/
#ifndef _EXT4_EXTENTS
#define _EXT4_EXTENTS
#include "ext4.h"
/*
* With AGGRESSIVE_TEST defined, the capacity of index/leaf blocks
* becomes very small, so index split, in-depth growing and
* other hard changes happen much more often.
* This is for debug purposes only.
*/
#define AGGRESSIVE_TEST_
/*
* With EXTENTS_STATS defined, the number of blocks and extents
* are collected in the truncate path. They'll be shown at
* umount time.
*/
#define EXTENTS_STATS__
/*
* If CHECK_BINSEARCH is defined, then the results of the binary search
* will also be checked by linear search.
*/
#define CHECK_BINSEARCH__
/*
* ext4_inode has i_block array (60 bytes total).
* The first 12 bytes store ext4_extent_header;
* the remainder stores an array of ext4_extent.
* For non-inode extent blocks, ext4_extent_tail
* follows the array.
*/
/*
* This is the extent tail on-disk structure.
* All other extent structures are 12 bytes long. It turns out that
* block_size % 12 >= 4 for at least all powers of 2 greater than 512, which
* covers all valid ext4 block sizes. Therefore, this tail structure can be
* crammed into the end of the block without having to rebalance the tree.
*/
struct ext4_extent_tail {
__le32 et_checksum; /* crc32c(uuid+inum+extent_block) */
};
/*
* This is the extent on-disk structure.
* It's used at the bottom of the tree.
*/
struct ext4_extent {
__le32 ee_block; /* first logical block extent covers */
__le16 ee_len; /* number of blocks covered by extent */
__le16 ee_start_hi; /* high 16 bits of physical block */
__le32 ee_start_lo; /* low 32 bits of physical block */
};
/*
* This is index on-disk structure.
* It's used at all the levels except the bottom.
*/
struct ext4_extent_idx {
__le32 ei_block; /* index covers logical blocks from 'block' */
__le32 ei_leaf_lo; /* pointer to the physical block of the next *
* level. leaf or next index could be there */
__le16 ei_leaf_hi; /* high 16 bits of physical block */
__u16 ei_unused;
};
/*
* Each block (leaves and indexes), even inode-stored has header.
*/
struct ext4_extent_header {
__le16 eh_magic; /* probably will support different formats */
__le16 eh_entries; /* number of valid entries */
__le16 eh_max; /* capacity of store in entries */
__le16 eh_depth; /* has tree real underlying blocks? */
__le32 eh_generation; /* generation of the tree */
};
#define EXT4_EXT_MAGIC cpu_to_le16(0xf30a)
#define EXT4_MAX_EXTENT_DEPTH 5
#define EXT4_EXTENT_TAIL_OFFSET(hdr) \
(sizeof(struct ext4_extent_header) + \
(sizeof(struct ext4_extent) * le16_to_cpu((hdr)->eh_max)))
static inline struct ext4_extent_tail *
find_ext4_extent_tail(struct ext4_extent_header *eh)
{
return (struct ext4_extent_tail *)(((void *)eh) +
EXT4_EXTENT_TAIL_OFFSET(eh));
}
/*
* Array of ext4_ext_path contains path to some extent.
* Creation/lookup routines use it for traversal/splitting/etc.
* Truncate uses it to simulate recursive walking.
*/
struct ext4_ext_path {
ext4_fsblk_t p_block;
__u16 p_depth;
__u16 p_maxdepth;
struct ext4_extent *p_ext;
struct ext4_extent_idx *p_idx;
struct ext4_extent_header *p_hdr;
struct buffer_head *p_bh;
};
/*
* Used to record a portion of a cluster found at the beginning or end
* of an extent while traversing the extent tree during space removal.
* A partial cluster may be removed if it does not contain blocks shared
* with extents that aren't being deleted (tofree state). Otherwise,
* it cannot be removed (nofree state).
*/
struct partial_cluster {
ext4_fsblk_t pclu; /* physical cluster number */
ext4_lblk_t lblk; /* logical block number within logical cluster */
enum {initial, tofree, nofree} state;
};
/*
* structure for external API
*/
/*
* EXT_INIT_MAX_LEN is the maximum number of blocks we can have in an
* initialized extent. This is 2^15 and not (2^16 - 1), since we use the
* MSB of ee_len field in the extent datastructure to signify if this
* particular extent is an initialized extent or an unwritten (i.e.
* preallocated).
* EXT_UNWRITTEN_MAX_LEN is the maximum number of blocks we can have in an
* unwritten extent.
* If ee_len is <= 0x8000, it is an initialized extent. Otherwise, it is an
* unwritten one. In other words, if MSB of ee_len is set, it is an
* unwritten extent with only one special scenario when ee_len = 0x8000.
* In this case we can not have an unwritten extent of zero length and
* thus we make it as a special case of initialized extent with 0x8000 length.
* This way we get better extent-to-group alignment for initialized extents.
* Hence, the maximum number of blocks we can have in an *initialized*
* extent is 2^15 (32768) and in an *unwritten* extent is 2^15-1 (32767).
*/
#define EXT_INIT_MAX_LEN (1UL << 15)
#define EXT_UNWRITTEN_MAX_LEN (EXT_INIT_MAX_LEN - 1)
#define EXT_FIRST_EXTENT(__hdr__) \
((struct ext4_extent *) (((char *) (__hdr__)) + \
sizeof(struct ext4_extent_header)))
#define EXT_FIRST_INDEX(__hdr__) \
((struct ext4_extent_idx *) (((char *) (__hdr__)) + \
sizeof(struct ext4_extent_header)))
#define EXT_HAS_FREE_INDEX(__path__) \
(le16_to_cpu((__path__)->p_hdr->eh_entries) \
< le16_to_cpu((__path__)->p_hdr->eh_max))
#define EXT_LAST_EXTENT(__hdr__) \
(EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
#define EXT_LAST_INDEX(__hdr__) \
(EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
#define EXT_MAX_EXTENT(__hdr__) \
((le16_to_cpu((__hdr__)->eh_max)) ? \
((EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)) \
: NULL)
#define EXT_MAX_INDEX(__hdr__) \
((le16_to_cpu((__hdr__)->eh_max)) ? \
((EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)) \
: NULL)
static inline struct ext4_extent_header *ext_inode_hdr(struct inode *inode)
{
return (struct ext4_extent_header *) EXT4_I(inode)->i_data;
}
static inline struct ext4_extent_header *ext_block_hdr(struct buffer_head *bh)
{
return (struct ext4_extent_header *) bh->b_data;
}
static inline unsigned short ext_depth(struct inode *inode)
{
return le16_to_cpu(ext_inode_hdr(inode)->eh_depth);
}
static inline void ext4_ext_mark_unwritten(struct ext4_extent *ext)
{
/* We can not have an unwritten extent of zero length! */
BUG_ON((le16_to_cpu(ext->ee_len) & ~EXT_INIT_MAX_LEN) == 0);
ext->ee_len |= cpu_to_le16(EXT_INIT_MAX_LEN);
}
static inline int ext4_ext_is_unwritten(struct ext4_extent *ext)
{
/* Extent with ee_len of 0x8000 is treated as an initialized extent */
return (le16_to_cpu(ext->ee_len) > EXT_INIT_MAX_LEN);
}
static inline int ext4_ext_get_actual_len(struct ext4_extent *ext)
{
return (le16_to_cpu(ext->ee_len) <= EXT_INIT_MAX_LEN ?
le16_to_cpu(ext->ee_len) :
(le16_to_cpu(ext->ee_len) - EXT_INIT_MAX_LEN));
}
static inline void ext4_ext_mark_initialized(struct ext4_extent *ext)
{
ext->ee_len = cpu_to_le16(ext4_ext_get_actual_len(ext));
}
/*
* ext4_ext_pblock:
* combine low and high parts of physical block number into ext4_fsblk_t
*/
static inline ext4_fsblk_t ext4_ext_pblock(struct ext4_extent *ex)
{
ext4_fsblk_t block;
block = le32_to_cpu(ex->ee_start_lo);
block |= ((ext4_fsblk_t) le16_to_cpu(ex->ee_start_hi) << 31) << 1;
return block;
}
/*
* ext4_idx_pblock:
* combine low and high parts of a leaf physical block number into ext4_fsblk_t
*/
static inline ext4_fsblk_t ext4_idx_pblock(struct ext4_extent_idx *ix)
{
ext4_fsblk_t block;
block = le32_to_cpu(ix->ei_leaf_lo);
block |= ((ext4_fsblk_t) le16_to_cpu(ix->ei_leaf_hi) << 31) << 1;
return block;
}
/*
* ext4_ext_store_pblock:
* stores a large physical block number into an extent struct,
* breaking it into parts
*/
static inline void ext4_ext_store_pblock(struct ext4_extent *ex,
ext4_fsblk_t pb)
{
ex->ee_start_lo = cpu_to_le32((unsigned long) (pb & 0xffffffff));
ex->ee_start_hi = cpu_to_le16((unsigned long) ((pb >> 31) >> 1) &
0xffff);
}
/*
* ext4_idx_store_pblock:
* stores a large physical block number into an index struct,
* breaking it into parts
*/
static inline void ext4_idx_store_pblock(struct ext4_extent_idx *ix,
ext4_fsblk_t pb)
{
ix->ei_leaf_lo = cpu_to_le32((unsigned long) (pb & 0xffffffff));
ix->ei_leaf_hi = cpu_to_le16((unsigned long) ((pb >> 31) >> 1) &
0xffff);
}
extern int __ext4_ext_dirty(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct ext4_ext_path *path);
extern int ext4_ext_zeroout(struct inode *inode, struct ext4_extent *ex);
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
extern int ext4_ext_space_root_idx_test(struct inode *inode, int check);
extern struct ext4_ext_path *ext4_split_convert_extents_test(
handle_t *handle, struct inode *inode,
struct ext4_map_blocks *map,
struct ext4_ext_path *path,
int flags, unsigned int *allocated);
#endif
#endif /* _EXT4_EXTENTS */
+408
View File
@@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Interface between ext4 and JBD
*/
#include "ext4_jbd2.h"
#include <trace/events/ext4.h>
int ext4_inode_journal_mode(struct inode *inode)
{
if (EXT4_JOURNAL(inode) == NULL)
return EXT4_INODE_WRITEBACK_DATA_MODE; /* writeback */
/* We do not support data journalling with delayed allocation */
if (!S_ISREG(inode->i_mode) ||
ext4_test_inode_flag(inode, EXT4_INODE_EA_INODE) ||
test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA ||
(ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA) &&
!test_opt(inode->i_sb, DELALLOC))) {
/* We do not support data journalling for encrypted data */
if (S_ISREG(inode->i_mode) && IS_ENCRYPTED(inode))
return EXT4_INODE_ORDERED_DATA_MODE; /* ordered */
return EXT4_INODE_JOURNAL_DATA_MODE; /* journal data */
}
if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_ORDERED_DATA)
return EXT4_INODE_ORDERED_DATA_MODE; /* ordered */
if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_WRITEBACK_DATA)
return EXT4_INODE_WRITEBACK_DATA_MODE; /* writeback */
BUG();
}
/* Just increment the non-pointer handle value */
static handle_t *ext4_get_nojournal(void)
{
handle_t *handle = current->journal_info;
unsigned long ref_cnt = (unsigned long)handle;
BUG_ON(ref_cnt >= EXT4_NOJOURNAL_MAX_REF_COUNT);
ref_cnt++;
handle = (handle_t *)ref_cnt;
current->journal_info = handle;
return handle;
}
/* Decrement the non-pointer handle value */
static void ext4_put_nojournal(handle_t *handle)
{
unsigned long ref_cnt = (unsigned long)handle;
BUG_ON(ref_cnt == 0);
ref_cnt--;
handle = (handle_t *)ref_cnt;
current->journal_info = handle;
}
/*
* Wrappers for jbd2_journal_start/end.
*/
static int ext4_journal_check_start(struct super_block *sb)
{
int ret;
journal_t *journal;
might_sleep();
ret = ext4_emergency_state(sb);
if (unlikely(ret))
return ret;
if (WARN_ON_ONCE(sb_rdonly(sb)))
return -EROFS;
WARN_ON(sb->s_writers.frozen == SB_FREEZE_COMPLETE);
journal = EXT4_SB(sb)->s_journal;
/*
* Special case here: if the journal has aborted behind our
* backs (eg. EIO in the commit thread), then we still need to
* take the FS itself readonly cleanly.
*/
if (journal && is_journal_aborted(journal)) {
ext4_abort(sb, -journal->j_errno, "Detected aborted journal");
return -EROFS;
}
return 0;
}
handle_t *__ext4_journal_start_sb(struct inode *inode,
struct super_block *sb, unsigned int line,
int type, int blocks, int rsv_blocks,
int revoke_creds)
{
journal_t *journal;
int err;
if (inode)
trace_ext4_journal_start_inode(inode, blocks, rsv_blocks,
revoke_creds, type,
_RET_IP_);
else
trace_ext4_journal_start_sb(sb, blocks, rsv_blocks,
revoke_creds, type,
_RET_IP_);
err = ext4_journal_check_start(sb);
if (err < 0)
return ERR_PTR(err);
journal = EXT4_SB(sb)->s_journal;
if (!journal || (EXT4_SB(sb)->s_mount_state & EXT4_FC_REPLAY))
return ext4_get_nojournal();
return jbd2__journal_start(journal, blocks, rsv_blocks, revoke_creds,
GFP_NOFS, type, line);
}
int __ext4_journal_stop(const char *where, unsigned int line, handle_t *handle)
{
struct super_block *sb;
int err;
int rc;
if (!ext4_handle_valid(handle)) {
ext4_put_nojournal(handle);
return 0;
}
err = handle->h_err;
if (!handle->h_transaction) {
rc = jbd2_journal_stop(handle);
return err ? err : rc;
}
sb = handle->h_transaction->t_journal->j_private;
rc = jbd2_journal_stop(handle);
if (!err)
err = rc;
if (err)
__ext4_std_error(sb, where, line, err);
return err;
}
handle_t *__ext4_journal_start_reserved(handle_t *handle, unsigned int line,
int type)
{
struct super_block *sb;
int err;
if (!ext4_handle_valid(handle))
return ext4_get_nojournal();
sb = handle->h_journal->j_private;
trace_ext4_journal_start_reserved(sb,
jbd2_handle_buffer_credits(handle), _RET_IP_);
err = ext4_journal_check_start(sb);
if (err < 0) {
jbd2_journal_free_reserved(handle);
return ERR_PTR(err);
}
err = jbd2_journal_start_reserved(handle, type, line);
if (err < 0)
return ERR_PTR(err);
return handle;
}
int __ext4_journal_ensure_credits(handle_t *handle, int check_cred,
int extend_cred, int revoke_cred)
{
if (!ext4_handle_valid(handle))
return 0;
if (is_handle_aborted(handle))
return -EROFS;
if (jbd2_handle_buffer_credits(handle) >= check_cred &&
handle->h_revoke_credits >= revoke_cred)
return 0;
extend_cred = max(0, extend_cred - jbd2_handle_buffer_credits(handle));
revoke_cred = max(0, revoke_cred - handle->h_revoke_credits);
return ext4_journal_extend(handle, extend_cred, revoke_cred);
}
static void ext4_journal_abort_handle(const char *caller, unsigned int line,
const char *err_fn,
struct buffer_head *bh,
handle_t *handle, int err)
{
char nbuf[16];
const char *errstr = ext4_decode_error(NULL, err, nbuf);
BUG_ON(!ext4_handle_valid(handle));
if (bh)
BUFFER_TRACE(bh, "abort");
if (!handle->h_err)
handle->h_err = err;
if (is_handle_aborted(handle))
return;
printk(KERN_ERR "EXT4-fs: %s:%d: aborting transaction: %s in %s\n",
caller, line, errstr, err_fn);
jbd2_journal_abort_handle(handle);
}
static void ext4_check_bdev_write_error(struct super_block *sb)
{
struct address_space *mapping = sb->s_bdev->bd_mapping;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int err;
/*
* If the block device has write error flag, it may have failed to
* async write out metadata buffers in the background. In this case,
* we could read old data from disk and write it out again, which
* may lead to on-disk filesystem inconsistency.
*/
if (errseq_check(&mapping->wb_err, READ_ONCE(sbi->s_bdev_wb_err))) {
spin_lock(&sbi->s_bdev_wb_lock);
err = errseq_check_and_advance(&mapping->wb_err, &sbi->s_bdev_wb_err);
spin_unlock(&sbi->s_bdev_wb_lock);
if (err)
ext4_error_err(sb, -err,
"Error while async write back metadata");
}
}
int __ext4_journal_get_write_access(const char *where, unsigned int line,
handle_t *handle, struct super_block *sb,
struct buffer_head *bh,
enum ext4_journal_trigger_type trigger_type)
{
int err;
might_sleep();
if (ext4_handle_valid(handle)) {
err = jbd2_journal_get_write_access(handle, bh);
if (err) {
ext4_journal_abort_handle(where, line, __func__, bh,
handle, err);
return err;
}
} else
ext4_check_bdev_write_error(sb);
if (trigger_type == EXT4_JTR_NONE ||
!ext4_has_feature_metadata_csum(sb))
return 0;
BUG_ON(trigger_type >= EXT4_JOURNAL_TRIGGER_COUNT);
jbd2_journal_set_triggers(bh,
&EXT4_SB(sb)->s_journal_triggers[trigger_type].tr_triggers);
return 0;
}
/*
* The ext4 forget function must perform a revoke if we are freeing data
* which has been journaled. Metadata (eg. indirect blocks) must be
* revoked in all cases.
*
* "bh" may be NULL: a metadata block may have been freed from memory
* but there may still be a record of it in the journal, and that record
* still needs to be revoked.
*/
int __ext4_forget(const char *where, unsigned int line, handle_t *handle,
int is_metadata, struct inode *inode,
struct buffer_head *bh, ext4_fsblk_t blocknr)
{
int err;
might_sleep();
trace_ext4_forget(inode, is_metadata, blocknr);
BUFFER_TRACE(bh, "enter");
ext4_debug("forgetting bh %p: is_metadata=%d, mode %o, data mode %x\n",
bh, is_metadata, inode->i_mode,
test_opt(inode->i_sb, DATA_FLAGS));
/*
* In the no journal case, we should wait for the ongoing buffer
* to complete and do a forget.
*/
if (!ext4_handle_valid(handle)) {
if (bh) {
clear_buffer_dirty(bh);
wait_on_buffer(bh);
__bforget(bh);
}
return 0;
}
/* Never use the revoke function if we are doing full data
* journaling: there is no need to, and a V1 superblock won't
* support it. Otherwise, only skip the revoke on un-journaled
* data blocks. */
if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA ||
(!is_metadata && !ext4_should_journal_data(inode))) {
if (bh) {
BUFFER_TRACE(bh, "call jbd2_journal_forget");
err = jbd2_journal_forget(handle, bh);
if (err)
ext4_journal_abort_handle(where, line, __func__,
bh, handle, err);
return err;
}
return 0;
}
/*
* data!=journal && (is_metadata || should_journal_data(inode))
*/
BUFFER_TRACE(bh, "call jbd2_journal_revoke");
err = jbd2_journal_revoke(handle, blocknr, bh);
if (err) {
ext4_journal_abort_handle(where, line, __func__,
bh, handle, err);
__ext4_error(inode->i_sb, where, line, true, -err, 0,
"error %d when attempting revoke", err);
}
BUFFER_TRACE(bh, "exit");
return err;
}
int __ext4_journal_get_create_access(const char *where, unsigned int line,
handle_t *handle, struct super_block *sb,
struct buffer_head *bh,
enum ext4_journal_trigger_type trigger_type)
{
int err;
if (!ext4_handle_valid(handle))
return 0;
err = jbd2_journal_get_create_access(handle, bh);
if (err) {
ext4_journal_abort_handle(where, line, __func__, bh, handle,
err);
return err;
}
if (trigger_type == EXT4_JTR_NONE ||
!ext4_has_feature_metadata_csum(sb))
return 0;
BUG_ON(trigger_type >= EXT4_JOURNAL_TRIGGER_COUNT);
jbd2_journal_set_triggers(bh,
&EXT4_SB(sb)->s_journal_triggers[trigger_type].tr_triggers);
return 0;
}
int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct buffer_head *bh)
{
int err = 0;
might_sleep();
set_buffer_meta(bh);
set_buffer_prio(bh);
set_buffer_uptodate(bh);
if (ext4_handle_valid(handle)) {
err = jbd2_journal_dirty_metadata(handle, bh);
/* Errors can only happen due to aborted journal or a nasty bug */
if (!is_handle_aborted(handle) && WARN_ON_ONCE(err)) {
ext4_journal_abort_handle(where, line, __func__, bh,
handle, err);
if (inode == NULL) {
pr_err("EXT4: jbd2_journal_dirty_metadata "
"failed: handle type %u started at "
"line %u, credits %u/%u, errcode %d",
handle->h_type,
handle->h_line_no,
handle->h_requested_credits,
jbd2_handle_buffer_credits(handle), err);
return err;
}
ext4_error_inode(inode, where, line,
bh->b_blocknr,
"journal_dirty_metadata failed: "
"handle type %u started at line %u, "
"credits %u/%u, errcode %d",
handle->h_type,
handle->h_line_no,
handle->h_requested_credits,
jbd2_handle_buffer_credits(handle),
err);
}
} else {
if (inode)
mmb_mark_buffer_dirty(bh,
&EXT4_I(inode)->i_metadata_bhs);
else
mark_buffer_dirty(bh);
if (inode && inode_needs_sync(inode)) {
sync_dirty_buffer(bh);
if (buffer_req(bh) && !buffer_uptodate(bh)) {
ext4_error_inode_err(inode, where, line,
bh->b_blocknr, EIO,
"IO error syncing itable block");
err = -EIO;
}
}
}
return err;
}
+461
View File
@@ -0,0 +1,461 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ext4_jbd2.h
*
* Written by Stephen C. Tweedie <sct@redhat.com>, 1999
*
* Copyright 1998--1999 Red Hat corp --- All Rights Reserved
*
* Ext4-specific journaling extensions.
*/
#ifndef _EXT4_JBD2_H
#define _EXT4_JBD2_H
#include <linux/fs.h>
#include <linux/jbd2.h>
#include "ext4.h"
#define EXT4_JOURNAL(inode) (EXT4_SB((inode)->i_sb)->s_journal)
/* Define the number of blocks we need to account to a transaction to
* modify one block of data.
*
* We may have to touch one inode, one bitmap buffer, up to three
* indirection blocks, the group and superblock summaries, and the data
* block to complete the transaction.
*
* For extents-enabled fs we may have to allocate and modify up to
* 5 levels of tree, data block (for each of these we need bitmap + group
* summaries), root which is stored in the inode, sb
*/
#define EXT4_SINGLEDATA_TRANS_BLOCKS(sb) \
(ext4_has_feature_extents(sb) ? 20U : 8U)
/* Extended attribute operations touch at most two data buffers,
* two bitmap buffers, and two group summaries, in addition to the inode
* and the superblock, which are already accounted for. */
#define EXT4_XATTR_TRANS_BLOCKS 6U
/* Define the minimum size for a transaction which modifies data. This
* needs to take into account the fact that we may end up modifying two
* quota files too (one for the group, one for the user quota). The
* superblock only gets updated once, of course, so don't bother
* counting that again for the quota updates. */
#define EXT4_DATA_TRANS_BLOCKS(sb) (EXT4_SINGLEDATA_TRANS_BLOCKS(sb) + \
EXT4_XATTR_TRANS_BLOCKS - 2 + \
EXT4_MAXQUOTAS_TRANS_BLOCKS(sb))
/*
* Define the number of metadata blocks we need to account to modify data.
*
* This include super block, inode block, quota blocks and xattr blocks
*/
#define EXT4_META_TRANS_BLOCKS(sb) (EXT4_XATTR_TRANS_BLOCKS + \
EXT4_MAXQUOTAS_TRANS_BLOCKS(sb))
/* Define an arbitrary limit for the amount of data we will anticipate
* writing to any given transaction. For unbounded transactions such as
* write(2) and truncate(2) we can write more than this, but we always
* start off at the maximum transaction size and grow the transaction
* optimistically as we go. */
#define EXT4_MAX_TRANS_DATA 64U
/* We break up a large truncate or write transaction once the handle's
* buffer credits gets this low, we need either to extend the
* transaction or to start a new one. Reserve enough space here for
* inode, bitmap, superblock, group and indirection updates for at least
* one block, plus two quota updates. Quota allocations are not
* needed. */
#define EXT4_RESERVE_TRANS_BLOCKS 12U
/*
* Number of credits needed if we need to insert an entry into a
* directory. For each new index block, we need 4 blocks (old index
* block, new index block, bitmap block, bg summary). For normal
* htree directories there are 2 levels; if the largedir feature
* enabled it's 3 levels.
*/
#define EXT4_INDEX_EXTRA_TRANS_BLOCKS 12U
#ifdef CONFIG_QUOTA
/* Amount of blocks needed for quota update - we know that the structure was
* allocated so we need to update only data block */
#define EXT4_QUOTA_TRANS_BLOCKS(sb) ((ext4_quota_capable(sb)) ? 1 : 0)
/* Amount of blocks needed for quota insert/delete - we do some block writes
* but inode, sb and group updates are done only once */
#define EXT4_QUOTA_INIT_BLOCKS(sb) ((ext4_quota_capable(sb)) ?\
(DQUOT_INIT_ALLOC*(EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)\
+3+DQUOT_INIT_REWRITE) : 0)
#define EXT4_QUOTA_DEL_BLOCKS(sb) ((ext4_quota_capable(sb)) ?\
(DQUOT_DEL_ALLOC*(EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)\
+3+DQUOT_DEL_REWRITE) : 0)
#else
#define EXT4_QUOTA_TRANS_BLOCKS(sb) 0
#define EXT4_QUOTA_INIT_BLOCKS(sb) 0
#define EXT4_QUOTA_DEL_BLOCKS(sb) 0
#endif
#define EXT4_MAXQUOTAS_TRANS_BLOCKS(sb) (EXT4_MAXQUOTAS*EXT4_QUOTA_TRANS_BLOCKS(sb))
#define EXT4_MAXQUOTAS_INIT_BLOCKS(sb) (EXT4_MAXQUOTAS*EXT4_QUOTA_INIT_BLOCKS(sb))
#define EXT4_MAXQUOTAS_DEL_BLOCKS(sb) (EXT4_MAXQUOTAS*EXT4_QUOTA_DEL_BLOCKS(sb))
/*
* Ext4 handle operation types -- for logging purposes
*/
#define EXT4_HT_MISC 0
#define EXT4_HT_INODE 1
#define EXT4_HT_WRITE_PAGE 2
#define EXT4_HT_MAP_BLOCKS 3
#define EXT4_HT_DIR 4
#define EXT4_HT_TRUNCATE 5
#define EXT4_HT_QUOTA 6
#define EXT4_HT_RESIZE 7
#define EXT4_HT_MIGRATE 8
#define EXT4_HT_MOVE_EXTENTS 9
#define EXT4_HT_XATTR 10
#define EXT4_HT_EXT_CONVERT 11
#define EXT4_HT_MAX 12
int
ext4_mark_iloc_dirty(handle_t *handle,
struct inode *inode,
struct ext4_iloc *iloc);
/*
* On success, We end up with an outstanding reference count against
* iloc->bh. This _must_ be cleaned up later.
*/
int ext4_reserve_inode_write(handle_t *handle, struct inode *inode,
struct ext4_iloc *iloc);
#define ext4_mark_inode_dirty(__h, __i) \
__ext4_mark_inode_dirty((__h), (__i), __func__, __LINE__)
int __ext4_mark_inode_dirty(handle_t *handle, struct inode *inode,
const char *func, unsigned int line);
int ext4_expand_extra_isize(struct inode *inode,
unsigned int new_extra_isize,
struct ext4_iloc *iloc);
/*
* Wrapper functions with which ext4 calls into JBD.
*/
int __ext4_journal_get_write_access(const char *where, unsigned int line,
handle_t *handle, struct super_block *sb,
struct buffer_head *bh,
enum ext4_journal_trigger_type trigger_type);
int __ext4_forget(const char *where, unsigned int line, handle_t *handle,
int is_metadata, struct inode *inode,
struct buffer_head *bh, ext4_fsblk_t blocknr);
int __ext4_journal_get_create_access(const char *where, unsigned int line,
handle_t *handle, struct super_block *sb,
struct buffer_head *bh,
enum ext4_journal_trigger_type trigger_type);
int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct buffer_head *bh);
#define ext4_journal_get_write_access(handle, sb, bh, trigger_type) \
__ext4_journal_get_write_access(__func__, __LINE__, (handle), (sb), \
(bh), (trigger_type))
#define ext4_forget(handle, is_metadata, inode, bh, block_nr) \
__ext4_forget(__func__, __LINE__, (handle), (is_metadata), (inode), \
(bh), (block_nr))
#define ext4_journal_get_create_access(handle, sb, bh, trigger_type) \
__ext4_journal_get_create_access(__func__, __LINE__, (handle), (sb), \
(bh), (trigger_type))
#define ext4_handle_dirty_metadata(handle, inode, bh) \
__ext4_handle_dirty_metadata(__func__, __LINE__, (handle), (inode), \
(bh))
handle_t *__ext4_journal_start_sb(struct inode *inode, struct super_block *sb,
unsigned int line, int type, int blocks,
int rsv_blocks, int revoke_creds);
int __ext4_journal_stop(const char *where, unsigned int line, handle_t *handle);
#define EXT4_NOJOURNAL_MAX_REF_COUNT ((unsigned long) 4096)
/* Note: Do not use this for NULL handles. This is only to determine if
* a properly allocated handle is using a journal or not. */
static inline int ext4_handle_valid(handle_t *handle)
{
if ((unsigned long)handle < EXT4_NOJOURNAL_MAX_REF_COUNT)
return 0;
return 1;
}
static inline void ext4_handle_sync(handle_t *handle)
{
if (ext4_handle_valid(handle))
handle->h_sync = 1;
}
static inline int ext4_handle_is_aborted(handle_t *handle)
{
if (ext4_handle_valid(handle))
return is_handle_aborted(handle);
return 0;
}
static inline int ext4_free_metadata_revoke_credits(struct super_block *sb,
int blocks)
{
/* Freeing each metadata block can result in freeing one cluster */
return blocks * EXT4_SB(sb)->s_cluster_ratio;
}
static inline int ext4_trans_default_revoke_credits(struct super_block *sb)
{
return ext4_free_metadata_revoke_credits(sb, 8);
}
#define ext4_journal_start_sb(sb, type, nblocks) \
__ext4_journal_start_sb(NULL, (sb), __LINE__, (type), (nblocks), 0,\
ext4_trans_default_revoke_credits(sb))
#define ext4_journal_start(inode, type, nblocks) \
__ext4_journal_start((inode), __LINE__, (type), (nblocks), 0, \
ext4_trans_default_revoke_credits((inode)->i_sb))
#define ext4_journal_start_with_reserve(inode, type, blocks, rsv_blocks)\
__ext4_journal_start((inode), __LINE__, (type), (blocks), (rsv_blocks),\
ext4_trans_default_revoke_credits((inode)->i_sb))
#define ext4_journal_start_with_revoke(inode, type, blocks, revoke_creds) \
__ext4_journal_start((inode), __LINE__, (type), (blocks), 0, \
(revoke_creds))
static inline handle_t *__ext4_journal_start(struct inode *inode,
unsigned int line, int type,
int blocks, int rsv_blocks,
int revoke_creds)
{
return __ext4_journal_start_sb(inode, inode->i_sb, line, type, blocks,
rsv_blocks, revoke_creds);
}
#define ext4_journal_stop(handle) \
__ext4_journal_stop(__func__, __LINE__, (handle))
#define ext4_journal_start_reserved(handle, type) \
__ext4_journal_start_reserved((handle), __LINE__, (type))
handle_t *__ext4_journal_start_reserved(handle_t *handle, unsigned int line,
int type);
static inline handle_t *ext4_journal_current_handle(void)
{
return journal_current_handle();
}
static inline int ext4_journal_extend(handle_t *handle, int nblocks, int revoke)
{
if (ext4_handle_valid(handle))
return jbd2_journal_extend(handle, nblocks, revoke);
return 0;
}
static inline int ext4_journal_restart(handle_t *handle, int nblocks,
int revoke)
{
if (ext4_handle_valid(handle))
return jbd2__journal_restart(handle, nblocks, revoke, GFP_NOFS);
return 0;
}
int __ext4_journal_ensure_credits(handle_t *handle, int check_cred,
int extend_cred, int revoke_cred);
/*
* Ensure @handle has at least @check_creds credits available. If not,
* transaction will be extended or restarted to contain at least @extend_cred
* credits. Before restarting transaction @fn is executed to allow for cleanup
* before the transaction is restarted.
*
* The return value is < 0 in case of error, 0 in case the handle has enough
* credits or transaction extension succeeded, 1 in case transaction had to be
* restarted.
*/
#define ext4_journal_ensure_credits_fn(handle, check_cred, extend_cred, \
revoke_cred, fn) \
({ \
__label__ __ensure_end; \
int err = __ext4_journal_ensure_credits((handle), (check_cred), \
(extend_cred), (revoke_cred)); \
\
if (err <= 0) \
goto __ensure_end; \
err = (fn); \
if (err < 0) \
goto __ensure_end; \
err = ext4_journal_restart((handle), (extend_cred), (revoke_cred)); \
if (err == 0) \
err = 1; \
__ensure_end: \
err; \
})
/*
* Ensure given handle has at least requested amount of credits available,
* possibly restarting transaction if needed. We also make sure the transaction
* has space for at least ext4_trans_default_revoke_credits(sb) revoke records
* as freeing one or two blocks is very common pattern and requesting this is
* very cheap.
*/
static inline int ext4_journal_ensure_credits(handle_t *handle, int credits,
int revoke_creds)
{
return ext4_journal_ensure_credits_fn(handle, credits, credits,
revoke_creds, 0);
}
static inline int ext4_journal_blocks_per_folio(struct inode *inode)
{
if (EXT4_JOURNAL(inode) != NULL)
return jbd2_journal_blocks_per_folio(inode);
return 0;
}
static inline int ext4_journal_force_commit(journal_t *journal)
{
if (journal)
return jbd2_journal_force_commit(journal);
return 0;
}
static inline int ext4_jbd2_inode_add_write(handle_t *handle,
struct inode *inode, loff_t start_byte, loff_t length)
{
if (ext4_handle_valid(handle))
return jbd2_journal_inode_ranged_write(handle,
EXT4_I(inode)->jinode, start_byte, length);
return 0;
}
static inline int ext4_jbd2_inode_add_wait(handle_t *handle,
struct inode *inode, loff_t start_byte, loff_t length)
{
if (ext4_handle_valid(handle))
return jbd2_journal_inode_ranged_wait(handle,
EXT4_I(inode)->jinode, start_byte, length);
return 0;
}
static inline void ext4_update_inode_fsync_trans(handle_t *handle,
struct inode *inode,
int datasync)
{
struct ext4_inode_info *ei = EXT4_I(inode);
if (ext4_handle_valid(handle) && !is_handle_aborted(handle)) {
ei->i_sync_tid = handle->h_transaction->t_tid;
if (datasync)
ei->i_datasync_tid = handle->h_transaction->t_tid;
}
}
/* super.c */
int ext4_force_commit(struct super_block *sb);
/*
* Ext4 inode journal modes
*/
#define EXT4_INODE_JOURNAL_DATA_MODE 0x01 /* journal data mode */
#define EXT4_INODE_ORDERED_DATA_MODE 0x02 /* ordered data mode */
#define EXT4_INODE_WRITEBACK_DATA_MODE 0x04 /* writeback data mode */
int ext4_inode_journal_mode(struct inode *inode);
static inline int ext4_should_journal_data(struct inode *inode)
{
return ext4_inode_journal_mode(inode) & EXT4_INODE_JOURNAL_DATA_MODE;
}
static inline int ext4_should_order_data(struct inode *inode)
{
return ext4_inode_journal_mode(inode) & EXT4_INODE_ORDERED_DATA_MODE;
}
static inline int ext4_should_writeback_data(struct inode *inode)
{
return ext4_inode_journal_mode(inode) & EXT4_INODE_WRITEBACK_DATA_MODE;
}
static inline int ext4_free_data_revoke_credits(struct inode *inode, int blocks)
{
if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA)
return 0;
if (!ext4_should_journal_data(inode))
return 0;
/*
* Data blocks in one extent are contiguous, just account for partial
* clusters at extent boundaries
*/
return blocks + 2*(EXT4_SB(inode->i_sb)->s_cluster_ratio - 1);
}
/*
* This function controls whether or not we should try to go down the
* dioread_nolock code paths, which makes it safe to avoid taking
* i_rwsem for direct I/O reads. This only works for extent-based
* files, and it doesn't work if data journaling is enabled, since the
* dioread_nolock code uses b_private to pass information back to the
* I/O completion handler, and this conflicts with the jbd's use of
* b_private.
*/
static inline int ext4_should_dioread_nolock(struct inode *inode)
{
if (!test_opt(inode->i_sb, DIOREAD_NOLOCK))
return 0;
if (!S_ISREG(inode->i_mode))
return 0;
if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
return 0;
if (ext4_should_journal_data(inode))
return 0;
/* temporary fix to prevent generic/422 test failures */
if (!test_opt(inode->i_sb, DELALLOC))
return 0;
return 1;
}
/*
* Pass journal explicitly as it may not be cached in the sbi->s_journal in some
* cases
*/
static inline int ext4_journal_destroy(struct ext4_sb_info *sbi, journal_t *journal)
{
int err = 0;
/*
* At this point only two things can be operating on the journal.
* JBD2 thread performing transaction commit and s_sb_upd_work
* issuing sb update through the journal. Once we set
* EXT4_JOURNAL_DESTROY, new ext4_handle_error() calls will not
* queue s_sb_upd_work and ext4_force_commit() makes sure any
* ext4_handle_error() calls from the running transaction commit are
* finished. Hence no new s_sb_upd_work can be queued after we
* flush it here.
*/
ext4_set_mount_flag(sbi->s_sb, EXT4_MF_JOURNAL_DESTROY);
ext4_force_commit(sbi->s_sb);
flush_work(&sbi->s_sb_upd_work);
err = jbd2_journal_destroy(journal);
sbi->s_journal = NULL;
return err;
}
#endif /* _EXT4_JBD2_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0
/*
* fs/ext4/extents_status.h
*
* Written by Yongqiang Yang <xiaoqiangnk@gmail.com>
* Modified by
* Allison Henderson <achender@linux.vnet.ibm.com>
* Zheng Liu <wenqing.lz@taobao.com>
*
*/
#ifndef _EXT4_EXTENTS_STATUS_H
#define _EXT4_EXTENTS_STATUS_H
/*
* Turn on ES_DEBUG__ to get lots of info about extent status operations.
*/
#ifdef ES_DEBUG__
#define es_debug(fmt, ...) printk(fmt, ##__VA_ARGS__)
#else
#define es_debug(fmt, ...) no_printk(fmt, ##__VA_ARGS__)
#endif
/*
* With ES_AGGRESSIVE_TEST defined, the result of es caching will be
* checked with old map_block's result.
*/
#define ES_AGGRESSIVE_TEST__
/*
* These flags live in the high bits of extent_status.es_pblk
*/
enum {
ES_WRITTEN_B,
ES_UNWRITTEN_B,
ES_DELAYED_B,
ES_HOLE_B,
ES_REFERENCED_B,
ES_FLAGS
};
#define ES_SHIFT (sizeof(ext4_fsblk_t)*8 - ES_FLAGS)
#define ES_MASK (~((ext4_fsblk_t)0) << ES_SHIFT)
/*
* Besides EXTENT_STATUS_REFERENCED, all these extent type masks
* are exclusive, only one type can be set at a time.
*/
#define EXTENT_STATUS_WRITTEN (1 << ES_WRITTEN_B)
#define EXTENT_STATUS_UNWRITTEN (1 << ES_UNWRITTEN_B)
#define EXTENT_STATUS_DELAYED (1 << ES_DELAYED_B)
#define EXTENT_STATUS_HOLE (1 << ES_HOLE_B)
#define EXTENT_STATUS_REFERENCED (1 << ES_REFERENCED_B)
#define ES_TYPE_MASK ((ext4_fsblk_t)(EXTENT_STATUS_WRITTEN | \
EXTENT_STATUS_UNWRITTEN | \
EXTENT_STATUS_DELAYED | \
EXTENT_STATUS_HOLE))
#define ES_TYPE_VALID(type) ((type) && !((type) & ((type) - 1)))
struct ext4_sb_info;
struct ext4_extent;
struct extent_status {
struct rb_node rb_node;
ext4_lblk_t es_lblk; /* first logical block extent covers */
ext4_lblk_t es_len; /* length of extent in block */
ext4_fsblk_t es_pblk; /* first physical block */
};
struct ext4_es_tree {
struct rb_root root;
struct extent_status *cache_es; /* recently accessed extent */
};
struct ext4_es_stats {
unsigned long es_stats_shrunk;
struct percpu_counter es_stats_cache_hits;
struct percpu_counter es_stats_cache_misses;
u64 es_stats_scan_time;
u64 es_stats_max_scan_time;
struct percpu_counter es_stats_all_cnt;
struct percpu_counter es_stats_shk_cnt;
};
/*
* Pending cluster reservations for bigalloc file systems
*
* A cluster with a pending reservation is a logical cluster shared by at
* least one extent in the extents status tree with delayed and unwritten
* status and at least one other written or unwritten extent. The
* reservation is said to be pending because a cluster reservation would
* have to be taken in the event all blocks in the cluster shared with
* written or unwritten extents were deleted while the delayed and
* unwritten blocks remained.
*
* The set of pending cluster reservations is an auxiliary data structure
* used with the extents status tree to implement reserved cluster/block
* accounting for bigalloc file systems. The set is kept in memory and
* records all pending cluster reservations.
*
* Its primary function is to avoid the need to read extents from the
* disk when invalidating pages as a result of a truncate, punch hole, or
* collapse range operation. Page invalidation requires a decrease in the
* reserved cluster count if it results in the removal of all delayed
* and unwritten extents (blocks) from a cluster that is not shared with a
* written or unwritten extent, and no decrease otherwise. Determining
* whether the cluster is shared can be done by searching for a pending
* reservation on it.
*
* Secondarily, it provides a potentially faster method for determining
* whether the reserved cluster count should be increased when a physical
* cluster is deallocated as a result of a truncate, punch hole, or
* collapse range operation. The necessary information is also present
* in the extents status tree, but might be more rapidly accessed in
* the pending reservation set in many cases due to smaller size.
*
* The pending cluster reservation set is implemented as a red-black tree
* with the goal of minimizing per page search time overhead.
*/
struct pending_reservation {
struct rb_node rb_node;
ext4_lblk_t lclu;
};
struct ext4_pending_tree {
struct rb_root root;
};
extern int __init ext4_init_es(void);
extern void ext4_exit_es(void);
extern void ext4_es_init_tree(struct ext4_es_tree *tree);
extern void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len, ext4_fsblk_t pblk,
unsigned int status,
bool delalloc_reserve_used);
extern void ext4_es_cache_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len, ext4_fsblk_t pblk,
unsigned int status);
extern void ext4_es_remove_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len);
extern void ext4_es_find_extent_range(struct inode *inode,
int (*match_fn)(struct extent_status *es),
ext4_lblk_t lblk, ext4_lblk_t end,
struct extent_status *es);
extern int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t *next_lblk,
struct extent_status *es, u64 *pseq);
extern bool ext4_es_scan_range(struct inode *inode,
int (*matching_fn)(struct extent_status *es),
ext4_lblk_t lblk, ext4_lblk_t end);
extern bool ext4_es_scan_clu(struct inode *inode,
int (*matching_fn)(struct extent_status *es),
ext4_lblk_t lblk);
static inline unsigned int ext4_es_status(struct extent_status *es)
{
return es->es_pblk >> ES_SHIFT;
}
static inline unsigned int ext4_es_type(struct extent_status *es)
{
return (es->es_pblk >> ES_SHIFT) & ES_TYPE_MASK;
}
static inline int ext4_es_is_written(struct extent_status *es)
{
return (ext4_es_type(es) & EXTENT_STATUS_WRITTEN) != 0;
}
static inline int ext4_es_is_unwritten(struct extent_status *es)
{
return (ext4_es_type(es) & EXTENT_STATUS_UNWRITTEN) != 0;
}
static inline int ext4_es_is_delayed(struct extent_status *es)
{
return (ext4_es_type(es) & EXTENT_STATUS_DELAYED) != 0;
}
static inline int ext4_es_is_hole(struct extent_status *es)
{
return (ext4_es_type(es) & EXTENT_STATUS_HOLE) != 0;
}
static inline int ext4_es_is_mapped(struct extent_status *es)
{
return (ext4_es_is_written(es) || ext4_es_is_unwritten(es));
}
static inline void ext4_es_set_referenced(struct extent_status *es)
{
es->es_pblk |= ((ext4_fsblk_t)EXTENT_STATUS_REFERENCED) << ES_SHIFT;
}
static inline void ext4_es_clear_referenced(struct extent_status *es)
{
es->es_pblk &= ~(((ext4_fsblk_t)EXTENT_STATUS_REFERENCED) << ES_SHIFT);
}
static inline int ext4_es_is_referenced(struct extent_status *es)
{
return (ext4_es_status(es) & EXTENT_STATUS_REFERENCED) != 0;
}
static inline ext4_fsblk_t ext4_es_pblock(struct extent_status *es)
{
return es->es_pblk & ~ES_MASK;
}
static inline ext4_fsblk_t ext4_es_show_pblock(struct extent_status *es)
{
ext4_fsblk_t pblock = ext4_es_pblock(es);
return pblock == ~ES_MASK ? 0 : pblock;
}
static inline void ext4_es_store_pblock(struct extent_status *es,
ext4_fsblk_t pb)
{
ext4_fsblk_t block;
block = (pb & ~ES_MASK) | (es->es_pblk & ES_MASK);
es->es_pblk = block;
}
static inline void ext4_es_store_pblock_status(struct extent_status *es,
ext4_fsblk_t pb,
unsigned int status)
{
WARN_ON_ONCE(!ES_TYPE_VALID(status & ES_TYPE_MASK));
es->es_pblk = (((ext4_fsblk_t)status << ES_SHIFT) & ES_MASK) |
(pb & ~ES_MASK);
}
extern int ext4_es_register_shrinker(struct ext4_sb_info *sbi);
extern void ext4_es_unregister_shrinker(struct ext4_sb_info *sbi);
extern int ext4_seq_es_shrinker_info_show(struct seq_file *seq, void *v);
extern int __init ext4_init_pending(void);
extern void ext4_exit_pending(void);
extern void ext4_init_pending_tree(struct ext4_pending_tree *tree);
extern void ext4_remove_pending(struct inode *inode, ext4_lblk_t lblk);
extern bool ext4_is_pending(struct inode *inode, ext4_lblk_t lblk);
extern void ext4_es_insert_delayed_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len, bool lclu_allocated,
bool end_allocated);
extern void ext4_clear_inode_es(struct inode *inode);
#endif /* _EXT4_EXTENTS_STATUS_H */
File diff suppressed because it is too large Load Diff
+189
View File
@@ -0,0 +1,189 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __FAST_COMMIT_H__
#define __FAST_COMMIT_H__
/*
* Note this file is present in e2fsprogs/lib/ext2fs/fast_commit.h and
* linux/fs/ext4/fast_commit.h. These file should always be byte identical.
*/
/* Fast commit tags */
#define EXT4_FC_TAG_ADD_RANGE 0x0001
#define EXT4_FC_TAG_DEL_RANGE 0x0002
#define EXT4_FC_TAG_CREAT 0x0003
#define EXT4_FC_TAG_LINK 0x0004
#define EXT4_FC_TAG_UNLINK 0x0005
#define EXT4_FC_TAG_INODE 0x0006
#define EXT4_FC_TAG_PAD 0x0007
#define EXT4_FC_TAG_TAIL 0x0008
#define EXT4_FC_TAG_HEAD 0x0009
#define EXT4_FC_SUPPORTED_FEATURES 0x0
/* On disk fast commit tlv value structures */
/* Fast commit on disk tag length structure */
struct ext4_fc_tl {
__le16 fc_tag;
__le16 fc_len;
};
/* Value structure for tag EXT4_FC_TAG_HEAD. */
struct ext4_fc_head {
__le32 fc_features;
__le32 fc_tid;
};
/* Value structure for EXT4_FC_TAG_ADD_RANGE. */
struct ext4_fc_add_range {
__le32 fc_ino;
__u8 fc_ex[12];
};
/* Value structure for tag EXT4_FC_TAG_DEL_RANGE. */
struct ext4_fc_del_range {
__le32 fc_ino;
__le32 fc_lblk;
__le32 fc_len;
};
/*
* This is the value structure for tags EXT4_FC_TAG_CREAT, EXT4_FC_TAG_LINK
* and EXT4_FC_TAG_UNLINK.
*/
struct ext4_fc_dentry_info {
__le32 fc_parent_ino;
__le32 fc_ino;
__u8 fc_dname[];
};
/* Value structure for EXT4_FC_TAG_INODE. */
struct ext4_fc_inode {
__le32 fc_ino;
__u8 fc_raw_inode[];
};
/* Value structure for tag EXT4_FC_TAG_TAIL. */
struct ext4_fc_tail {
__le32 fc_tid;
__le32 fc_crc;
};
/* Tag base length */
#define EXT4_FC_TAG_BASE_LEN (sizeof(struct ext4_fc_tl))
/*
* Fast commit status codes
*/
enum {
EXT4_FC_STATUS_OK = 0,
EXT4_FC_STATUS_INELIGIBLE,
EXT4_FC_STATUS_SKIPPED,
EXT4_FC_STATUS_FAILED,
};
/*
* Fast commit ineligiblity reasons:
*/
enum {
EXT4_FC_REASON_XATTR = 0,
EXT4_FC_REASON_CROSS_RENAME,
EXT4_FC_REASON_JOURNAL_FLAG_CHANGE,
EXT4_FC_REASON_NOMEM,
EXT4_FC_REASON_SWAP_BOOT,
EXT4_FC_REASON_RESIZE,
EXT4_FC_REASON_RENAME_DIR,
EXT4_FC_REASON_FALLOC_RANGE,
EXT4_FC_REASON_INODE_JOURNAL_DATA,
EXT4_FC_REASON_ENCRYPTED_FILENAME,
EXT4_FC_REASON_MIGRATE,
EXT4_FC_REASON_VERITY,
EXT4_FC_REASON_MOVE_EXT,
EXT4_FC_REASON_MAX
};
#ifdef __KERNEL__
/*
* In memory list of dentry updates that are performed on the file
* system used by fast commit code.
*/
struct ext4_fc_dentry_update {
int fcd_op; /* Type of update create / unlink / link */
int fcd_parent; /* Parent inode number */
int fcd_ino; /* Inode number */
struct name_snapshot fcd_name; /* Dirent name */
struct list_head fcd_list;
struct list_head fcd_dilist;
};
struct ext4_fc_stats {
unsigned int fc_ineligible_reason_count[EXT4_FC_REASON_MAX];
unsigned long fc_num_commits;
unsigned long fc_ineligible_commits;
unsigned long fc_failed_commits;
unsigned long fc_skipped_commits;
unsigned long fc_numblks;
u64 s_fc_avg_commit_time;
};
#define EXT4_FC_REPLAY_REALLOC_INCREMENT 4
/*
* Physical block regions added to different inodes due to fast commit
* recovery. These are set during the SCAN phase. During the replay phase,
* our allocator excludes these from its allocation. This ensures that
* we don't accidentally allocating a block that is going to be used by
* another inode.
*/
struct ext4_fc_alloc_region {
ext4_lblk_t lblk;
ext4_fsblk_t pblk;
int ino, len;
};
/*
* Fast commit replay state.
*/
struct ext4_fc_replay_state {
int fc_replay_num_tags;
int fc_replay_expected_off;
int fc_current_pass;
int fc_cur_tag;
int fc_crc;
struct ext4_fc_alloc_region *fc_regions;
int fc_regions_size, fc_regions_used, fc_regions_valid;
int *fc_modified_inodes;
int fc_modified_inodes_used, fc_modified_inodes_size;
};
#define region_last(__region) (((__region)->lblk) + ((__region)->len) - 1)
#endif
static inline const char *tag2str(__u16 tag)
{
switch (tag) {
case EXT4_FC_TAG_LINK:
return "ADD_ENTRY";
case EXT4_FC_TAG_UNLINK:
return "DEL_ENTRY";
case EXT4_FC_TAG_ADD_RANGE:
return "ADD_RANGE";
case EXT4_FC_TAG_CREAT:
return "CREAT_DENTRY";
case EXT4_FC_TAG_DEL_RANGE:
return "DEL_RANGE";
case EXT4_FC_TAG_INODE:
return "INODE";
case EXT4_FC_TAG_PAD:
return "PAD";
case EXT4_FC_TAG_TAIL:
return "TAIL";
case EXT4_FC_TAG_HEAD:
return "HEAD";
default:
return "ERROR";
}
}
#endif /* __FAST_COMMIT_H__ */
File diff suppressed because it is too large Load Diff
+792
View File
@@ -0,0 +1,792 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Oracle. All Rights Reserved.
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "ext4.h"
#include <linux/fsmap.h>
#include "fsmap.h"
#include "mballoc.h"
#include <linux/sort.h>
#include <linux/list_sort.h>
#include <trace/events/ext4.h>
/* Convert an ext4_fsmap to an fsmap. */
void ext4_fsmap_from_internal(struct super_block *sb, struct fsmap *dest,
struct ext4_fsmap *src)
{
dest->fmr_device = src->fmr_device;
dest->fmr_flags = src->fmr_flags;
dest->fmr_physical = src->fmr_physical << sb->s_blocksize_bits;
dest->fmr_owner = src->fmr_owner;
dest->fmr_offset = 0;
dest->fmr_length = src->fmr_length << sb->s_blocksize_bits;
dest->fmr_reserved[0] = 0;
dest->fmr_reserved[1] = 0;
dest->fmr_reserved[2] = 0;
}
/* Convert an fsmap to an ext4_fsmap. */
void ext4_fsmap_to_internal(struct super_block *sb, struct ext4_fsmap *dest,
struct fsmap *src)
{
dest->fmr_device = src->fmr_device;
dest->fmr_flags = src->fmr_flags;
dest->fmr_physical = src->fmr_physical >> sb->s_blocksize_bits;
dest->fmr_owner = src->fmr_owner;
dest->fmr_length = src->fmr_length >> sb->s_blocksize_bits;
}
/* getfsmap query state */
struct ext4_getfsmap_info {
struct ext4_fsmap_head *gfi_head;
ext4_fsmap_format_t gfi_formatter; /* formatting fn */
void *gfi_format_arg;/* format buffer */
ext4_fsblk_t gfi_next_fsblk; /* next fsblock we expect */
u32 gfi_dev; /* device id */
ext4_group_t gfi_agno; /* bg number, if applicable */
struct ext4_fsmap gfi_low; /* low rmap key */
struct ext4_fsmap gfi_high; /* high rmap key */
struct ext4_fsmap gfi_lastfree; /* free ext at end of last bg */
struct list_head gfi_meta_list; /* fixed metadata list */
bool gfi_last; /* last extent? */
};
/* Associate a device with a getfsmap handler. */
struct ext4_getfsmap_dev {
int (*gfd_fn)(struct super_block *sb,
struct ext4_fsmap *keys,
struct ext4_getfsmap_info *info);
u32 gfd_dev;
};
/* Compare two getfsmap device handlers. */
static int ext4_getfsmap_dev_compare(const void *p1, const void *p2)
{
const struct ext4_getfsmap_dev *d1 = p1;
const struct ext4_getfsmap_dev *d2 = p2;
return d1->gfd_dev - d2->gfd_dev;
}
/* Compare a record against our starting point */
static bool ext4_getfsmap_rec_before_low_key(struct ext4_getfsmap_info *info,
struct ext4_fsmap *rec)
{
return rec->fmr_physical + rec->fmr_length <=
info->gfi_low.fmr_physical;
}
/*
* Format a reverse mapping for getfsmap, having translated rm_startblock
* into the appropriate daddr units.
*/
static int ext4_getfsmap_helper(struct super_block *sb,
struct ext4_getfsmap_info *info,
struct ext4_fsmap *rec)
{
struct ext4_fsmap fmr;
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t rec_fsblk = rec->fmr_physical;
ext4_group_t agno;
ext4_grpblk_t cno;
int error;
if (fatal_signal_pending(current))
return -EINTR;
/*
* Filter out records that start before our startpoint, if the
* caller requested that.
*/
if (ext4_getfsmap_rec_before_low_key(info, rec)) {
rec_fsblk += rec->fmr_length;
if (info->gfi_next_fsblk < rec_fsblk)
info->gfi_next_fsblk = rec_fsblk;
return EXT4_QUERY_RANGE_CONTINUE;
}
/* Are we just counting mappings? */
if (info->gfi_head->fmh_count == 0) {
if (info->gfi_head->fmh_entries == UINT_MAX)
return EXT4_QUERY_RANGE_ABORT;
if (rec_fsblk > info->gfi_next_fsblk)
info->gfi_head->fmh_entries++;
if (info->gfi_last)
return EXT4_QUERY_RANGE_CONTINUE;
info->gfi_head->fmh_entries++;
rec_fsblk += rec->fmr_length;
if (info->gfi_next_fsblk < rec_fsblk)
info->gfi_next_fsblk = rec_fsblk;
return EXT4_QUERY_RANGE_CONTINUE;
}
/*
* If the record starts past the last physical block we saw,
* then we've found a gap. Report the gap as being owned by
* whatever the caller specified is the missing owner.
*/
if (rec_fsblk > info->gfi_next_fsblk) {
if (info->gfi_head->fmh_entries >= info->gfi_head->fmh_count)
return EXT4_QUERY_RANGE_ABORT;
ext4_get_group_no_and_offset(sb, info->gfi_next_fsblk,
&agno, &cno);
trace_ext4_fsmap_mapping(sb, info->gfi_dev, agno,
EXT4_C2B(sbi, cno),
rec_fsblk - info->gfi_next_fsblk,
EXT4_FMR_OWN_UNKNOWN);
fmr.fmr_device = info->gfi_dev;
fmr.fmr_physical = info->gfi_next_fsblk;
fmr.fmr_owner = EXT4_FMR_OWN_UNKNOWN;
fmr.fmr_length = rec_fsblk - info->gfi_next_fsblk;
fmr.fmr_flags = FMR_OF_SPECIAL_OWNER;
error = info->gfi_formatter(&fmr, info->gfi_format_arg);
if (error)
return error;
info->gfi_head->fmh_entries++;
}
if (info->gfi_last)
goto out;
/* Fill out the extent we found */
if (info->gfi_head->fmh_entries >= info->gfi_head->fmh_count)
return EXT4_QUERY_RANGE_ABORT;
ext4_get_group_no_and_offset(sb, rec_fsblk, &agno, &cno);
trace_ext4_fsmap_mapping(sb, info->gfi_dev, agno, EXT4_C2B(sbi, cno),
rec->fmr_length, rec->fmr_owner);
fmr.fmr_device = info->gfi_dev;
fmr.fmr_physical = rec_fsblk;
fmr.fmr_owner = rec->fmr_owner;
fmr.fmr_flags = FMR_OF_SPECIAL_OWNER;
fmr.fmr_length = rec->fmr_length;
error = info->gfi_formatter(&fmr, info->gfi_format_arg);
if (error)
return error;
info->gfi_head->fmh_entries++;
out:
rec_fsblk += rec->fmr_length;
if (info->gfi_next_fsblk < rec_fsblk)
info->gfi_next_fsblk = rec_fsblk;
return EXT4_QUERY_RANGE_CONTINUE;
}
static inline ext4_fsblk_t ext4_fsmap_next_pblk(struct ext4_fsmap *fmr)
{
return fmr->fmr_physical + fmr->fmr_length;
}
static int ext4_getfsmap_meta_helper(struct super_block *sb,
ext4_group_t agno, ext4_grpblk_t start,
ext4_grpblk_t len, void *priv)
{
struct ext4_getfsmap_info *info = priv;
struct ext4_fsmap *p;
struct ext4_fsmap *tmp;
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t fsb, fs_start, fs_end;
int error;
fs_start = fsb = (EXT4_C2B(sbi, start) +
ext4_group_first_block_no(sb, agno));
fs_end = fs_start + EXT4_C2B(sbi, len);
/*
* Return relevant extents from the meta_list. We emit all extents that
* partially/fully overlap with the query range
*/
list_for_each_entry_safe(p, tmp, &info->gfi_meta_list, fmr_list) {
if (p->fmr_physical + p->fmr_length <= info->gfi_next_fsblk) {
list_del(&p->fmr_list);
kfree(p);
continue;
}
if (p->fmr_physical <= fs_end &&
p->fmr_physical + p->fmr_length > fs_start) {
/* Emit the retained free extent record if present */
if (info->gfi_lastfree.fmr_owner) {
error = ext4_getfsmap_helper(sb, info,
&info->gfi_lastfree);
if (error)
return error;
info->gfi_lastfree.fmr_owner = 0;
}
error = ext4_getfsmap_helper(sb, info, p);
if (error)
return error;
fsb = p->fmr_physical + p->fmr_length;
if (info->gfi_next_fsblk < fsb)
info->gfi_next_fsblk = fsb;
list_del(&p->fmr_list);
kfree(p);
continue;
}
}
if (info->gfi_next_fsblk < fsb)
info->gfi_next_fsblk = fsb;
return 0;
}
/* Transform a blockgroup's free record into a fsmap */
static int ext4_getfsmap_datadev_helper(struct super_block *sb,
ext4_group_t agno, ext4_grpblk_t start,
ext4_grpblk_t len, void *priv)
{
struct ext4_fsmap irec;
struct ext4_getfsmap_info *info = priv;
struct ext4_fsmap *p;
struct ext4_fsmap *tmp;
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t fsb;
ext4_fsblk_t fslen;
int error;
fsb = (EXT4_C2B(sbi, start) + ext4_group_first_block_no(sb, agno));
fslen = EXT4_C2B(sbi, len);
/* If the retained free extent record is set... */
if (info->gfi_lastfree.fmr_owner) {
/* ...and abuts this one, lengthen it and return. */
if (ext4_fsmap_next_pblk(&info->gfi_lastfree) == fsb) {
info->gfi_lastfree.fmr_length += fslen;
return 0;
}
/*
* There's a gap between the two free extents; emit the
* retained extent prior to merging the meta_list.
*/
error = ext4_getfsmap_helper(sb, info, &info->gfi_lastfree);
if (error)
return error;
info->gfi_lastfree.fmr_owner = 0;
}
/* Merge in any relevant extents from the meta_list */
list_for_each_entry_safe(p, tmp, &info->gfi_meta_list, fmr_list) {
if (p->fmr_physical + p->fmr_length <= info->gfi_next_fsblk) {
list_del(&p->fmr_list);
kfree(p);
} else if (p->fmr_physical < fsb) {
error = ext4_getfsmap_helper(sb, info, p);
if (error)
return error;
list_del(&p->fmr_list);
kfree(p);
}
}
irec.fmr_device = 0;
irec.fmr_physical = fsb;
irec.fmr_length = fslen;
irec.fmr_owner = EXT4_FMR_OWN_FREE;
irec.fmr_flags = 0;
/* If this is a free extent at the end of a bg, buffer it. */
if (ext4_fsmap_next_pblk(&irec) ==
ext4_group_first_block_no(sb, agno + 1)) {
info->gfi_lastfree = irec;
return 0;
}
/* Otherwise, emit it */
return ext4_getfsmap_helper(sb, info, &irec);
}
/* Execute a getfsmap query against the log device. */
static int ext4_getfsmap_logdev(struct super_block *sb, struct ext4_fsmap *keys,
struct ext4_getfsmap_info *info)
{
journal_t *journal = EXT4_SB(sb)->s_journal;
struct ext4_fsmap irec;
/* Set up search keys */
info->gfi_low = keys[0];
info->gfi_low.fmr_length = 0;
memset(&info->gfi_high, 0xFF, sizeof(info->gfi_high));
trace_ext4_fsmap_low_key(sb, info->gfi_dev, 0,
info->gfi_low.fmr_physical,
info->gfi_low.fmr_length,
info->gfi_low.fmr_owner);
trace_ext4_fsmap_high_key(sb, info->gfi_dev, 0,
info->gfi_high.fmr_physical,
info->gfi_high.fmr_length,
info->gfi_high.fmr_owner);
if (keys[0].fmr_physical > 0)
return 0;
/* Fabricate an rmap entry for the external log device. */
irec.fmr_physical = journal->j_blk_offset;
irec.fmr_length = journal->j_total_len;
irec.fmr_owner = EXT4_FMR_OWN_LOG;
irec.fmr_flags = 0;
return ext4_getfsmap_helper(sb, info, &irec);
}
/* Helper to fill out an ext4_fsmap. */
static inline int ext4_getfsmap_fill(struct list_head *meta_list,
ext4_fsblk_t fsb, ext4_fsblk_t len,
uint64_t owner)
{
struct ext4_fsmap *fsm;
fsm = kmalloc_obj(*fsm, GFP_NOFS);
if (!fsm)
return -ENOMEM;
fsm->fmr_device = 0;
fsm->fmr_flags = 0;
fsm->fmr_physical = fsb;
fsm->fmr_owner = owner;
fsm->fmr_length = len;
list_add_tail(&fsm->fmr_list, meta_list);
return 0;
}
/*
* This function returns the number of file system metadata blocks at
* the beginning of a block group, including the reserved gdt blocks.
*/
static unsigned int ext4_getfsmap_find_sb(struct super_block *sb,
ext4_group_t agno,
struct list_head *meta_list)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t fsb = ext4_group_first_block_no(sb, agno);
ext4_fsblk_t len;
unsigned long first_meta_bg = le32_to_cpu(sbi->s_es->s_first_meta_bg);
unsigned long metagroup = agno / EXT4_DESC_PER_BLOCK(sb);
int error;
/* Record the superblock. */
if (ext4_bg_has_super(sb, agno)) {
error = ext4_getfsmap_fill(meta_list, fsb, 1, EXT4_FMR_OWN_FS);
if (error)
return error;
fsb++;
}
/* Record the group descriptors. */
len = ext4_bg_num_gdb(sb, agno);
if (!len)
return 0;
error = ext4_getfsmap_fill(meta_list, fsb, len,
EXT4_FMR_OWN_GDT);
if (error)
return error;
fsb += len;
/* Reserved GDT blocks */
if (!ext4_has_feature_meta_bg(sb) || metagroup < first_meta_bg) {
len = le16_to_cpu(sbi->s_es->s_reserved_gdt_blocks);
/*
* mkfs.ext4 can set s_reserved_gdt_blocks as 0 in some cases,
* check for that.
*/
if (!len)
return 0;
error = ext4_getfsmap_fill(meta_list, fsb, len,
EXT4_FMR_OWN_RESV_GDT);
if (error)
return error;
}
return 0;
}
/* Compare two fsmap items. */
static int ext4_getfsmap_compare(void *priv,
const struct list_head *a,
const struct list_head *b)
{
struct ext4_fsmap *fa;
struct ext4_fsmap *fb;
fa = container_of(a, struct ext4_fsmap, fmr_list);
fb = container_of(b, struct ext4_fsmap, fmr_list);
if (fa->fmr_physical < fb->fmr_physical)
return -1;
else if (fa->fmr_physical > fb->fmr_physical)
return 1;
return 0;
}
/* Merge adjacent extents of fixed metadata. */
static void ext4_getfsmap_merge_fixed_metadata(struct list_head *meta_list)
{
struct ext4_fsmap *p;
struct ext4_fsmap *prev = NULL;
struct ext4_fsmap *tmp;
list_for_each_entry_safe(p, tmp, meta_list, fmr_list) {
if (!prev) {
prev = p;
continue;
}
if (prev->fmr_owner == p->fmr_owner &&
prev->fmr_physical + prev->fmr_length == p->fmr_physical) {
prev->fmr_length += p->fmr_length;
list_del(&p->fmr_list);
kfree(p);
} else
prev = p;
}
}
/* Free a list of fixed metadata. */
static void ext4_getfsmap_free_fixed_metadata(struct list_head *meta_list)
{
struct ext4_fsmap *p;
struct ext4_fsmap *tmp;
list_for_each_entry_safe(p, tmp, meta_list, fmr_list) {
list_del(&p->fmr_list);
kfree(p);
}
}
/* Find all the fixed metadata in the filesystem. */
static int ext4_getfsmap_find_fixed_metadata(struct super_block *sb,
struct list_head *meta_list)
{
struct ext4_group_desc *gdp;
ext4_group_t agno;
int error;
INIT_LIST_HEAD(meta_list);
/* Collect everything. */
for (agno = 0; agno < EXT4_SB(sb)->s_groups_count; agno++) {
gdp = ext4_get_group_desc(sb, agno, NULL);
if (!gdp) {
error = -EFSCORRUPTED;
goto err;
}
/* Superblock & GDT */
error = ext4_getfsmap_find_sb(sb, agno, meta_list);
if (error)
goto err;
/* Block bitmap */
error = ext4_getfsmap_fill(meta_list,
ext4_block_bitmap(sb, gdp), 1,
EXT4_FMR_OWN_BLKBM);
if (error)
goto err;
/* Inode bitmap */
error = ext4_getfsmap_fill(meta_list,
ext4_inode_bitmap(sb, gdp), 1,
EXT4_FMR_OWN_INOBM);
if (error)
goto err;
/* Inodes */
error = ext4_getfsmap_fill(meta_list,
ext4_inode_table(sb, gdp),
EXT4_SB(sb)->s_itb_per_group,
EXT4_FMR_OWN_INODES);
if (error)
goto err;
}
/* Sort the list */
list_sort(NULL, meta_list, ext4_getfsmap_compare);
/* Merge adjacent extents */
ext4_getfsmap_merge_fixed_metadata(meta_list);
return 0;
err:
ext4_getfsmap_free_fixed_metadata(meta_list);
return error;
}
/* Execute a getfsmap query against the buddy bitmaps */
static int ext4_getfsmap_datadev(struct super_block *sb,
struct ext4_fsmap *keys,
struct ext4_getfsmap_info *info)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
ext4_fsblk_t start_fsb;
ext4_fsblk_t end_fsb;
ext4_fsblk_t bofs;
ext4_fsblk_t eofs;
ext4_group_t start_ag;
ext4_group_t end_ag;
ext4_grpblk_t first_cluster;
ext4_grpblk_t last_cluster;
struct ext4_fsmap irec;
int error = 0;
bofs = le32_to_cpu(sbi->s_es->s_first_data_block);
eofs = ext4_blocks_count(sbi->s_es);
if (keys[0].fmr_physical >= eofs)
return 0;
else if (keys[0].fmr_physical < bofs)
keys[0].fmr_physical = bofs;
if (keys[1].fmr_physical >= eofs)
keys[1].fmr_physical = eofs - 1;
if (keys[1].fmr_physical < keys[0].fmr_physical)
return 0;
start_fsb = keys[0].fmr_physical;
end_fsb = keys[1].fmr_physical;
/* Determine first and last group to examine based on start and end */
ext4_get_group_no_and_offset(sb, start_fsb, &start_ag, &first_cluster);
ext4_get_group_no_and_offset(sb, end_fsb, &end_ag, &last_cluster);
/*
* Convert the fsmap low/high keys to bg based keys. Initialize
* low to the fsmap low key and max out the high key to the end
* of the bg.
*/
info->gfi_low = keys[0];
info->gfi_low.fmr_physical = EXT4_C2B(sbi, first_cluster);
info->gfi_low.fmr_length = 0;
memset(&info->gfi_high, 0xFF, sizeof(info->gfi_high));
/* Assemble a list of all the fixed-location metadata. */
error = ext4_getfsmap_find_fixed_metadata(sb, &info->gfi_meta_list);
if (error)
goto err;
/* Query each bg */
for (info->gfi_agno = start_ag;
info->gfi_agno <= end_ag;
info->gfi_agno++) {
/*
* Set the bg high key from the fsmap high key if this
* is the last bg that we're querying.
*/
if (info->gfi_agno == end_ag) {
info->gfi_high = keys[1];
info->gfi_high.fmr_physical = EXT4_C2B(sbi,
last_cluster);
info->gfi_high.fmr_length = 0;
}
trace_ext4_fsmap_low_key(sb, info->gfi_dev, info->gfi_agno,
info->gfi_low.fmr_physical,
info->gfi_low.fmr_length,
info->gfi_low.fmr_owner);
trace_ext4_fsmap_high_key(sb, info->gfi_dev, info->gfi_agno,
info->gfi_high.fmr_physical,
info->gfi_high.fmr_length,
info->gfi_high.fmr_owner);
error = ext4_mballoc_query_range(sb, info->gfi_agno,
EXT4_B2C(sbi, info->gfi_low.fmr_physical),
EXT4_B2C(sbi, info->gfi_high.fmr_physical),
ext4_getfsmap_meta_helper,
ext4_getfsmap_datadev_helper, info);
if (error)
goto err;
/*
* Set the bg low key to the start of the bg prior to
* moving on to the next bg.
*/
if (info->gfi_agno == start_ag)
memset(&info->gfi_low, 0, sizeof(info->gfi_low));
}
/* Do we have a retained free extent? */
if (info->gfi_lastfree.fmr_owner) {
error = ext4_getfsmap_helper(sb, info, &info->gfi_lastfree);
if (error)
goto err;
}
/*
* The dummy record below will cause ext4_getfsmap_helper() to report
* any allocated blocks at the end of the range.
*/
irec.fmr_device = 0;
irec.fmr_physical = end_fsb + 1;
irec.fmr_length = 0;
irec.fmr_owner = EXT4_FMR_OWN_FREE;
irec.fmr_flags = 0;
info->gfi_last = true;
error = ext4_getfsmap_helper(sb, info, &irec);
if (error)
goto err;
err:
ext4_getfsmap_free_fixed_metadata(&info->gfi_meta_list);
return error;
}
/* Do we recognize the device? */
static bool ext4_getfsmap_is_valid_device(struct super_block *sb,
struct ext4_fsmap *fm)
{
if (fm->fmr_device == 0 || fm->fmr_device == UINT_MAX ||
fm->fmr_device == new_encode_dev(sb->s_bdev->bd_dev))
return true;
if (EXT4_SB(sb)->s_journal_bdev_file &&
fm->fmr_device ==
new_encode_dev(file_bdev(EXT4_SB(sb)->s_journal_bdev_file)->bd_dev))
return true;
return false;
}
/* Ensure that the low key is less than the high key. */
static bool ext4_getfsmap_check_keys(struct ext4_fsmap *low_key,
struct ext4_fsmap *high_key)
{
if (low_key->fmr_device > high_key->fmr_device)
return false;
if (low_key->fmr_device < high_key->fmr_device)
return true;
if (low_key->fmr_physical > high_key->fmr_physical)
return false;
if (low_key->fmr_physical < high_key->fmr_physical)
return true;
if (low_key->fmr_owner > high_key->fmr_owner)
return false;
if (low_key->fmr_owner < high_key->fmr_owner)
return true;
return false;
}
#define EXT4_GETFSMAP_DEVS 2
/*
* Get filesystem's extents as described in head, and format for
* output. Calls formatter to fill the user's buffer until all
* extents are mapped, until the passed-in head->fmh_count slots have
* been filled, or until the formatter short-circuits the loop, if it
* is tracking filled-in extents on its own.
*
* Key to Confusion
* ----------------
* There are multiple levels of keys and counters at work here:
* _fsmap_head.fmh_keys -- low and high fsmap keys passed in;
* these reflect fs-wide block addrs.
* dkeys -- fmh_keys used to query each device;
* these are fmh_keys but w/ the low key
* bumped up by fmr_length.
* _getfsmap_info.gfi_next_fsblk-- next fs block we expect to see; this
* is how we detect gaps in the fsmap
* records and report them.
* _getfsmap_info.gfi_low/high -- per-bg low/high keys computed from
* dkeys; used to query the free space.
*/
int ext4_getfsmap(struct super_block *sb, struct ext4_fsmap_head *head,
ext4_fsmap_format_t formatter, void *arg)
{
struct ext4_fsmap dkeys[2]; /* per-dev keys */
struct ext4_getfsmap_dev handlers[EXT4_GETFSMAP_DEVS];
struct ext4_getfsmap_info info = { NULL };
int i;
int error = 0;
if (head->fmh_iflags & ~FMH_IF_VALID)
return -EINVAL;
if (!ext4_getfsmap_is_valid_device(sb, &head->fmh_keys[0]) ||
!ext4_getfsmap_is_valid_device(sb, &head->fmh_keys[1]))
return -EINVAL;
head->fmh_entries = 0;
/* Set up our device handlers. */
memset(handlers, 0, sizeof(handlers));
handlers[0].gfd_dev = new_encode_dev(sb->s_bdev->bd_dev);
handlers[0].gfd_fn = ext4_getfsmap_datadev;
if (EXT4_SB(sb)->s_journal_bdev_file) {
handlers[1].gfd_dev = new_encode_dev(
file_bdev(EXT4_SB(sb)->s_journal_bdev_file)->bd_dev);
handlers[1].gfd_fn = ext4_getfsmap_logdev;
}
sort(handlers, EXT4_GETFSMAP_DEVS, sizeof(struct ext4_getfsmap_dev),
ext4_getfsmap_dev_compare, NULL);
/*
* To continue where we left off, we allow userspace to use the
* last mapping from a previous call as the low key of the next.
* This is identified by a non-zero length in the low key. We
* have to increment the low key in this scenario to ensure we
* don't return the same mapping again, and instead return the
* very next mapping.
*
* Bump the physical offset as there can be no other mapping for
* the same physical block range.
*/
dkeys[0] = head->fmh_keys[0];
dkeys[0].fmr_physical += dkeys[0].fmr_length;
dkeys[0].fmr_owner = 0;
dkeys[0].fmr_length = 0;
memset(&dkeys[1], 0xFF, sizeof(struct ext4_fsmap));
if (!ext4_getfsmap_check_keys(dkeys, &head->fmh_keys[1]))
return -EINVAL;
info.gfi_next_fsblk = head->fmh_keys[0].fmr_physical +
head->fmh_keys[0].fmr_length;
info.gfi_formatter = formatter;
info.gfi_format_arg = arg;
info.gfi_head = head;
/* For each device we support... */
for (i = 0; i < EXT4_GETFSMAP_DEVS; i++) {
/* Is this device within the range the user asked for? */
if (!handlers[i].gfd_fn)
continue;
if (head->fmh_keys[0].fmr_device > handlers[i].gfd_dev)
continue;
if (head->fmh_keys[1].fmr_device < handlers[i].gfd_dev)
break;
/*
* If this device number matches the high key, we have
* to pass the high key to the handler to limit the
* query results. If the device number exceeds the
* low key, zero out the low key so that we get
* everything from the beginning.
*/
if (handlers[i].gfd_dev == head->fmh_keys[1].fmr_device)
dkeys[1] = head->fmh_keys[1];
if (handlers[i].gfd_dev > head->fmh_keys[0].fmr_device)
memset(&dkeys[0], 0, sizeof(struct ext4_fsmap));
info.gfi_dev = handlers[i].gfd_dev;
info.gfi_last = false;
info.gfi_agno = -1;
error = handlers[i].gfd_fn(sb, dkeys, &info);
if (error)
break;
info.gfi_next_fsblk = 0;
}
head->fmh_oflags = FMH_OF_DEV_T;
return error;
}
+56
View File
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Oracle. All Rights Reserved.
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#ifndef __EXT4_FSMAP_H__
#define __EXT4_FSMAP_H__
struct fsmap;
/* internal fsmap representation */
struct ext4_fsmap {
struct list_head fmr_list;
dev_t fmr_device; /* device id */
uint32_t fmr_flags; /* mapping flags */
uint64_t fmr_physical; /* device offset of segment */
uint64_t fmr_owner; /* owner id */
uint64_t fmr_length; /* length of segment, blocks */
};
struct ext4_fsmap_head {
uint32_t fmh_iflags; /* control flags */
uint32_t fmh_oflags; /* output flags */
unsigned int fmh_count; /* # of entries in array incl. input */
unsigned int fmh_entries; /* # of entries filled in (output). */
struct ext4_fsmap fmh_keys[2]; /* low and high keys */
};
void ext4_fsmap_from_internal(struct super_block *sb, struct fsmap *dest,
struct ext4_fsmap *src);
void ext4_fsmap_to_internal(struct super_block *sb, struct ext4_fsmap *dest,
struct fsmap *src);
/* fsmap to userspace formatter - copy to user & advance pointer */
typedef int (*ext4_fsmap_format_t)(struct ext4_fsmap *, void *);
int ext4_getfsmap(struct super_block *sb, struct ext4_fsmap_head *head,
ext4_fsmap_format_t formatter, void *arg);
#define EXT4_QUERY_RANGE_ABORT 1
#define EXT4_QUERY_RANGE_CONTINUE 0
/* fmr_owner special values for FS_IOC_GETFSMAP; some share w/ XFS */
#define EXT4_FMR_OWN_FREE FMR_OWN_FREE /* free space */
#define EXT4_FMR_OWN_UNKNOWN FMR_OWN_UNKNOWN /* unknown owner */
#define EXT4_FMR_OWN_FS FMR_OWNER('X', 1) /* static fs metadata */
#define EXT4_FMR_OWN_LOG FMR_OWNER('X', 2) /* journalling log */
#define EXT4_FMR_OWN_INODES FMR_OWNER('X', 5) /* inodes */
#define EXT4_FMR_OWN_GDT FMR_OWNER('f', 1) /* group descriptors */
#define EXT4_FMR_OWN_RESV_GDT FMR_OWNER('f', 2) /* reserved gdt blocks */
#define EXT4_FMR_OWN_BLKBM FMR_OWNER('f', 3) /* block bitmap */
#define EXT4_FMR_OWN_INOBM FMR_OWNER('f', 4) /* inode bitmap */
#endif /* __EXT4_FSMAP_H__ */
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/fsync.c
*
* Copyright (C) 1993 Stephen Tweedie (sct@redhat.com)
* from
* Copyright (C) 1992 Remy Card (card@masi.ibp.fr)
* Laboratoire MASI - Institut Blaise Pascal
* Universite Pierre et Marie Curie (Paris VI)
* from
* linux/fs/minix/truncate.c Copyright (C) 1991, 1992 Linus Torvalds
*
* ext4fs fsync primitive
*
* Big-endian to little-endian byte-swapping/bitmaps by
* David S. Miller (davem@caip.rutgers.edu), 1995
*
* Removed unnecessary code duplication for little endian machines
* and excessive __inline__s.
* Andi Kleen, 1997
*
* Major simplications and cleanup - we only need to do the metadata, because
* we can depend on generic_block_fdatasync() to sync the data blocks.
*/
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/writeback.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include "ext4.h"
#include "ext4_jbd2.h"
#include <trace/events/ext4.h>
/*
* If we're not journaling and this is a just-created file, we have to
* sync our parent directory (if it was freshly created) since
* otherwise it will only be written by writeback, leaving a huge
* window during which a crash may lose the file. This may apply for
* the parent directory's parent as well, and so on recursively, if
* they are also freshly created.
*/
static int ext4_sync_parent(struct inode *inode)
{
struct dentry *dentry, *next;
int ret = 0;
if (!ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY))
return 0;
dentry = d_find_any_alias(inode);
if (!dentry)
return 0;
while (ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) {
ext4_clear_inode_state(inode, EXT4_STATE_NEWENTRY);
next = dget_parent(dentry);
dput(dentry);
dentry = next;
inode = dentry->d_inode;
/*
* The directory inode may have gone through rmdir by now. But
* the inode itself and its blocks are still allocated (we hold
* a reference to the inode via its dentry), so it didn't go
* through ext4_evict_inode()) and so we are safe to flush
* metadata blocks and the inode.
*/
ret = mmb_sync(&EXT4_I(inode)->i_metadata_bhs);
if (ret)
break;
ret = sync_inode_metadata(inode, 1);
if (ret)
break;
}
dput(dentry);
return ret;
}
static int ext4_fsync_nojournal(struct file *file, loff_t start, loff_t end,
int datasync, bool *needs_barrier)
{
struct inode *inode = file->f_inode;
struct writeback_control wbc = {
.sync_mode = WB_SYNC_ALL,
.nr_to_write = 0,
};
int ret;
ret = mmb_fsync_noflush(file, &EXT4_I(inode)->i_metadata_bhs,
start, end, datasync);
if (ret)
return ret;
/* Force writeout of inode table buffer to disk */
ret = ext4_write_inode(inode, &wbc);
if (ret)
return ret;
ret = ext4_sync_parent(inode);
if (test_opt(inode->i_sb, BARRIER))
*needs_barrier = true;
return ret;
}
static int ext4_fsync_journal(struct inode *inode, bool datasync,
bool *needs_barrier)
{
struct ext4_inode_info *ei = EXT4_I(inode);
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
tid_t commit_tid = datasync ? ei->i_datasync_tid : ei->i_sync_tid;
/*
* Fastcommit does not really support fsync on directories or other
* special files. Force a full commit.
*/
if (!S_ISREG(inode->i_mode))
return ext4_force_commit(inode->i_sb);
if (journal->j_flags & JBD2_BARRIER &&
!jbd2_trans_will_send_data_barrier(journal, commit_tid))
*needs_barrier = true;
return ext4_fc_commit(journal, commit_tid);
}
/*
* akpm: A new design for ext4_sync_file().
*
* This is only called from sys_fsync(), sys_fdatasync() and sys_msync().
* There cannot be a transaction open by this task.
* Another task could have dirtied this inode. Its data can be in any
* state in the journalling system.
*
* What we do is just kick off a commit and wait on it. This will snapshot the
* inode to disk.
*/
int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
{
int ret = 0, err;
bool needs_barrier = false;
struct inode *inode = file->f_mapping->host;
ret = ext4_emergency_state(inode->i_sb);
if (unlikely(ret))
return ret;
ASSERT(ext4_journal_current_handle() == NULL);
trace_ext4_sync_file_enter(file, datasync);
if (sb_rdonly(inode->i_sb))
goto out;
if (!EXT4_SB(inode->i_sb)->s_journal) {
ret = ext4_fsync_nojournal(file, start, end, datasync,
&needs_barrier);
if (needs_barrier)
goto issue_flush;
goto out;
}
ret = file_write_and_wait_range(file, start, end);
if (ret)
goto out;
/*
* The caller's filemap_fdatawrite()/wait will sync the data.
* Metadata is in the journal, we wait for proper transaction to
* commit here.
*/
ret = ext4_fsync_journal(inode, datasync, &needs_barrier);
issue_flush:
if (needs_barrier) {
err = blkdev_issue_flush(inode->i_sb->s_bdev);
if (!ret)
ret = err;
}
out:
err = file_check_and_advance_wb_err(file);
if (ret == 0)
ret = err;
trace_ext4_sync_file_exit(inode, ret);
return ret;
}
+567
View File
@@ -0,0 +1,567 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for ext4 directory hash computation.
*/
#include <kunit/test.h>
#include <kunit/resource.h>
#include <linux/fs.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/unicode.h>
#include "ext4.h"
static void ext4_hash_init_fake_dir(struct inode *dir, struct super_block *sb)
{
memset(sb, 0, sizeof(*sb));
memset(dir, 0, sizeof(*dir));
dir->i_sb = sb;
strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
}
static void ext4_hash_init_fake_dir_with_sbi(struct inode *dir,
struct super_block *sb,
struct ext4_sb_info *sbi)
{
ext4_hash_init_fake_dir(dir, sb);
memset(sbi, 0, sizeof(*sbi));
sb->s_fs_info = sbi;
sbi->s_sb = sb;
}
#ifdef CONFIG_FS_ENCRYPTION
static const struct fscrypt_operations ext4_hash_test_cryptops = {
.inode_info_offs =
(int)offsetof(struct ext4_inode_info, i_crypt_info) -
(int)offsetof(struct ext4_inode_info, vfs_inode),
};
#endif
static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei,
struct super_block *sb,
struct ext4_sb_info *sbi)
{
struct inode *dir = &ei->vfs_inode;
memset(sb, 0, sizeof(*sb));
memset(ei, 0, sizeof(*ei));
memset(sbi, 0, sizeof(*sbi));
strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
sb->s_fs_info = sbi;
sbi->s_sb = sb;
dir->i_sb = sb;
dir->i_mode = S_IFDIR;
#ifdef CONFIG_FS_ENCRYPTION
fscrypt_set_ops(sb, &ext4_hash_test_cryptops);
#endif
}
struct ext4_dirhash_test_case {
const char *name;
u32 hash_version;
const char *input;
int len;
u32 seed[4];
bool use_seed;
u32 expected_hash;
u32 expected_minor_hash;
};
static const struct ext4_dirhash_test_case ext4_dirhash_test_cases[] = {
{
.name = "legacy_abc",
.hash_version = DX_HASH_LEGACY,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0x75afd992,
.expected_minor_hash = 0x00000000,
},
{
.name = "legacy_unsigned_abc",
.hash_version = DX_HASH_LEGACY_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0x75afd992,
.expected_minor_hash = 0x00000000,
},
{
.name = "half_md4_abc",
.hash_version = DX_HASH_HALF_MD4,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xd196a868,
.expected_minor_hash = 0xc420eb28,
},
{
.name = "half_md4_unsigned_abc",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xd196a868,
.expected_minor_hash = 0xc420eb28,
},
{
.name = "tea_abc",
.hash_version = DX_HASH_TEA,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xb1435ec4,
.expected_minor_hash = 0x3f7eaa0e,
},
{
.name = "tea_unsigned_abc",
.hash_version = DX_HASH_TEA_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xb1435ec4,
.expected_minor_hash = 0x3f7eaa0e,
},
{
.name = "empty_half_md4",
.hash_version = DX_HASH_HALF_MD4,
.input = "",
.len = 0,
.use_seed = false,
.expected_hash = 0xefcdab88,
.expected_minor_hash = 0x98badcfe,
},
{
.name = "half_md4_31bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "1234567890123456789012345678901",
.len = 31,
.use_seed = false,
.expected_hash = 0xc4db1f78,
.expected_minor_hash = 0xea23921b,
},
{
.name = "half_md4_32bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "12345678901234567890123456789012",
.len = 32,
.use_seed = false,
.expected_hash = 0xfa6cc63e,
.expected_minor_hash = 0x2f77bd1c,
},
{
.name = "half_md4_33bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "123456789012345678901234567890123",
.len = 33,
.use_seed = false,
.expected_hash = 0xdc0c2dec,
.expected_minor_hash = 0x5ca23365,
},
{
.name = "half_md4_unsigned_31bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "1234567890123456789012345678901",
.len = 31,
.use_seed = false,
.expected_hash = 0xc4db1f78,
.expected_minor_hash = 0xea23921b,
},
{
.name = "half_md4_unsigned_32bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "12345678901234567890123456789012",
.len = 32,
.use_seed = false,
.expected_hash = 0xfa6cc63e,
.expected_minor_hash = 0x2f77bd1c,
},
{
.name = "half_md4_unsigned_33bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "123456789012345678901234567890123",
.len = 33,
.use_seed = false,
.expected_hash = 0xdc0c2dec,
.expected_minor_hash = 0x5ca23365,
},
{
.name = "tea_15bytes",
.hash_version = DX_HASH_TEA,
.input = "123456789abcdef",
.len = 15,
.use_seed = false,
.expected_hash = 0xa562903a,
.expected_minor_hash = 0x6174a00f,
},
{
.name = "tea_16bytes",
.hash_version = DX_HASH_TEA,
.input = "1234567890abcdef",
.len = 16,
.use_seed = false,
.expected_hash = 0x8449f258,
.expected_minor_hash = 0x49a16d46,
},
{
.name = "tea_17bytes",
.hash_version = DX_HASH_TEA,
.input = "123456789abcdefgh",
.len = 17,
.use_seed = false,
.expected_hash = 0xf32ec10c,
.expected_minor_hash = 0x58ceae61,
},
{
.name = "half_md4_seeded",
.hash_version = DX_HASH_HALF_MD4,
.input = "same-name",
.len = 9,
.seed = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 },
.use_seed = true,
.expected_hash = 0x8aebf604,
.expected_minor_hash = 0x66ce48fe,
},
{
.name = "half_md4_non_ascii_signed",
.hash_version = DX_HASH_HALF_MD4,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x8bab0498,
.expected_minor_hash = 0xc326632d,
},
{
.name = "half_md4_non_ascii_unsigned",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0xbc48596e,
.expected_minor_hash = 0xde0fad41,
},
{
.name = "tea_non_ascii_signed",
.hash_version = DX_HASH_TEA,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x21e3a154,
.expected_minor_hash = 0x90112c3d,
},
{
.name = "tea_non_ascii_unsigned",
.hash_version = DX_HASH_TEA_UNSIGNED,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x9b648616,
.expected_minor_hash = 0x011dd507,
},
};
static void test_ext4fs_dirhash_vectors(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
int i;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
for (i = 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) {
const struct ext4_dirhash_test_case *tc =
&ext4_dirhash_test_cases[i];
struct dx_hash_info hinfo;
int ret;
memset(&hinfo, 0, sizeof(hinfo));
hinfo.hash_version = tc->hash_version;
hinfo.seed = tc->use_seed ? (u32 *)tc->seed : NULL;
ret = ext4fs_dirhash(dir, tc->input, tc->len, &hinfo);
KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=%s", tc->name);
KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash,
"case=%s", tc->name);
KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash,
tc->expected_minor_hash,
"case=%s", tc->name);
}
}
static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
u32 seed[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
struct dx_hash_info plain = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info seeded = {
.hash_version = DX_HASH_HALF_MD4,
.seed = seed,
};
int ret_plain, ret_seeded;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
ret_plain = ext4fs_dirhash(dir, "same-name", 9, &plain);
ret_seeded = ext4fs_dirhash(dir, "same-name", 9, &seeded);
KUNIT_ASSERT_EQ(test, ret_plain, 0);
KUNIT_ASSERT_EQ(test, ret_seeded, 0);
KUNIT_EXPECT_TRUE(test,
plain.hash != seeded.hash ||
plain.minor_hash != seeded.minor_hash);
}
static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
struct ext4_sb_info *sbi;
struct dx_hash_info hinfo = {
.hash = 0xdeadbeef,
.minor_hash = 0xcafebabe,
.hash_version = DX_HASH_LAST + 1,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, dir);
KUNIT_ASSERT_NOT_NULL(test, sbi);
ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi);
ret = ext4fs_dirhash(dir, "abc", 3, &hinfo);
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
KUNIT_EXPECT_EQ(test, hinfo.hash, 0);
KUNIT_EXPECT_EQ(test, hinfo.minor_hash, 0);
}
static void test_ext4fs_dirhash_siphash_without_key_returns_einval(struct kunit *test)
{
struct super_block *sb;
struct ext4_inode_info *ei;
struct inode *dir;
struct ext4_sb_info *sbi;
struct dx_hash_info hinfo = {
.hash_version = DX_HASH_SIPHASH,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, ei);
KUNIT_ASSERT_NOT_NULL(test, sbi);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
dir = &ei->vfs_inode;
ret = ext4fs_dirhash(dir, "name", strlen("name"), &hinfo);
KUNIT_EXPECT_EQ(test, ret, -EINVAL);
}
static void test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
static const char input[] = "\x80\xff\x81\xfe\101bc";
struct dx_hash_info legacy_signed = {
.hash_version = DX_HASH_LEGACY,
};
struct dx_hash_info legacy_unsigned = {
.hash_version = DX_HASH_LEGACY_UNSIGNED,
};
struct dx_hash_info md4_signed = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info md4_unsigned = {
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
};
struct dx_hash_info tea_signed = {
.hash_version = DX_HASH_TEA,
};
struct dx_hash_info tea_unsigned = {
.hash_version = DX_HASH_TEA_UNSIGNED,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_unsigned);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_EXPECT_NE(test, legacy_signed.hash, legacy_unsigned.hash);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_EXPECT_TRUE(test,
md4_signed.hash != md4_unsigned.hash ||
md4_signed.minor_hash != md4_unsigned.minor_hash);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_EXPECT_TRUE(test,
tea_signed.hash != tea_unsigned.hash ||
tea_signed.minor_hash != tea_unsigned.minor_hash);
}
#if IS_ENABLED(CONFIG_UNICODE)
KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload,
struct unicode_map *);
static void test_ext4fs_dirhash_casefolded_names_hash_consistently(struct kunit *test)
{
struct super_block *sb;
struct ext4_inode_info *ei;
struct ext4_sb_info *sbi;
struct unicode_map *um;
struct dx_hash_info h1 = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info h2 = {
.hash_version = DX_HASH_HALF_MD4,
};
int ret, ret1, ret2;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb);
KUNIT_ASSERT_NOT_NULL(test, ei);
KUNIT_ASSERT_NOT_NULL(test, sbi);
um = utf8_load(UTF8_LATEST);
if (IS_ERR(um)) {
kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
um);
return;
}
ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
sb->s_encoding = um;
ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ret1 = ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1);
ret2 = ext4fs_dirhash(&ei->vfs_inode, "aLPHa", 5, &h2);
KUNIT_ASSERT_EQ(test, ret1, 0);
KUNIT_ASSERT_EQ(test, ret2, 0);
KUNIT_EXPECT_EQ(test, h1.hash, h2.hash);
KUNIT_EXPECT_EQ(test, h1.minor_hash, h2.minor_hash);
}
static void test_ext4fs_dirhash_casefold_fallback(struct kunit *test)
{
struct super_block *sb_cf, *sb_plain;
struct ext4_inode_info *ei;
struct ext4_sb_info *sbi;
struct inode *plain_dir;
struct unicode_map *um;
static const char invalid_utf8[] = "\xc3\x28";
struct dx_hash_info folded_dir = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info plain = {
.hash_version = DX_HASH_HALF_MD4,
};
int ret, ret_cf, ret_plain;
sb_cf = kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL);
sb_plain = kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
plain_dir = kunit_kzalloc(test, sizeof(*plain_dir), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sb_cf);
KUNIT_ASSERT_NOT_NULL(test, sb_plain);
KUNIT_ASSERT_NOT_NULL(test, ei);
KUNIT_ASSERT_NOT_NULL(test, sbi);
KUNIT_ASSERT_NOT_NULL(test, plain_dir);
um = utf8_load(UTF8_LATEST);
if (IS_ERR(um)) {
kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
um);
return;
}
ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi);
sb_cf->s_encoding = um;
ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ext4_hash_init_fake_dir(plain_dir, sb_plain);
ret_cf = ext4fs_dirhash(&ei->vfs_inode, invalid_utf8,
sizeof(invalid_utf8) - 1, &folded_dir);
ret_plain = ext4fs_dirhash(plain_dir, invalid_utf8,
sizeof(invalid_utf8) - 1, &plain);
KUNIT_ASSERT_EQ(test, ret_cf, 0);
KUNIT_ASSERT_EQ(test, ret_plain, 0);
KUNIT_EXPECT_EQ(test, folded_dir.hash, plain.hash);
KUNIT_EXPECT_EQ(test, folded_dir.minor_hash, plain.minor_hash);
}
#endif
static struct kunit_case ext4_hash_test_cases[] = {
KUNIT_CASE(test_ext4fs_dirhash_vectors),
KUNIT_CASE(test_ext4fs_dirhash_seed_changes_result),
KUNIT_CASE(test_ext4fs_dirhash_invalid_version_returns_einval),
KUNIT_CASE(test_ext4fs_dirhash_siphash_without_key_returns_einval),
KUNIT_CASE(test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii),
#if IS_ENABLED(CONFIG_UNICODE)
KUNIT_CASE(test_ext4fs_dirhash_casefolded_names_hash_consistently),
KUNIT_CASE(test_ext4fs_dirhash_casefold_fallback),
#endif
{}
};
static struct kunit_suite ext4_hash_test_suite = {
.name = "ext4_hash",
.test_cases = ext4_hash_test_cases,
};
kunit_test_suites(&ext4_hash_test_suite);
MODULE_LICENSE("GPL");
+347
View File
@@ -0,0 +1,347 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/hash.c
*
* Copyright (C) 2002 by Theodore Ts'o
*/
#include <linux/fs.h>
#include <linux/unicode.h>
#include <linux/compiler.h>
#include <linux/bitops.h>
#include <linux/unaligned.h>
#include "ext4.h"
#define DELTA 0x9E3779B9
static void TEA_transform(__u32 buf[4], __u32 const in[])
{
__u32 sum = 0;
__u32 b0 = buf[0], b1 = buf[1];
__u32 a = in[0], b = in[1], c = in[2], d = in[3];
int n = 16;
do {
sum += DELTA;
b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b);
b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d);
} while (--n);
buf[0] += b0;
buf[1] += b1;
}
/* F, G and H are basic MD4 functions: selection, majority, parity */
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
/*
* The generic round function. The application is so specific that
* we don't bother protecting all the arguments with parens, as is generally
* good macro practice, in favor of extra legibility.
* Rotation is separate from addition to prevent recomputation
*/
#define ROUND(f, a, b, c, d, x, s) \
(a += f(b, c, d) + x, a = rol32(a, s))
#define K1 0
#define K2 013240474631UL
#define K3 015666365641UL
/*
* Basic cut-down MD4 transform. Returns only 32 bits of result.
*/
static __u32 half_md4_transform(__u32 buf[4], __u32 const in[8])
{
__u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
/* Round 1 */
ROUND(F, a, b, c, d, in[0] + K1, 3);
ROUND(F, d, a, b, c, in[1] + K1, 7);
ROUND(F, c, d, a, b, in[2] + K1, 11);
ROUND(F, b, c, d, a, in[3] + K1, 19);
ROUND(F, a, b, c, d, in[4] + K1, 3);
ROUND(F, d, a, b, c, in[5] + K1, 7);
ROUND(F, c, d, a, b, in[6] + K1, 11);
ROUND(F, b, c, d, a, in[7] + K1, 19);
/* Round 2 */
ROUND(G, a, b, c, d, in[1] + K2, 3);
ROUND(G, d, a, b, c, in[3] + K2, 5);
ROUND(G, c, d, a, b, in[5] + K2, 9);
ROUND(G, b, c, d, a, in[7] + K2, 13);
ROUND(G, a, b, c, d, in[0] + K2, 3);
ROUND(G, d, a, b, c, in[2] + K2, 5);
ROUND(G, c, d, a, b, in[4] + K2, 9);
ROUND(G, b, c, d, a, in[6] + K2, 13);
/* Round 3 */
ROUND(H, a, b, c, d, in[3] + K3, 3);
ROUND(H, d, a, b, c, in[7] + K3, 9);
ROUND(H, c, d, a, b, in[2] + K3, 11);
ROUND(H, b, c, d, a, in[6] + K3, 15);
ROUND(H, a, b, c, d, in[1] + K3, 3);
ROUND(H, d, a, b, c, in[5] + K3, 9);
ROUND(H, c, d, a, b, in[0] + K3, 11);
ROUND(H, b, c, d, a, in[4] + K3, 15);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
return buf[1]; /* "most hashed" word */
}
#undef ROUND
#undef K1
#undef K2
#undef K3
#undef F
#undef G
#undef H
/* The old legacy hash */
static __u32 dx_hack_hash_unsigned(const char *name, int len)
{
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
const unsigned char *ucp = (const unsigned char *) name;
while (len--) {
hash = hash1 + (hash0 ^ (((int) *ucp++) * 7152373));
if (hash & 0x80000000)
hash -= 0x7fffffff;
hash1 = hash0;
hash0 = hash;
}
return hash0 << 1;
}
static __u32 dx_hack_hash_signed(const char *name, int len)
{
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
const signed char *scp = (const signed char *) name;
while (len--) {
hash = hash1 + (hash0 ^ (((int) *scp++) * 7152373));
if (hash & 0x80000000)
hash -= 0x7fffffff;
hash1 = hash0;
hash0 = hash;
}
return hash0 << 1;
}
static void str2hashbuf_signed(const char *msg, int len, __u32 *buf, int num)
{
__u32 pad, val;
int i;
const signed char *scp = (const signed char *) msg;
pad = (__u32)len | ((__u32)len << 8);
pad |= pad << 16;
if (len > num*4)
len = num * 4;
while (len >= 4) {
val = ((__u32)scp[0] << 24) + ((__u32)scp[1] << 16) + ((__u32)scp[2] << 8) + scp[3];
*buf++ = val;
scp += 4;
len -= 4;
num--;
}
val = pad;
for (i = 0; i < len; i++)
val = scp[i] + (val << 8);
if (--num >= 0)
*buf++ = val;
while (--num >= 0)
*buf++ = pad;
}
static void str2hashbuf_unsigned(const char *msg, int len, __u32 *buf, int num)
{
__u32 pad, val;
int i;
const unsigned char *ucp = (const unsigned char *) msg;
pad = (__u32)len | ((__u32)len << 8);
pad |= pad << 16;
if (len > num*4)
len = num * 4;
while (len >= 4) {
val = get_unaligned_be32(ucp);
*buf++ = val;
ucp += 4;
len -= 4;
num--;
}
val = pad;
for (i = 0; i < len; i++)
val = ucp[i] + (val << 8);
if (--num >= 0)
*buf++ = val;
while (--num >= 0)
*buf++ = pad;
}
/*
* Returns the hash of a filename. If len is 0 and name is NULL, then
* this function can be used to test whether or not a hash version is
* supported.
*
* The seed is an 4 longword (32 bits) "secret" which can be used to
* uniquify a hash. If the seed is all zero's, then some default seed
* may be used.
*
* A particular hash version specifies whether or not the seed is
* represented, and whether or not the returned hash is 32 bits or 64
* bits. 32 bit hashes will return 0 for the minor hash.
*/
static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
struct dx_hash_info *hinfo)
{
__u32 hash;
__u32 minor_hash = 0;
const char *p;
int i;
__u32 in[8], buf[4];
bool use_unsigned = false;
/* Initialize the default seed for the hash checksum functions */
buf[0] = 0x67452301;
buf[1] = 0xefcdab89;
buf[2] = 0x98badcfe;
buf[3] = 0x10325476;
/* Check to see if the seed is all zero's */
if (hinfo->seed) {
for (i = 0; i < 4; i++) {
if (hinfo->seed[i]) {
memcpy(buf, hinfo->seed, sizeof(buf));
break;
}
}
}
switch (hinfo->hash_version) {
case DX_HASH_LEGACY_UNSIGNED:
hash = dx_hack_hash_unsigned(name, len);
break;
case DX_HASH_LEGACY:
hash = dx_hack_hash_signed(name, len);
break;
case DX_HASH_HALF_MD4_UNSIGNED:
use_unsigned = true;
fallthrough;
case DX_HASH_HALF_MD4:
p = name;
while (len > 0) {
if (use_unsigned)
str2hashbuf_unsigned(p, len, in, 8);
else
str2hashbuf_signed(p, len, in, 8);
half_md4_transform(buf, in);
len -= 32;
p += 32;
}
minor_hash = buf[2];
hash = buf[1];
break;
case DX_HASH_TEA_UNSIGNED:
use_unsigned = true;
fallthrough;
case DX_HASH_TEA:
p = name;
while (len > 0) {
if (use_unsigned)
str2hashbuf_unsigned(p, len, in, 4);
else
str2hashbuf_signed(p, len, in, 4);
TEA_transform(buf, in);
len -= 16;
p += 16;
}
hash = buf[0];
minor_hash = buf[1];
break;
case DX_HASH_SIPHASH:
{
struct qstr qname = QSTR_INIT(name, len);
__u64 combined_hash;
if (fscrypt_has_encryption_key(dir)) {
combined_hash = fscrypt_fname_siphash(dir, &qname);
} else {
ext4_warning_inode(dir, "Siphash requires key");
return -EINVAL;
}
hash = (__u32)(combined_hash >> 32);
minor_hash = (__u32)combined_hash;
break;
}
default:
hinfo->hash = 0;
hinfo->minor_hash = 0;
ext4_warning(dir->i_sb,
"invalid/unsupported hash tree version %u",
hinfo->hash_version);
return -EINVAL;
}
hash = hash & ~1;
if (hash == (EXT4_HTREE_EOF_32BIT << 1))
hash = (EXT4_HTREE_EOF_32BIT - 1) << 1;
hinfo->hash = hash;
hinfo->minor_hash = minor_hash;
return 0;
}
int ext4fs_dirhash(const struct inode *dir, const char *name, int len,
struct dx_hash_info *hinfo)
{
#if IS_ENABLED(CONFIG_UNICODE)
const struct unicode_map *um = dir->i_sb->s_encoding;
int r, dlen;
unsigned char *buff;
struct qstr qstr = {.name = name, .len = len };
if (len && IS_CASEFOLDED(dir) &&
(!IS_ENCRYPTED(dir) || fscrypt_has_encryption_key(dir))) {
buff = kzalloc(PATH_MAX, GFP_KERNEL);
if (!buff)
return -ENOMEM;
dlen = utf8_casefold(um, &qstr, buff, PATH_MAX);
if (dlen < 0) {
kfree(buff);
goto opaque_seq;
}
r = __ext4fs_dirhash(dir, buff, dlen, hinfo);
kfree(buff);
return r;
}
opaque_seq:
#endif
return __ext4fs_dirhash(dir, name, len, hinfo);
}
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4fs_dirhash);
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+283
View File
@@ -0,0 +1,283 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit test of ext4 inode that verify the seconds part of [a/c/m]
* timestamps in ext4 inode structs are decoded correctly.
*/
#include <kunit/test.h>
#include <linux/kernel.h>
#include <linux/time64.h>
#include "ext4.h"
/*
* For constructing the nonnegative timestamp lower bound value.
* binary: 00000000 00000000 00000000 00000000
*/
#define LOWER_MSB_0 0L
/*
* For constructing the nonnegative timestamp upper bound value.
* binary: 01111111 11111111 11111111 11111111
*
*/
#define UPPER_MSB_0 0x7fffffffL
/*
* For constructing the negative timestamp lower bound value.
* binary: 10000000 00000000 00000000 00000000
*/
#define LOWER_MSB_1 (-(UPPER_MSB_0) - 1L) /* avoid overflow */
/*
* For constructing the negative timestamp upper bound value.
* binary: 11111111 11111111 11111111 11111111
*/
#define UPPER_MSB_1 (-1L)
/*
* Upper bound for nanoseconds value supported by the encoding.
* binary: 00111111 11111111 11111111 11111111
*/
#define MAX_NANOSECONDS ((1L << 30) - 1)
#define CASE_NAME_FORMAT "%s: msb:%x lower_bound:%x extra_bits: %x"
#define LOWER_BOUND_NEG_NO_EXTRA_BITS_CASE\
"1901-12-13 Lower bound of 32bit < 0 timestamp, no extra bits"
#define UPPER_BOUND_NEG_NO_EXTRA_BITS_CASE\
"1969-12-31 Upper bound of 32bit < 0 timestamp, no extra bits"
#define LOWER_BOUND_NONNEG_NO_EXTRA_BITS_CASE\
"1970-01-01 Lower bound of 32bit >=0 timestamp, no extra bits"
#define UPPER_BOUND_NONNEG_NO_EXTRA_BITS_CASE\
"2038-01-19 Upper bound of 32bit >=0 timestamp, no extra bits"
#define LOWER_BOUND_NEG_LO_1_CASE\
"2038-01-19 Lower bound of 32bit <0 timestamp, lo extra sec bit on"
#define UPPER_BOUND_NEG_LO_1_CASE\
"2106-02-07 Upper bound of 32bit <0 timestamp, lo extra sec bit on"
#define LOWER_BOUND_NONNEG_LO_1_CASE\
"2106-02-07 Lower bound of 32bit >=0 timestamp, lo extra sec bit on"
#define UPPER_BOUND_NONNEG_LO_1_CASE\
"2174-02-25 Upper bound of 32bit >=0 timestamp, lo extra sec bit on"
#define LOWER_BOUND_NEG_HI_1_CASE\
"2174-02-25 Lower bound of 32bit <0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NEG_HI_1_CASE\
"2242-03-16 Upper bound of 32bit <0 timestamp, hi extra sec bit on"
#define LOWER_BOUND_NONNEG_HI_1_CASE\
"2242-03-16 Lower bound of 32bit >=0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NONNEG_HI_1_CASE\
"2310-04-04 Upper bound of 32bit >=0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NONNEG_HI_1_NS_1_CASE\
"2310-04-04 Upper bound of 32bit>=0 timestamp, hi extra sec bit 1. 1 ns"
#define LOWER_BOUND_NONNEG_HI_1_NS_MAX_CASE\
"2378-04-22 Lower bound of 32bit>= timestamp. Extra sec bits 1. Max ns"
#define LOWER_BOUND_NONNEG_EXTRA_BITS_1_CASE\
"2378-04-22 Lower bound of 32bit >=0 timestamp. All extra sec bits on"
#define UPPER_BOUND_NONNEG_EXTRA_BITS_1_CASE\
"2446-05-10 Upper bound of 32bit >=0 timestamp. All extra sec bits on"
struct timestamp_expectation {
const char *test_case_name;
struct timespec64 expected;
u32 extra_bits;
bool msb_set;
bool lower_bound;
};
static const struct timestamp_expectation test_data[] = {
{
.test_case_name = LOWER_BOUND_NEG_NO_EXTRA_BITS_CASE,
.msb_set = true,
.lower_bound = true,
.extra_bits = 0,
.expected = {.tv_sec = -0x80000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NEG_NO_EXTRA_BITS_CASE,
.msb_set = true,
.lower_bound = false,
.extra_bits = 0,
.expected = {.tv_sec = -1LL, .tv_nsec = 0L},
},
{
.test_case_name = LOWER_BOUND_NONNEG_NO_EXTRA_BITS_CASE,
.msb_set = false,
.lower_bound = true,
.extra_bits = 0,
.expected = {0LL, 0L},
},
{
.test_case_name = UPPER_BOUND_NONNEG_NO_EXTRA_BITS_CASE,
.msb_set = false,
.lower_bound = false,
.extra_bits = 0,
.expected = {.tv_sec = 0x7fffffffLL, .tv_nsec = 0L},
},
{
.test_case_name = LOWER_BOUND_NEG_LO_1_CASE,
.msb_set = true,
.lower_bound = true,
.extra_bits = 1,
.expected = {.tv_sec = 0x80000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NEG_LO_1_CASE,
.msb_set = true,
.lower_bound = false,
.extra_bits = 1,
.expected = {.tv_sec = 0xffffffffLL, .tv_nsec = 0L},
},
{
.test_case_name = LOWER_BOUND_NONNEG_LO_1_CASE,
.msb_set = false,
.lower_bound = true,
.extra_bits = 1,
.expected = {.tv_sec = 0x100000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NONNEG_LO_1_CASE,
.msb_set = false,
.lower_bound = false,
.extra_bits = 1,
.expected = {.tv_sec = 0x17fffffffLL, .tv_nsec = 0L},
},
{
.test_case_name = LOWER_BOUND_NEG_HI_1_CASE,
.msb_set = true,
.lower_bound = true,
.extra_bits = 2,
.expected = {.tv_sec = 0x180000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NEG_HI_1_CASE,
.msb_set = true,
.lower_bound = false,
.extra_bits = 2,
.expected = {.tv_sec = 0x1ffffffffLL, .tv_nsec = 0L},
},
{
.test_case_name = LOWER_BOUND_NONNEG_HI_1_CASE,
.msb_set = false,
.lower_bound = true,
.extra_bits = 2,
.expected = {.tv_sec = 0x200000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NONNEG_HI_1_CASE,
.msb_set = false,
.lower_bound = false,
.extra_bits = 2,
.expected = {.tv_sec = 0x27fffffffLL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NONNEG_HI_1_NS_1_CASE,
.msb_set = false,
.lower_bound = false,
.extra_bits = 6,
.expected = {.tv_sec = 0x27fffffffLL, .tv_nsec = 1L},
},
{
.test_case_name = LOWER_BOUND_NONNEG_HI_1_NS_MAX_CASE,
.msb_set = false,
.lower_bound = true,
.extra_bits = 0xFFFFFFFF,
.expected = {.tv_sec = 0x300000000LL,
.tv_nsec = MAX_NANOSECONDS},
},
{
.test_case_name = LOWER_BOUND_NONNEG_EXTRA_BITS_1_CASE,
.msb_set = false,
.lower_bound = true,
.extra_bits = 3,
.expected = {.tv_sec = 0x300000000LL, .tv_nsec = 0L},
},
{
.test_case_name = UPPER_BOUND_NONNEG_EXTRA_BITS_1_CASE,
.msb_set = false,
.lower_bound = false,
.extra_bits = 3,
.expected = {.tv_sec = 0x37fffffffLL, .tv_nsec = 0L},
}
};
static void timestamp_expectation_to_desc(const struct timestamp_expectation *t,
char *desc)
{
strscpy(desc, t->test_case_name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(ext4_inode, test_data, timestamp_expectation_to_desc);
static time64_t get_32bit_time(const struct timestamp_expectation * const test)
{
if (test->msb_set) {
if (test->lower_bound)
return LOWER_MSB_1;
return UPPER_MSB_1;
}
if (test->lower_bound)
return LOWER_MSB_0;
return UPPER_MSB_0;
}
/*
* Test data is derived from the table in the Inode Timestamps section of
* Documentation/filesystems/ext4/inodes.rst.
*/
static void inode_test_xtimestamp_decoding(struct kunit *test)
{
struct timespec64 timestamp;
struct timestamp_expectation *test_param =
(struct timestamp_expectation *)(test->param_value);
timestamp = ext4_decode_extra_time(
cpu_to_le32(get_32bit_time(test_param)),
cpu_to_le32(test_param->extra_bits));
KUNIT_EXPECT_EQ_MSG(test,
test_param->expected.tv_sec,
timestamp.tv_sec,
CASE_NAME_FORMAT,
test_param->test_case_name,
test_param->msb_set,
test_param->lower_bound,
test_param->extra_bits);
KUNIT_EXPECT_EQ_MSG(test,
test_param->expected.tv_nsec,
timestamp.tv_nsec,
CASE_NAME_FORMAT,
test_param->test_case_name,
test_param->msb_set,
test_param->lower_bound,
test_param->extra_bits);
}
static struct kunit_case ext4_inode_test_cases[] = {
KUNIT_CASE_PARAM(inode_test_xtimestamp_decoding, ext4_inode_gen_params),
{}
};
static struct kunit_suite ext4_inode_test_suite = {
.name = "ext4_inode_test",
.test_cases = ext4_inode_test_cases,
};
kunit_test_suites(&ext4_inode_test_suite);
MODULE_DESCRIPTION("KUnit test of ext4 inode timestamp decoding");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+303
View File
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0
/*
* fs/ext4/mballoc.h
*
* Written by: Alex Tomas <alex@clusterfs.com>
*
*/
#ifndef _EXT4_MBALLOC_H
#define _EXT4_MBALLOC_H
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/quotaops.h>
#include <linux/buffer_head.h>
#include <linux/module.h>
#include <linux/swap.h>
#include <linux/proc_fs.h>
#include <linux/pagemap.h>
#include <linux/seq_file.h>
#include <linux/blkdev.h>
#include <linux/mutex.h>
#include "ext4_jbd2.h"
#include "ext4.h"
/*
* mb_debug() dynamic printk msgs could be used to debug mballoc code.
*/
#ifdef CONFIG_EXT4_DEBUG
#define mb_debug(sb, fmt, ...) \
pr_debug("[%s/%d] EXT4-fs (%s): (%s, %d): %s: " fmt, \
current->comm, task_pid_nr(current), sb->s_id, \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
#else
#define mb_debug(sb, fmt, ...) no_printk(fmt, ##__VA_ARGS__)
#endif
#define EXT4_MB_HISTORY_ALLOC 1 /* allocation */
#define EXT4_MB_HISTORY_PREALLOC 2 /* preallocated blocks used */
/*
* How long mballoc can look for a best extent (in found extents)
*/
#define MB_DEFAULT_MAX_TO_SCAN 200
/*
* How long mballoc must look for a best extent
*/
#define MB_DEFAULT_MIN_TO_SCAN 10
/*
* with 's_mb_stats' allocator will collect stats that will be
* shown at umount. The collecting costs though!
*/
#define MB_DEFAULT_STATS 0
/*
* files smaller than MB_DEFAULT_STREAM_THRESHOLD are served
* by the stream allocator, which purpose is to pack requests
* as close each to other as possible to produce smooth I/O traffic
* We use locality group prealloc space for stream request.
* We can tune the same via /proc/fs/ext4/<partition>/stream_req
*/
#define MB_DEFAULT_STREAM_THRESHOLD 16 /* 64K */
/*
* for which requests use 2^N search using buddies
*/
#define MB_DEFAULT_ORDER2_REQS 2
/*
* default group prealloc size 512 blocks
*/
#define MB_DEFAULT_GROUP_PREALLOC 512
/*
* Number of groups to search linearly before performing group scanning
* optimization.
*/
#define MB_DEFAULT_LINEAR_LIMIT 4
/*
* Minimum number of groups that should be present in the file system to perform
* group scanning optimizations.
*/
#define MB_DEFAULT_LINEAR_SCAN_THRESHOLD 16
/*
* The maximum order upto which CR_BEST_AVAIL_LEN can trim a particular
* allocation request. Example, if we have an order 7 request and max trim order
* of 3, we can trim this request upto order 4.
*/
#define MB_DEFAULT_BEST_AVAIL_TRIM_ORDER 3
/*
* Number of valid buddy orders
*/
#define MB_NUM_ORDERS(sb) ((sb)->s_blocksize_bits + 2)
struct ext4_free_data {
/* this links the free block information from sb_info */
struct list_head efd_list;
/* this links the free block information from group_info */
struct rb_node efd_node;
/* group which free block extent belongs */
ext4_group_t efd_group;
/* free block extent */
ext4_grpblk_t efd_start_cluster;
ext4_grpblk_t efd_count;
/* transaction which freed this extent */
tid_t efd_tid;
};
struct ext4_prealloc_space {
union {
struct rb_node inode_node; /* for inode PA rbtree */
struct list_head lg_list; /* for lg PAs */
} pa_node;
struct list_head pa_group_list;
union {
struct list_head pa_tmp_list;
struct rcu_head pa_rcu;
} u;
spinlock_t pa_lock;
atomic_t pa_count;
unsigned pa_deleted;
ext4_fsblk_t pa_pstart; /* phys. block */
ext4_lblk_t pa_lstart; /* log. block */
ext4_grpblk_t pa_len; /* len of preallocated chunk */
ext4_grpblk_t pa_free; /* how many blocks are free */
unsigned short pa_type; /* pa type. inode or group */
union {
rwlock_t *inode_lock; /* locks the rbtree holding this PA */
spinlock_t *lg_lock; /* locks the lg list holding this PA */
} pa_node_lock;
struct inode *pa_inode; /* used to get the inode during group discard */
};
enum {
MB_INODE_PA = 0,
MB_GROUP_PA = 1
};
struct ext4_free_extent {
ext4_lblk_t fe_logical;
ext4_grpblk_t fe_start; /* In cluster units */
ext4_group_t fe_group;
ext4_grpblk_t fe_len; /* In cluster units */
};
/*
* Locality group:
* we try to group all related changes together
* so that writeback can flush/allocate them together as well
* Size of lg_prealloc_list hash is determined by MB_DEFAULT_GROUP_PREALLOC
* (512). We store prealloc space into the hash based on the pa_free blocks
* order value.ie, fls(pa_free)-1;
*/
#define PREALLOC_TB_SIZE 10
struct ext4_locality_group {
/* for allocator */
/* to serialize allocates */
struct mutex lg_mutex;
/* list of preallocations */
struct list_head lg_prealloc_list[PREALLOC_TB_SIZE];
spinlock_t lg_prealloc_lock;
};
struct ext4_allocation_context {
struct inode *ac_inode;
struct super_block *ac_sb;
/* original request */
struct ext4_free_extent ac_o_ex;
/* goal request (normalized ac_o_ex) */
struct ext4_free_extent ac_g_ex;
/* the best found extent */
struct ext4_free_extent ac_b_ex;
/* copy of the best found extent taken before preallocation efforts */
struct ext4_free_extent ac_f_ex;
/*
* goal len can change in CR_BEST_AVAIL_LEN, so save the original len.
* This is used while adjusting the PA window and for accounting.
*/
ext4_grpblk_t ac_orig_goal_len;
ext4_group_t ac_prefetch_grp;
unsigned int ac_prefetch_ios;
unsigned int ac_prefetch_nr;
int ac_first_err;
__u32 ac_flags; /* allocation hints */
__u16 ac_groups_scanned;
__u16 ac_found;
__u16 ac_cX_found[EXT4_MB_NUM_CRS];
__u16 ac_tail;
__u16 ac_buddy;
__u8 ac_status;
__u8 ac_criteria;
__u8 ac_2order; /* if request is to allocate 2^N blocks and
* N > 0, the field stores N, otherwise 0 */
__u8 ac_op; /* operation, for history only */
struct ext4_buddy *ac_e4b;
struct folio *ac_bitmap_folio;
struct folio *ac_buddy_folio;
struct ext4_prealloc_space *ac_pa;
struct ext4_locality_group *ac_lg;
};
#define AC_STATUS_CONTINUE 1
#define AC_STATUS_FOUND 2
#define AC_STATUS_BREAK 3
struct ext4_buddy {
struct folio *bd_buddy_folio;
void *bd_buddy;
struct folio *bd_bitmap_folio;
void *bd_bitmap;
struct ext4_group_info *bd_info;
struct super_block *bd_sb;
__u16 bd_blkbits;
ext4_group_t bd_group;
};
static inline ext4_fsblk_t ext4_grp_offs_to_block(struct super_block *sb,
struct ext4_free_extent *fex)
{
return ext4_group_first_block_no(sb, fex->fe_group) +
(fex->fe_start << EXT4_SB(sb)->s_cluster_bits);
}
static inline loff_t extent_logical_end(struct ext4_sb_info *sbi,
struct ext4_free_extent *fex)
{
/* Use loff_t to avoid end exceeding ext4_lblk_t max. */
return (loff_t)fex->fe_logical + EXT4_C2B(sbi, fex->fe_len);
}
static inline loff_t pa_logical_end(struct ext4_sb_info *sbi,
struct ext4_prealloc_space *pa)
{
/* Use loff_t to avoid end exceeding ext4_lblk_t max. */
return (loff_t)pa->pa_lstart + EXT4_C2B(sbi, pa->pa_len);
}
typedef int (*ext4_mballoc_query_range_fn)(
struct super_block *sb,
ext4_group_t agno,
ext4_grpblk_t start,
ext4_grpblk_t len,
void *priv);
int
ext4_mballoc_query_range(
struct super_block *sb,
ext4_group_t agno,
ext4_grpblk_t start,
ext4_grpblk_t end,
ext4_mballoc_query_range_fn meta_formatter,
ext4_mballoc_query_range_fn formatter,
void *priv);
extern int ext4_mb_mark_context(handle_t *handle,
struct super_block *sb, bool state,
ext4_group_t group, ext4_grpblk_t blkoff,
ext4_grpblk_t len, int flags,
ext4_grpblk_t *ret_changed);
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
extern void mb_clear_bits_test(void *bm, int cur, int len);
extern ext4_fsblk_t
ext4_mb_new_blocks_simple_test(struct ext4_allocation_request *ar,
int *errp);
extern int mb_find_next_zero_bit_test(void *addr, int max, int start);
extern int mb_find_next_bit_test(void *addr, int max, int start);
extern void mb_clear_bit_test(int bit, void *addr);
extern int mb_test_bit_test(int bit, void *addr);
extern int
ext4_mb_mark_diskspace_used_test(struct ext4_allocation_context *ac,
handle_t *handle);
extern int mb_mark_used_test(struct ext4_buddy *e4b,
struct ext4_free_extent *ex);
extern void ext4_mb_generate_buddy_test(struct super_block *sb,
void *buddy, void *bitmap, ext4_group_t group,
struct ext4_group_info *grp);
extern int ext4_mb_load_buddy_test(struct super_block *sb,
ext4_group_t group, struct ext4_buddy *e4b);
extern void ext4_mb_unload_buddy_test(struct ext4_buddy *e4b);
extern void mb_free_blocks_test(struct inode *inode,
struct ext4_buddy *e4b, int first, int count);
extern void ext4_free_blocks_simple_test(struct inode *inode,
ext4_fsblk_t block, unsigned long count);
#endif
#endif
+684
View File
@@ -0,0 +1,684 @@
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright IBM Corporation, 2007
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
*/
#include <linux/slab.h>
#include "ext4_jbd2.h"
#include "ext4_extents.h"
/*
* The contiguous blocks details which can be
* represented by a single extent
*/
struct migrate_struct {
ext4_lblk_t first_block, last_block, curr_block;
ext4_fsblk_t first_pblock, last_pblock;
};
static int finish_range(handle_t *handle, struct inode *inode,
struct migrate_struct *lb)
{
int retval = 0, needed;
struct ext4_extent newext;
struct ext4_ext_path *path;
if (lb->first_pblock == 0)
return 0;
/* Add the extent to temp inode*/
newext.ee_block = cpu_to_le32(lb->first_block);
newext.ee_len = cpu_to_le16(lb->last_block - lb->first_block + 1);
ext4_ext_store_pblock(&newext, lb->first_pblock);
/* Locking only for convenience since we are operating on temp inode */
down_write(&EXT4_I(inode)->i_data_sem);
path = ext4_find_extent(inode, lb->first_block, NULL, 0);
if (IS_ERR(path)) {
retval = PTR_ERR(path);
goto err_out;
}
/*
* Calculate the credit needed to inserting this extent
* Since we are doing this in loop we may accumulate extra
* credit. But below we try to not accumulate too much
* of them by restarting the journal.
*/
needed = ext4_ext_calc_credits_for_single_extent(inode,
lb->last_block - lb->first_block + 1, path);
retval = ext4_datasem_ensure_credits(handle, inode, needed, needed, 0);
if (retval < 0)
goto err_out;
path = ext4_ext_insert_extent(handle, inode, path, &newext, 0);
if (IS_ERR(path))
retval = PTR_ERR(path);
err_out:
up_write((&EXT4_I(inode)->i_data_sem));
ext4_free_ext_path(path);
lb->first_pblock = 0;
return retval;
}
static int update_extent_range(handle_t *handle, struct inode *inode,
ext4_fsblk_t pblock, struct migrate_struct *lb)
{
int retval;
/*
* See if we can add on to the existing range (if it exists)
*/
if (lb->first_pblock &&
(lb->last_pblock+1 == pblock) &&
(lb->last_block+1 == lb->curr_block)) {
lb->last_pblock = pblock;
lb->last_block = lb->curr_block;
lb->curr_block++;
return 0;
}
/*
* Start a new range.
*/
retval = finish_range(handle, inode, lb);
lb->first_pblock = lb->last_pblock = pblock;
lb->first_block = lb->last_block = lb->curr_block;
lb->curr_block++;
return retval;
}
static int update_ind_extent_range(handle_t *handle, struct inode *inode,
ext4_fsblk_t pblock,
struct migrate_struct *lb)
{
struct buffer_head *bh;
__le32 *i_data;
int i, retval = 0;
unsigned long max_entries = inode->i_sb->s_blocksize >> 2;
bh = ext4_sb_bread(inode->i_sb, pblock, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
i_data = (__le32 *)bh->b_data;
for (i = 0; i < max_entries; i++) {
if (i_data[i]) {
retval = update_extent_range(handle, inode,
le32_to_cpu(i_data[i]), lb);
if (retval)
break;
} else {
lb->curr_block++;
}
}
put_bh(bh);
return retval;
}
static int update_dind_extent_range(handle_t *handle, struct inode *inode,
ext4_fsblk_t pblock,
struct migrate_struct *lb)
{
struct buffer_head *bh;
__le32 *i_data;
int i, retval = 0;
unsigned long max_entries = inode->i_sb->s_blocksize >> 2;
bh = ext4_sb_bread(inode->i_sb, pblock, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
i_data = (__le32 *)bh->b_data;
for (i = 0; i < max_entries; i++) {
if (i_data[i]) {
retval = update_ind_extent_range(handle, inode,
le32_to_cpu(i_data[i]), lb);
if (retval)
break;
} else {
/* Only update the file block number */
lb->curr_block += max_entries;
}
}
put_bh(bh);
return retval;
}
static int update_tind_extent_range(handle_t *handle, struct inode *inode,
ext4_fsblk_t pblock,
struct migrate_struct *lb)
{
struct buffer_head *bh;
__le32 *i_data;
int i, retval = 0;
unsigned long max_entries = inode->i_sb->s_blocksize >> 2;
bh = ext4_sb_bread(inode->i_sb, pblock, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
i_data = (__le32 *)bh->b_data;
for (i = 0; i < max_entries; i++) {
if (i_data[i]) {
retval = update_dind_extent_range(handle, inode,
le32_to_cpu(i_data[i]), lb);
if (retval)
break;
} else {
/* Only update the file block number */
lb->curr_block += max_entries * max_entries;
}
}
put_bh(bh);
return retval;
}
static int free_dind_blocks(handle_t *handle,
struct inode *inode, __le32 i_data)
{
int i;
__le32 *tmp_idata;
struct buffer_head *bh;
struct super_block *sb = inode->i_sb;
unsigned long max_entries = inode->i_sb->s_blocksize >> 2;
int err;
bh = ext4_sb_bread(sb, le32_to_cpu(i_data), 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
tmp_idata = (__le32 *)bh->b_data;
for (i = 0; i < max_entries; i++) {
if (tmp_idata[i]) {
err = ext4_journal_ensure_credits(handle,
EXT4_RESERVE_TRANS_BLOCKS,
ext4_free_metadata_revoke_credits(sb, 1));
if (err < 0) {
put_bh(bh);
return err;
}
ext4_free_blocks(handle, inode, NULL,
le32_to_cpu(tmp_idata[i]), 1,
EXT4_FREE_BLOCKS_METADATA |
EXT4_FREE_BLOCKS_FORGET);
}
}
put_bh(bh);
err = ext4_journal_ensure_credits(handle, EXT4_RESERVE_TRANS_BLOCKS,
ext4_free_metadata_revoke_credits(sb, 1));
if (err < 0)
return err;
ext4_free_blocks(handle, inode, NULL, le32_to_cpu(i_data), 1,
EXT4_FREE_BLOCKS_METADATA |
EXT4_FREE_BLOCKS_FORGET);
return 0;
}
static int free_tind_blocks(handle_t *handle,
struct inode *inode, __le32 i_data)
{
int i, retval = 0;
__le32 *tmp_idata;
struct buffer_head *bh;
unsigned long max_entries = inode->i_sb->s_blocksize >> 2;
bh = ext4_sb_bread(inode->i_sb, le32_to_cpu(i_data), 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
tmp_idata = (__le32 *)bh->b_data;
for (i = 0; i < max_entries; i++) {
if (tmp_idata[i]) {
retval = free_dind_blocks(handle,
inode, tmp_idata[i]);
if (retval) {
put_bh(bh);
return retval;
}
}
}
put_bh(bh);
retval = ext4_journal_ensure_credits(handle, EXT4_RESERVE_TRANS_BLOCKS,
ext4_free_metadata_revoke_credits(inode->i_sb, 1));
if (retval < 0)
return retval;
ext4_free_blocks(handle, inode, NULL, le32_to_cpu(i_data), 1,
EXT4_FREE_BLOCKS_METADATA |
EXT4_FREE_BLOCKS_FORGET);
return 0;
}
static int free_ind_block(handle_t *handle, struct inode *inode, __le32 *i_data)
{
int retval;
/* ei->i_data[EXT4_IND_BLOCK] */
if (i_data[0]) {
retval = ext4_journal_ensure_credits(handle,
EXT4_RESERVE_TRANS_BLOCKS,
ext4_free_metadata_revoke_credits(inode->i_sb, 1));
if (retval < 0)
return retval;
ext4_free_blocks(handle, inode, NULL,
le32_to_cpu(i_data[0]), 1,
EXT4_FREE_BLOCKS_METADATA |
EXT4_FREE_BLOCKS_FORGET);
}
/* ei->i_data[EXT4_DIND_BLOCK] */
if (i_data[1]) {
retval = free_dind_blocks(handle, inode, i_data[1]);
if (retval)
return retval;
}
/* ei->i_data[EXT4_TIND_BLOCK] */
if (i_data[2]) {
retval = free_tind_blocks(handle, inode, i_data[2]);
if (retval)
return retval;
}
return 0;
}
static int ext4_ext_swap_inode_data(handle_t *handle, struct inode *inode,
struct inode *tmp_inode)
{
int retval, retval2 = 0;
__le32 i_data[3];
struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_inode_info *tmp_ei = EXT4_I(tmp_inode);
/*
* One credit accounted for writing the
* i_data field of the original inode
*/
retval = ext4_journal_ensure_credits(handle, 1, 0);
if (retval < 0)
goto err_out;
i_data[0] = ei->i_data[EXT4_IND_BLOCK];
i_data[1] = ei->i_data[EXT4_DIND_BLOCK];
i_data[2] = ei->i_data[EXT4_TIND_BLOCK];
down_write(&EXT4_I(inode)->i_data_sem);
/*
* if EXT4_STATE_EXT_MIGRATE is cleared a block allocation
* happened after we started the migrate. We need to
* fail the migrate
*/
if (!ext4_test_inode_state(inode, EXT4_STATE_EXT_MIGRATE)) {
retval = -EAGAIN;
up_write(&EXT4_I(inode)->i_data_sem);
goto err_out;
} else
ext4_clear_inode_state(inode, EXT4_STATE_EXT_MIGRATE);
/*
* We have the extent map build with the tmp inode.
* Now copy the i_data across
*/
ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
memcpy(ei->i_data, tmp_ei->i_data, sizeof(ei->i_data));
/*
* Update i_blocks with the new blocks that got
* allocated while adding extents for extent index
* blocks.
*
* While converting to extents we need not
* update the original inode i_blocks for extent blocks
* via quota APIs. The quota update happened via tmp_inode already.
*/
spin_lock(&inode->i_lock);
inode->i_blocks += tmp_inode->i_blocks;
spin_unlock(&inode->i_lock);
up_write(&EXT4_I(inode)->i_data_sem);
/*
* We mark the inode dirty after, because we decrement the
* i_blocks when freeing the indirect meta-data blocks
*/
retval = free_ind_block(handle, inode, i_data);
retval2 = ext4_mark_inode_dirty(handle, inode);
if (unlikely(retval2 && !retval))
retval = retval2;
err_out:
return retval;
}
static int free_ext_idx(handle_t *handle, struct inode *inode,
struct ext4_extent_idx *ix)
{
int i, retval = 0;
ext4_fsblk_t block;
struct buffer_head *bh;
struct ext4_extent_header *eh;
block = ext4_idx_pblock(ix);
bh = ext4_sb_bread(inode->i_sb, block, 0);
if (IS_ERR(bh))
return PTR_ERR(bh);
eh = (struct ext4_extent_header *)bh->b_data;
if (eh->eh_depth != 0) {
ix = EXT_FIRST_INDEX(eh);
for (i = 0; i < le16_to_cpu(eh->eh_entries); i++, ix++) {
retval = free_ext_idx(handle, inode, ix);
if (retval) {
put_bh(bh);
return retval;
}
}
}
put_bh(bh);
retval = ext4_journal_ensure_credits(handle, EXT4_RESERVE_TRANS_BLOCKS,
ext4_free_metadata_revoke_credits(inode->i_sb, 1));
if (retval < 0)
return retval;
ext4_free_blocks(handle, inode, NULL, block, 1,
EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_FORGET);
return 0;
}
/*
* Free the extent meta data blocks only
*/
static int free_ext_block(handle_t *handle, struct inode *inode)
{
int i, retval = 0;
struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_extent_header *eh = (struct ext4_extent_header *)ei->i_data;
struct ext4_extent_idx *ix;
if (eh->eh_depth == 0)
/*
* No extra blocks allocated for extent meta data
*/
return 0;
ix = EXT_FIRST_INDEX(eh);
for (i = 0; i < le16_to_cpu(eh->eh_entries); i++, ix++) {
retval = free_ext_idx(handle, inode, ix);
if (retval)
return retval;
}
return retval;
}
int ext4_ext_migrate(struct inode *inode)
{
handle_t *handle;
int retval = 0, i;
__le32 *i_data;
struct ext4_inode_info *ei;
struct inode *tmp_inode = NULL;
struct migrate_struct lb;
unsigned long max_entries;
__u32 goal, tmp_csum_seed;
uid_t owner[2];
int alloc_ctx;
/*
* If the filesystem does not support extents, or the inode
* already is extent-based, error out.
*/
if (!ext4_has_feature_extents(inode->i_sb) ||
ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS) ||
ext4_has_inline_data(inode))
return -EINVAL;
if (S_ISLNK(inode->i_mode) && inode->i_blocks == 0)
/*
* don't migrate fast symlink
*/
return retval;
alloc_ctx = ext4_writepages_down_write(inode->i_sb);
/*
* Worst case we can touch the allocation bitmaps and a block
* group descriptor block. We do need to worry about
* credits for modifying the quota inode.
*/
handle = ext4_journal_start(inode, EXT4_HT_MIGRATE,
3 + EXT4_MAXQUOTAS_TRANS_BLOCKS(inode->i_sb));
if (IS_ERR(handle)) {
retval = PTR_ERR(handle);
goto out_unlock;
}
/*
* This operation rewrites the inode's block mapping layout
* (indirect to extents) and is not tracked in the fast commit
* log, so disable fast commits for this transaction.
*/
ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_MIGRATE, handle);
goal = ((((u32)inode->i_ino - 1) / EXT4_INODES_PER_GROUP(inode->i_sb)) *
EXT4_INODES_PER_GROUP(inode->i_sb)) + 1;
owner[0] = i_uid_read(inode);
owner[1] = i_gid_read(inode);
tmp_inode = ext4_new_inode(handle, d_inode(inode->i_sb->s_root),
S_IFREG, NULL, goal, owner, 0);
if (IS_ERR(tmp_inode)) {
retval = PTR_ERR(tmp_inode);
ext4_journal_stop(handle);
goto out_unlock;
}
/*
* Use the correct seed for checksum (i.e. the seed from 'inode'). This
* is so that the metadata blocks will have the correct checksum after
* the migration.
*/
ei = EXT4_I(inode);
tmp_csum_seed = EXT4_I(tmp_inode)->i_csum_seed;
EXT4_I(tmp_inode)->i_csum_seed = ei->i_csum_seed;
i_size_write(tmp_inode, i_size_read(inode));
/*
* Set the i_nlink to zero so it will be deleted later
* when we drop inode reference.
*/
clear_nlink(tmp_inode);
ext4_ext_tree_init(handle, tmp_inode);
ext4_journal_stop(handle);
/*
* start with one credit accounted for
* superblock modification.
*
* For the tmp_inode we already have committed the
* transaction that created the inode. Later as and
* when we add extents we extent the journal
*/
/*
* Even though we take i_rwsem we can still cause block
* allocation via mmap write to holes. If we have allocated
* new blocks we fail migrate. New block allocation will
* clear EXT4_STATE_EXT_MIGRATE flag. The flag is updated
* with i_data_sem held to prevent racing with block
* allocation.
*/
down_read(&EXT4_I(inode)->i_data_sem);
ext4_set_inode_state(inode, EXT4_STATE_EXT_MIGRATE);
up_read((&EXT4_I(inode)->i_data_sem));
handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1);
if (IS_ERR(handle)) {
retval = PTR_ERR(handle);
goto out_tmp_inode;
}
i_data = ei->i_data;
memset(&lb, 0, sizeof(lb));
/* 32 bit block address 4 bytes */
max_entries = inode->i_sb->s_blocksize >> 2;
for (i = 0; i < EXT4_NDIR_BLOCKS; i++) {
if (i_data[i]) {
retval = update_extent_range(handle, tmp_inode,
le32_to_cpu(i_data[i]), &lb);
if (retval)
goto err_out;
} else
lb.curr_block++;
}
if (i_data[EXT4_IND_BLOCK]) {
retval = update_ind_extent_range(handle, tmp_inode,
le32_to_cpu(i_data[EXT4_IND_BLOCK]), &lb);
if (retval)
goto err_out;
} else
lb.curr_block += max_entries;
if (i_data[EXT4_DIND_BLOCK]) {
retval = update_dind_extent_range(handle, tmp_inode,
le32_to_cpu(i_data[EXT4_DIND_BLOCK]), &lb);
if (retval)
goto err_out;
} else
lb.curr_block += max_entries * max_entries;
if (i_data[EXT4_TIND_BLOCK]) {
retval = update_tind_extent_range(handle, tmp_inode,
le32_to_cpu(i_data[EXT4_TIND_BLOCK]), &lb);
if (retval)
goto err_out;
}
/*
* Build the last extent
*/
retval = finish_range(handle, tmp_inode, &lb);
err_out:
if (retval)
/*
* Failure case delete the extent information with the
* tmp_inode
*/
free_ext_block(handle, tmp_inode);
else {
retval = ext4_ext_swap_inode_data(handle, inode, tmp_inode);
if (retval)
/*
* if we fail to swap inode data free the extent
* details of the tmp inode
*/
free_ext_block(handle, tmp_inode);
}
/* We mark the tmp_inode dirty via ext4_ext_tree_init. */
retval = ext4_journal_ensure_credits(handle, 1, 0);
if (retval < 0)
goto out_stop;
/*
* Mark the tmp_inode as of size zero
*/
i_size_write(tmp_inode, 0);
/*
* set the i_blocks count to zero
* so that the ext4_evict_inode() does the
* right job
*
* We don't need to take the i_lock because
* the inode is not visible to user space.
*/
tmp_inode->i_blocks = 0;
EXT4_I(tmp_inode)->i_csum_seed = tmp_csum_seed;
/* Reset the extent details */
ext4_ext_tree_init(handle, tmp_inode);
out_stop:
ext4_journal_stop(handle);
out_tmp_inode:
unlock_new_inode(tmp_inode);
iput(tmp_inode);
out_unlock:
ext4_writepages_up_write(inode->i_sb, alloc_ctx);
return retval;
}
/*
* Migrate a simple extent-based inode to use the i_blocks[] array
*/
int ext4_ind_migrate(struct inode *inode)
{
struct ext4_extent_header *eh;
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
struct ext4_super_block *es = sbi->s_es;
struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_extent *ex;
unsigned int i, len;
ext4_lblk_t start, end;
ext4_fsblk_t blk;
handle_t *handle;
int ret, ret2 = 0;
int alloc_ctx;
if (!ext4_has_feature_extents(inode->i_sb) ||
(!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
return -EINVAL;
if (ext4_has_feature_bigalloc(inode->i_sb))
return -EOPNOTSUPP;
/*
* In order to get correct extent info, force all delayed allocation
* blocks to be allocated, otherwise delayed allocation blocks may not
* be reflected and bypass the checks on extent header.
*/
if (test_opt(inode->i_sb, DELALLOC))
ext4_alloc_da_blocks(inode);
alloc_ctx = ext4_writepages_down_write(inode->i_sb);
handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto out_unlock;
}
/*
* This operation rewrites the inode's block mapping layout
* (extents to indirect blocks) and is not tracked in the fast
* commit log, so disable fast commits for this transaction.
*/
ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_MIGRATE, handle);
down_write(&EXT4_I(inode)->i_data_sem);
ret = ext4_ext_check_inode(inode);
if (ret)
goto errout;
eh = ext_inode_hdr(inode);
ex = EXT_FIRST_EXTENT(eh);
if (ext4_blocks_count(es) > EXT4_MAX_BLOCK_FILE_PHYS ||
eh->eh_depth != 0 || le16_to_cpu(eh->eh_entries) > 1) {
ret = -EOPNOTSUPP;
goto errout;
}
if (eh->eh_entries == 0)
blk = len = start = end = 0;
else {
len = le16_to_cpu(ex->ee_len);
blk = ext4_ext_pblock(ex);
start = le32_to_cpu(ex->ee_block);
end = start + len - 1;
if (end >= EXT4_NDIR_BLOCKS) {
ret = -EOPNOTSUPP;
goto errout;
}
}
ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS);
memset(ei->i_data, 0, sizeof(ei->i_data));
for (i = start; i <= end; i++)
ei->i_data[i] = cpu_to_le32(blk++);
ret2 = ext4_mark_inode_dirty(handle, inode);
if (unlikely(ret2 && !ret))
ret = ret2;
errout:
up_write(&EXT4_I(inode)->i_data_sem);
ext4_journal_stop(handle);
out_unlock:
ext4_writepages_up_write(inode->i_sb, alloc_ctx);
return ret;
}
+403
View File
@@ -0,0 +1,403 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/fs.h>
#include <linux/random.h>
#include <linux/buffer_head.h>
#include <linux/utsname.h>
#include <linux/kthread.h>
#include "ext4.h"
/* Checksumming functions */
static __le32 ext4_mmp_csum(struct super_block *sb, struct mmp_struct *mmp)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
int offset = offsetof(struct mmp_struct, mmp_checksum);
__u32 csum;
csum = ext4_chksum(sbi->s_csum_seed, (char *)mmp, offset);
return cpu_to_le32(csum);
}
static int ext4_mmp_csum_verify(struct super_block *sb, struct mmp_struct *mmp)
{
if (!ext4_has_feature_metadata_csum(sb))
return 1;
return mmp->mmp_checksum == ext4_mmp_csum(sb, mmp);
}
static void ext4_mmp_csum_set(struct super_block *sb, struct mmp_struct *mmp)
{
if (!ext4_has_feature_metadata_csum(sb))
return;
mmp->mmp_checksum = ext4_mmp_csum(sb, mmp);
}
/*
* Write the MMP block using REQ_SYNC to try to get the block on-disk
* faster.
*/
static int write_mmp_block_thawed(struct super_block *sb,
struct buffer_head *bh)
{
struct mmp_struct *mmp = (struct mmp_struct *)(bh->b_data);
ext4_mmp_csum_set(sb, mmp);
lock_buffer(bh);
bh_submit(bh, REQ_OP_WRITE | REQ_SYNC | REQ_META | REQ_PRIO,
bh_end_write);
wait_on_buffer(bh);
if (unlikely(!buffer_uptodate(bh)))
return -EIO;
return 0;
}
static int write_mmp_block(struct super_block *sb, struct buffer_head *bh)
{
/*
* We protect against freezing so that we don't create dirty buffers
* on frozen filesystem.
*/
scoped_guard(super_write, sb)
return write_mmp_block_thawed(sb, bh);
}
/*
* Read the MMP block. It _must_ be read from disk and hence we clear the
* uptodate flag on the buffer.
*/
static int read_mmp_block(struct super_block *sb, struct buffer_head **bh,
ext4_fsblk_t mmp_block)
{
struct mmp_struct *mmp;
int ret;
if (*bh)
clear_buffer_uptodate(*bh);
/* This would be sb_bread(sb, mmp_block), except we need to be sure
* that the MD RAID device cache has been bypassed, and that the read
* is not blocked in the elevator. */
if (!*bh) {
*bh = sb_getblk(sb, mmp_block);
if (!*bh) {
ret = -ENOMEM;
goto warn_exit;
}
}
lock_buffer(*bh);
ret = ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL, false);
if (ret)
goto warn_exit;
mmp = (struct mmp_struct *)((*bh)->b_data);
if (le32_to_cpu(mmp->mmp_magic) != EXT4_MMP_MAGIC) {
ret = -EFSCORRUPTED;
goto warn_exit;
}
if (!ext4_mmp_csum_verify(sb, mmp)) {
ret = -EFSBADCRC;
goto warn_exit;
}
return 0;
warn_exit:
brelse(*bh);
*bh = NULL;
ext4_warning(sb, "Error %d while reading MMP block %llu",
ret, mmp_block);
return ret;
}
/*
* Dump as much information as possible to help the admin.
*/
void __dump_mmp_msg(struct super_block *sb, struct mmp_struct *mmp,
const char *function, unsigned int line, const char *msg)
{
__ext4_warning(sb, function, line, "%s", msg);
__ext4_warning(sb, function, line,
"MMP failure info: last update time: %llu, last update node: %.*s, last update device: %.*s",
(unsigned long long)le64_to_cpu(mmp->mmp_time),
(int)sizeof(mmp->mmp_nodename), mmp->mmp_nodename,
(int)sizeof(mmp->mmp_bdevname), mmp->mmp_bdevname);
}
/*
* kmmpd will update the MMP sequence every s_mmp_update_interval seconds
*/
static int kmmpd(void *data)
{
struct super_block *sb = data;
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
struct buffer_head *bh = EXT4_SB(sb)->s_mmp_bh;
struct mmp_struct *mmp;
ext4_fsblk_t mmp_block;
u32 seq = 0;
unsigned long failed_writes = 0;
int mmp_update_interval = le16_to_cpu(es->s_mmp_update_interval);
unsigned mmp_check_interval;
unsigned long last_update_time;
unsigned long diff;
int retval = 0;
mmp_block = le64_to_cpu(es->s_mmp_block);
mmp = (struct mmp_struct *)(bh->b_data);
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds());
/*
* Start with the higher mmp_check_interval and reduce it if
* the MMP block is being updated on time.
*/
mmp_check_interval = max(EXT4_MMP_CHECK_MULT * mmp_update_interval,
EXT4_MMP_MIN_CHECK_INTERVAL);
mmp->mmp_check_interval = cpu_to_le16(mmp_check_interval);
memcpy(mmp->mmp_nodename, init_utsname()->nodename,
sizeof(mmp->mmp_nodename));
while (!kthread_should_stop() && !ext4_emergency_state(sb)) {
if (!ext4_has_feature_mmp(sb)) {
ext4_warning(sb, "kmmpd being stopped since MMP feature"
" has been disabled.");
goto wait_to_exit;
}
if (++seq > EXT4_MMP_SEQ_MAX)
seq = 1;
mmp->mmp_seq = cpu_to_le32(seq);
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds());
last_update_time = jiffies;
retval = write_mmp_block(sb, bh);
/*
* Don't spew too many error messages. Print one every
* (s_mmp_update_interval * 60) seconds.
*/
if (retval) {
if ((failed_writes % 60) == 0) {
ext4_error_err(sb, -retval,
"Error writing to MMP block");
}
failed_writes++;
}
diff = jiffies - last_update_time;
if (diff < mmp_update_interval * HZ)
schedule_timeout_interruptible(mmp_update_interval *
HZ - diff);
/*
* We need to make sure that more than mmp_check_interval
* seconds have not passed since writing. If that has happened
* we need to check if the MMP block is as we left it.
*/
diff = jiffies - last_update_time;
if (diff > mmp_check_interval * HZ) {
struct buffer_head *bh_check = NULL;
struct mmp_struct *mmp_check;
retval = read_mmp_block(sb, &bh_check, mmp_block);
if (retval) {
ext4_error_err(sb, -retval,
"error reading MMP data: %d",
retval);
goto wait_to_exit;
}
mmp_check = (struct mmp_struct *)(bh_check->b_data);
if (mmp->mmp_seq != mmp_check->mmp_seq ||
memcmp(mmp->mmp_nodename, mmp_check->mmp_nodename,
sizeof(mmp->mmp_nodename))) {
dump_mmp_msg(sb, mmp_check,
"Error while updating MMP info. "
"The filesystem seems to have been"
" multiply mounted.");
ext4_error_err(sb, EBUSY, "abort");
put_bh(bh_check);
retval = -EBUSY;
goto wait_to_exit;
}
put_bh(bh_check);
}
/*
* Adjust the mmp_check_interval depending on how much time
* it took for the MMP block to be written.
*/
mmp_check_interval = clamp(EXT4_MMP_CHECK_MULT * diff / HZ,
EXT4_MMP_MIN_CHECK_INTERVAL,
EXT4_MMP_MAX_CHECK_INTERVAL);
mmp->mmp_check_interval = cpu_to_le16(mmp_check_interval);
}
/*
* Unmount seems to be clean.
*/
mmp->mmp_seq = cpu_to_le32(EXT4_MMP_SEQ_CLEAN);
mmp->mmp_time = cpu_to_le64(ktime_get_real_seconds());
retval = write_mmp_block(sb, bh);
wait_to_exit:
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
if (!kthread_should_stop())
schedule();
}
set_current_state(TASK_RUNNING);
return retval;
}
void ext4_stop_mmpd(struct ext4_sb_info *sbi)
{
if (sbi->s_mmp_tsk) {
kthread_stop(sbi->s_mmp_tsk);
brelse(sbi->s_mmp_bh);
sbi->s_mmp_tsk = NULL;
}
}
/*
* Get a random new sequence number but make sure it is not greater than
* EXT4_MMP_SEQ_MAX.
*/
static unsigned int mmp_new_seq(void)
{
return get_random_u32_below(EXT4_MMP_SEQ_MAX + 1);
}
/*
* Protect the filesystem from being mounted more than once.
*/
int ext4_multi_mount_protect(struct super_block *sb,
ext4_fsblk_t mmp_block)
{
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
struct buffer_head *bh = NULL;
struct mmp_struct *mmp = NULL;
u32 seq;
unsigned int mmp_check_interval = le16_to_cpu(es->s_mmp_update_interval);
unsigned int wait_time = 0;
int retval;
if (mmp_block < le32_to_cpu(es->s_first_data_block) ||
mmp_block >= ext4_blocks_count(es)) {
ext4_warning(sb, "Invalid MMP block in superblock");
retval = -EINVAL;
goto failed;
}
retval = read_mmp_block(sb, &bh, mmp_block);
if (retval)
goto failed;
mmp = (struct mmp_struct *)(bh->b_data);
if (mmp_check_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
mmp_check_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
/*
* If check_interval in MMP block is larger, use that instead of
* update_interval from the superblock.
*/
if (le16_to_cpu(mmp->mmp_check_interval) > mmp_check_interval)
mmp_check_interval = le16_to_cpu(mmp->mmp_check_interval);
seq = le32_to_cpu(mmp->mmp_seq);
if (seq == EXT4_MMP_SEQ_CLEAN)
goto skip;
if (seq == EXT4_MMP_SEQ_FSCK) {
dump_mmp_msg(sb, mmp, "fsck is running on the filesystem");
retval = -EBUSY;
goto failed;
}
wait_time = min(mmp_check_interval * 2 + 1,
mmp_check_interval + 60);
/* Print MMP interval if more than 20 secs. */
if (wait_time > EXT4_MMP_MIN_CHECK_INTERVAL * 4)
ext4_warning(sb, "MMP interval %u higher than expected, please"
" wait.\n", wait_time * 2);
if (schedule_timeout_interruptible(HZ * wait_time) != 0) {
ext4_warning(sb, "MMP startup interrupted, failing mount\n");
retval = -ETIMEDOUT;
goto failed;
}
retval = read_mmp_block(sb, &bh, mmp_block);
if (retval)
goto failed;
mmp = (struct mmp_struct *)(bh->b_data);
if (seq != le32_to_cpu(mmp->mmp_seq)) {
dump_mmp_msg(sb, mmp,
"Device is already active on another node.");
retval = -EBUSY;
goto failed;
}
skip:
/*
* write a new random sequence number.
*/
seq = mmp_new_seq();
mmp->mmp_seq = cpu_to_le32(seq);
/*
* On mount / remount we are protected against fs freezing (by s_umount
* semaphore) and grabbing freeze protection upsets lockdep
*/
retval = write_mmp_block_thawed(sb, bh);
if (retval)
goto failed;
/*
* wait for MMP interval and check mmp_seq.
*/
if (schedule_timeout_interruptible(HZ * wait_time) != 0) {
ext4_warning(sb, "MMP startup interrupted, failing mount");
retval = -ETIMEDOUT;
goto failed;
}
retval = read_mmp_block(sb, &bh, mmp_block);
if (retval)
goto failed;
mmp = (struct mmp_struct *)(bh->b_data);
if (seq != le32_to_cpu(mmp->mmp_seq)) {
dump_mmp_msg(sb, mmp,
"Device is already active on another node.");
retval = -EBUSY;
goto failed;
}
EXT4_SB(sb)->s_mmp_bh = bh;
BUILD_BUG_ON(sizeof(mmp->mmp_bdevname) < BDEVNAME_SIZE);
snprintf(mmp->mmp_bdevname, sizeof(mmp->mmp_bdevname),
"%pg", bh->b_bdev);
/*
* Start a kernel thread to update the MMP block periodically.
*/
EXT4_SB(sb)->s_mmp_tsk = kthread_run(kmmpd, sb, "kmmpd-%.*s",
(int)sizeof(mmp->mmp_bdevname),
mmp->mmp_bdevname);
if (IS_ERR(EXT4_SB(sb)->s_mmp_tsk)) {
EXT4_SB(sb)->s_mmp_tsk = NULL;
ext4_warning(sb, "Unable to create kmmpd thread for %s.",
sb->s_id);
retval = -ENOMEM;
goto failed;
}
return 0;
failed:
brelse(bh);
return retval;
}
+658
View File
@@ -0,0 +1,658 @@
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (c) 2008,2009 NEC Software Tohoku, Ltd.
* Written by Takashi Sato <t-sato@yk.jp.nec.com>
* Akira Fujita <a-fujita@rs.jp.nec.com>
*/
#include <linux/fs.h>
#include <linux/quotaops.h>
#include <linux/slab.h>
#include <linux/sched/mm.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "ext4_extents.h"
#include <trace/events/ext4.h>
struct mext_data {
struct inode *orig_inode; /* Origin file inode */
struct inode *donor_inode; /* Donor file inode */
struct ext4_map_blocks orig_map;/* Origin file's move mapping */
ext4_lblk_t donor_lblk; /* Start block of the donor file */
};
/**
* ext4_double_down_write_data_sem() - write lock two inodes's i_data_sem
* @first: inode to be locked
* @second: inode to be locked
*
* Acquire write lock of i_data_sem of the two inodes
*/
void
ext4_double_down_write_data_sem(struct inode *first, struct inode *second)
{
if (first < second) {
down_write(&EXT4_I(first)->i_data_sem);
down_write_nested(&EXT4_I(second)->i_data_sem, I_DATA_SEM_OTHER);
} else {
down_write(&EXT4_I(second)->i_data_sem);
down_write_nested(&EXT4_I(first)->i_data_sem, I_DATA_SEM_OTHER);
}
}
/**
* ext4_double_up_write_data_sem - Release two inodes' write lock of i_data_sem
*
* @orig_inode: original inode structure to be released its lock first
* @donor_inode: donor inode structure to be released its lock second
* Release write lock of i_data_sem of two inodes (orig and donor).
*/
void
ext4_double_up_write_data_sem(struct inode *orig_inode,
struct inode *donor_inode)
{
up_write(&EXT4_I(orig_inode)->i_data_sem);
up_write(&EXT4_I(donor_inode)->i_data_sem);
}
/* Grab and lock folio on both @inode1 and @inode2 by inode order. */
static int mext_folio_double_lock(struct inode *inode1, struct inode *inode2,
pgoff_t index1, pgoff_t index2, size_t len,
struct folio *folio[2])
{
struct address_space *mapping[2];
unsigned int flags;
fgf_t fgp_flags = FGP_WRITEBEGIN;
BUG_ON(!inode1 || !inode2);
if (inode1 < inode2) {
mapping[0] = inode1->i_mapping;
mapping[1] = inode2->i_mapping;
} else {
swap(index1, index2);
mapping[0] = inode2->i_mapping;
mapping[1] = inode1->i_mapping;
}
flags = memalloc_nofs_save();
fgp_flags |= fgf_set_order(len);
folio[0] = __filemap_get_folio(mapping[0], index1, fgp_flags,
mapping_gfp_mask(mapping[0]));
if (IS_ERR(folio[0])) {
memalloc_nofs_restore(flags);
return PTR_ERR(folio[0]);
}
folio[1] = __filemap_get_folio(mapping[1], index2, fgp_flags,
mapping_gfp_mask(mapping[1]));
memalloc_nofs_restore(flags);
if (IS_ERR(folio[1])) {
folio_unlock(folio[0]);
folio_put(folio[0]);
return PTR_ERR(folio[1]);
}
/*
* __filemap_get_folio() may not wait on folio's writeback if
* BDI not demand that. But it is reasonable to be very conservative
* here and explicitly wait on folio's writeback
*/
folio_wait_writeback(folio[0]);
folio_wait_writeback(folio[1]);
if (inode1 > inode2)
swap(folio[0], folio[1]);
return 0;
}
static void mext_folio_double_unlock(struct folio *folio[2])
{
folio_unlock(folio[0]);
folio_put(folio[0]);
folio_unlock(folio[1]);
folio_put(folio[1]);
}
/* Force folio buffers uptodate w/o dropping folio's lock */
static int mext_folio_mkuptodate(struct folio *folio, size_t from, size_t to)
{
struct inode *inode = folio->mapping->host;
sector_t block;
struct buffer_head *bh, *head;
unsigned int blocksize, block_start, block_end;
int nr = 0;
bool partial = false;
BUG_ON(!folio_test_locked(folio));
BUG_ON(folio_test_writeback(folio));
if (folio_test_uptodate(folio))
return 0;
blocksize = i_blocksize(inode);
head = folio_buffers(folio);
if (!head)
head = create_empty_buffers(folio, blocksize, 0);
block = folio_pos(folio) >> inode->i_blkbits;
block_end = 0;
bh = head;
do {
block_start = block_end;
block_end = block_start + blocksize;
if (block_end <= from || block_start >= to) {
if (!buffer_uptodate(bh))
partial = true;
continue;
}
if (buffer_uptodate(bh))
continue;
if (!buffer_mapped(bh)) {
int err = ext4_get_block(inode, block, bh, 0);
if (err)
return err;
if (!buffer_mapped(bh)) {
folio_zero_range(folio, block_start, blocksize);
set_buffer_uptodate(bh);
continue;
}
}
lock_buffer(bh);
if (buffer_uptodate(bh)) {
unlock_buffer(bh);
continue;
}
ext4_read_bh_nowait(bh, 0, NULL, false);
nr++;
} while (block++, (bh = bh->b_this_page) != head);
/* No io required */
if (!nr)
goto out;
bh = head;
do {
if (bh_offset(bh) + blocksize <= from)
continue;
if (bh_offset(bh) >= to)
break;
wait_on_buffer(bh);
if (buffer_uptodate(bh))
continue;
return -EIO;
} while ((bh = bh->b_this_page) != head);
out:
if (!partial)
folio_mark_uptodate(folio);
return 0;
}
enum mext_move_type {MEXT_SKIP_EXTENT, MEXT_MOVE_EXTENT, MEXT_COPY_DATA};
/*
* Start to move extent between the origin inode and the donor inode,
* hold one folio for each inode and check the candidate moving extent
* mapping status again.
*/
static int mext_move_begin(struct mext_data *mext, struct folio *folio[2],
enum mext_move_type *move_type)
{
struct inode *orig_inode = mext->orig_inode;
struct inode *donor_inode = mext->donor_inode;
unsigned int blkbits = orig_inode->i_blkbits;
struct ext4_map_blocks donor_map = {0};
loff_t orig_pos, donor_pos;
size_t move_len;
int ret;
orig_pos = ((loff_t)mext->orig_map.m_lblk) << blkbits;
donor_pos = ((loff_t)mext->donor_lblk) << blkbits;
ret = mext_folio_double_lock(orig_inode, donor_inode,
orig_pos >> PAGE_SHIFT, donor_pos >> PAGE_SHIFT,
((size_t)mext->orig_map.m_len) << blkbits, folio);
if (ret)
return ret;
/*
* Check the origin inode's mapping information again under the
* folio lock, as we do not hold the i_data_sem at all times, and
* it may change during the concurrent write-back operation.
*/
if (mext->orig_map.m_seq != READ_ONCE(EXT4_I(orig_inode)->i_es_seq)) {
ret = -ESTALE;
goto error;
}
/* Adjust the moving length according to the length of shorter folio. */
move_len = umin(folio_next_pos(folio[0]) - orig_pos,
folio_next_pos(folio[1]) - donor_pos);
move_len >>= blkbits;
if (move_len < mext->orig_map.m_len)
mext->orig_map.m_len = move_len;
donor_map.m_lblk = mext->donor_lblk;
donor_map.m_len = mext->orig_map.m_len;
donor_map.m_flags = 0;
ret = ext4_map_blocks(NULL, donor_inode, &donor_map, 0);
if (ret < 0)
goto error;
/* Adjust the moving length according to the donor mapping length. */
mext->orig_map.m_len = donor_map.m_len;
/* Skip moving if the donor range is a hole or a delalloc extent. */
if (!(donor_map.m_flags & (EXT4_MAP_MAPPED | EXT4_MAP_UNWRITTEN)))
*move_type = MEXT_SKIP_EXTENT;
/* If both mapping ranges are unwritten, no need to copy data. */
else if ((mext->orig_map.m_flags & EXT4_MAP_UNWRITTEN) &&
(donor_map.m_flags & EXT4_MAP_UNWRITTEN))
*move_type = MEXT_MOVE_EXTENT;
else
*move_type = MEXT_COPY_DATA;
return 0;
error:
mext_folio_double_unlock(folio);
return ret;
}
/*
* Re-create the new moved mapping buffers of the original inode and commit
* the entire written range.
*/
static int mext_folio_mkwrite(struct inode *inode, struct folio *folio,
size_t from, size_t to)
{
unsigned int blocksize = i_blocksize(inode);
struct buffer_head *bh, *head;
size_t block_start, block_end;
sector_t block;
int ret;
head = folio_buffers(folio);
if (!head)
head = create_empty_buffers(folio, blocksize, 0);
block = folio_pos(folio) >> inode->i_blkbits;
block_end = 0;
bh = head;
do {
block_start = block_end;
block_end = block_start + blocksize;
if (block_end <= from || block_start >= to)
continue;
ret = ext4_get_block(inode, block, bh, 0);
if (ret)
return ret;
} while (block++, (bh = bh->b_this_page) != head);
block_commit_write(folio, from, to);
return 0;
}
/*
* Save the data in original inode extent blocks and replace one folio size
* aligned original inode extent with one or one partial donor inode extent,
* and then write out the saved data in new original inode blocks. Pass out
* the replaced block count through m_len. Return 0 on success, and an error
* code otherwise.
*/
static int mext_move_extent(struct mext_data *mext, u64 *m_len)
{
struct inode *orig_inode = mext->orig_inode;
struct inode *donor_inode = mext->donor_inode;
struct ext4_map_blocks *orig_map = &mext->orig_map;
unsigned int blkbits = orig_inode->i_blkbits;
struct folio *folio[2] = {NULL, NULL};
loff_t from, length;
enum mext_move_type move_type = 0;
handle_t *handle;
u64 r_len = 0;
unsigned int credits;
int ret, ret2;
*m_len = 0;
trace_ext4_move_extent_enter(orig_inode, orig_map, donor_inode,
mext->donor_lblk);
credits = ext4_chunk_trans_extent(orig_inode, 0) * 2;
handle = ext4_journal_start(orig_inode, EXT4_HT_MOVE_EXTENTS, credits);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto out;
}
ext4_fc_mark_ineligible(orig_inode->i_sb, EXT4_FC_REASON_MOVE_EXT,
handle);
ret = mext_move_begin(mext, folio, &move_type);
if (ret)
goto stop_handle;
if (move_type == MEXT_SKIP_EXTENT)
goto unlock;
/*
* Copy the data. First, read the original inode data into the page
* cache. Then, release the existing mapping relationships and swap
* the extent. Finally, re-establish the new mapping relationships
* and dirty the page cache.
*/
if (move_type == MEXT_COPY_DATA) {
from = offset_in_folio(folio[0],
((loff_t)orig_map->m_lblk) << blkbits);
length = ((loff_t)orig_map->m_len) << blkbits;
ret = mext_folio_mkuptodate(folio[0], from, from + length);
if (ret)
goto unlock;
}
if (!filemap_release_folio(folio[0], 0) ||
!filemap_release_folio(folio[1], 0)) {
ret = -EBUSY;
goto unlock;
}
/* Move extent */
ext4_double_down_write_data_sem(orig_inode, donor_inode);
*m_len = ext4_swap_extents(handle, orig_inode, donor_inode,
orig_map->m_lblk, mext->donor_lblk,
orig_map->m_len, 1, &ret);
ext4_double_up_write_data_sem(orig_inode, donor_inode);
/* A short-length swap cannot occur after a successful swap extent. */
if (WARN_ON_ONCE(!ret && (*m_len != orig_map->m_len)))
ret = -EIO;
if (!(*m_len) || (move_type == MEXT_MOVE_EXTENT))
goto unlock;
/* Copy data */
length = (*m_len) << blkbits;
ret2 = mext_folio_mkwrite(orig_inode, folio[0], from, from + length);
if (ret2) {
if (!ret)
ret = ret2;
goto repair_branches;
}
/*
* Even in case of data=writeback it is reasonable to pin
* inode to transaction, to prevent unexpected data loss.
*/
ret2 = ext4_jbd2_inode_add_write(handle, orig_inode,
((loff_t)orig_map->m_lblk) << blkbits, length);
if (!ret)
ret = ret2;
unlock:
mext_folio_double_unlock(folio);
stop_handle:
ext4_journal_stop(handle);
out:
trace_ext4_move_extent_exit(orig_inode, orig_map->m_lblk, donor_inode,
mext->donor_lblk, orig_map->m_len, *m_len,
move_type, ret);
return ret;
repair_branches:
ret2 = 0;
ext4_double_down_write_data_sem(orig_inode, donor_inode);
r_len = ext4_swap_extents(handle, donor_inode, orig_inode,
mext->donor_lblk, orig_map->m_lblk,
*m_len, 0, &ret2);
ext4_double_up_write_data_sem(orig_inode, donor_inode);
if (ret2 || r_len != *m_len) {
ext4_error_inode_block(orig_inode, (sector_t)(orig_map->m_lblk),
EIO, "Unable to copy data block, data will be lost!");
ret = -EIO;
}
*m_len = 0;
goto unlock;
}
/*
* Check the validity of the basic filesystem environment and the
* inodes' support status.
*/
static int mext_check_validity(struct inode *orig_inode,
struct inode *donor_inode)
{
struct super_block *sb = orig_inode->i_sb;
/* origin and donor should be different inodes */
if (orig_inode == donor_inode) {
ext4_debug("ext4 move extent: The argument files should not be same inode [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
/* origin and donor should belone to the same filesystem */
if (orig_inode->i_sb != donor_inode->i_sb) {
ext4_debug("ext4 move extent: The argument files should be in same FS [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
/* Regular file check */
if (!S_ISREG(orig_inode->i_mode) || !S_ISREG(donor_inode->i_mode)) {
ext4_debug("ext4 move extent: The argument files should be regular file [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
if (ext4_has_feature_bigalloc(sb)) {
ext4_msg(sb, KERN_ERR,
"Online defrag not supported with bigalloc");
return -EOPNOTSUPP;
}
if (IS_DAX(orig_inode)) {
ext4_msg(sb, KERN_ERR,
"Online defrag not supported with DAX");
return -EOPNOTSUPP;
}
/*
* TODO: it's not obvious how to swap blocks for inodes with full
* journaling enabled.
*/
if (ext4_should_journal_data(orig_inode) ||
ext4_should_journal_data(donor_inode)) {
ext4_msg(sb, KERN_ERR,
"Online defrag not supported with data journaling");
return -EOPNOTSUPP;
}
if (IS_ENCRYPTED(orig_inode) || IS_ENCRYPTED(donor_inode)) {
ext4_msg(sb, KERN_ERR,
"Online defrag not supported for encrypted files");
return -EOPNOTSUPP;
}
/* Ext4 move extent supports only extent based file */
if (!(ext4_test_inode_flag(orig_inode, EXT4_INODE_EXTENTS)) ||
!(ext4_test_inode_flag(donor_inode, EXT4_INODE_EXTENTS))) {
ext4_msg(sb, KERN_ERR,
"Online defrag not supported for non-extent files");
return -EOPNOTSUPP;
}
if (donor_inode->i_mode & (S_ISUID|S_ISGID)) {
ext4_debug("ext4 move extent: suid or sgid is set to donor file [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
if (IS_IMMUTABLE(donor_inode) || IS_APPEND(donor_inode)) {
ext4_debug("ext4 move extent: donor should not be immutable or append file [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EPERM;
}
/* Ext4 move extent does not support swap files */
if (IS_SWAPFILE(orig_inode) || IS_SWAPFILE(donor_inode)) {
ext4_debug("ext4 move extent: The argument files should not be swap files [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -ETXTBSY;
}
if (ext4_is_quota_file(orig_inode) || ext4_is_quota_file(donor_inode)) {
ext4_debug("ext4 move extent: The argument files should not be quota files [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EOPNOTSUPP;
}
if ((!orig_inode->i_size) || (!donor_inode->i_size)) {
ext4_debug("ext4 move extent: File size is 0 byte\n");
return -EINVAL;
}
return 0;
}
/*
* Check the moving range of ext4_move_extents() whether the files can be
* exchanged with each other, and adjust the length to fit within the file
* size. Return 0 on success, or a negative error value on failure.
*/
static int mext_check_adjust_range(struct inode *orig_inode,
struct inode *donor_inode, __u64 orig_start,
__u64 donor_start, __u64 *len)
{
__u64 orig_eof, donor_eof;
/* Start offset should be same */
if ((orig_start & ~(PAGE_MASK >> orig_inode->i_blkbits)) !=
(donor_start & ~(PAGE_MASK >> orig_inode->i_blkbits))) {
ext4_debug("ext4 move extent: orig and donor's start offsets are not aligned [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
if ((orig_start >= EXT_MAX_BLOCKS) ||
(donor_start >= EXT_MAX_BLOCKS) ||
(*len > EXT_MAX_BLOCKS) ||
(donor_start + *len >= EXT_MAX_BLOCKS) ||
(orig_start + *len >= EXT_MAX_BLOCKS)) {
ext4_debug("ext4 move extent: Can't handle over [%u] blocks [ino:orig %llu, donor %llu]\n",
EXT_MAX_BLOCKS,
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
orig_eof = EXT4_B_TO_LBLK(orig_inode, i_size_read(orig_inode));
donor_eof = EXT4_B_TO_LBLK(donor_inode, i_size_read(donor_inode));
if (orig_eof <= orig_start)
*len = 0;
else if (orig_eof < orig_start + *len - 1)
*len = orig_eof - orig_start;
if (donor_eof <= donor_start)
*len = 0;
else if (donor_eof < donor_start + *len - 1)
*len = donor_eof - donor_start;
if (!*len) {
ext4_debug("ext4 move extent: len should not be 0 [ino:orig %llu, donor %llu]\n",
orig_inode->i_ino, donor_inode->i_ino);
return -EINVAL;
}
return 0;
}
/**
* ext4_move_extents - Exchange the specified range of a file
*
* @o_filp: file structure of the original file
* @d_filp: file structure of the donor file
* @orig_blk: start offset in block for orig
* @donor_blk: start offset in block for donor
* @len: the number of blocks to be moved
* @moved_len: moved block length
*
* This function returns 0 and moved block length is set in moved_len
* if succeed, otherwise returns error value.
*/
int ext4_move_extents(struct file *o_filp, struct file *d_filp, __u64 orig_blk,
__u64 donor_blk, __u64 len, __u64 *moved_len)
{
struct inode *orig_inode = file_inode(o_filp);
struct inode *donor_inode = file_inode(d_filp);
struct mext_data mext;
struct super_block *sb = orig_inode->i_sb;
struct ext4_sb_info *sbi = EXT4_SB(sb);
int retries = 0;
u64 m_len;
int ret;
*moved_len = 0;
/* Protect orig and donor inodes against a truncate */
lock_two_nondirectories(orig_inode, donor_inode);
ret = mext_check_validity(orig_inode, donor_inode);
if (ret)
goto out;
/* Wait for all existing dio workers */
inode_dio_wait(orig_inode);
inode_dio_wait(donor_inode);
/* Check and adjust the specified move_extent range. */
ret = mext_check_adjust_range(orig_inode, donor_inode, orig_blk,
donor_blk, &len);
if (ret)
goto out;
mext.orig_inode = orig_inode;
mext.donor_inode = donor_inode;
while (len) {
mext.orig_map.m_lblk = orig_blk;
mext.orig_map.m_len = len;
mext.orig_map.m_flags = 0;
mext.donor_lblk = donor_blk;
ret = ext4_map_blocks(NULL, orig_inode, &mext.orig_map, 0);
if (ret < 0)
goto out;
/* Skip moving if it is a hole or a delalloc extent. */
if (mext.orig_map.m_flags &
(EXT4_MAP_MAPPED | EXT4_MAP_UNWRITTEN)) {
ret = mext_move_extent(&mext, &m_len);
*moved_len += m_len;
if (!ret)
goto next;
/* Move failed or partially failed. */
if (m_len) {
orig_blk += m_len;
donor_blk += m_len;
len -= m_len;
}
if (ret == -ESTALE)
continue;
if (ret == -ENOSPC &&
ext4_should_retry_alloc(sb, &retries))
continue;
if (ret == -EBUSY &&
sbi->s_journal && retries++ < 4 &&
jbd2_journal_force_commit_nested(sbi->s_journal))
continue;
goto out;
}
next:
orig_blk += mext.orig_map.m_len;
donor_blk += mext.orig_map.m_len;
len -= mext.orig_map.m_len;
retries = 0;
}
out:
if (*moved_len) {
ext4_discard_preallocations(orig_inode);
ext4_discard_preallocations(donor_inode);
}
unlock_two_nondirectories(orig_inode, donor_inode);
return ret;
}
File diff suppressed because it is too large Load Diff
+659
View File
@@ -0,0 +1,659 @@
/*
* Ext4 orphan inode handling
*/
#include <linux/fs.h>
#include <linux/quotaops.h>
#include <linux/buffer_head.h>
#include "ext4.h"
#include "ext4_jbd2.h"
#define EXT4_MAX_ORPHAN_FILE_BLOCKS 512
static int ext4_orphan_file_add(handle_t *handle, struct inode *inode)
{
int i, j, start;
struct ext4_orphan_info *oi = &EXT4_SB(inode->i_sb)->s_orphan_info;
int ret = 0;
bool found = false;
__le32 *bdata;
int inodes_per_ob = ext4_inodes_per_orphan_block(inode->i_sb);
int looped = 0;
/*
* Find block with free orphan entry. Use CPU number for a naive hash
* for a search start in the orphan file
*/
start = raw_smp_processor_id()*13 % oi->of_blocks;
i = start;
do {
if (atomic_dec_if_positive(&oi->of_binfo[i].ob_free_entries)
>= 0) {
found = true;
break;
}
if (++i >= oi->of_blocks)
i = 0;
} while (i != start);
if (!found) {
/*
* For now we don't grow or shrink orphan file. We just use
* whatever was allocated at mke2fs time. The additional
* credits we would have to reserve for each orphan inode
* operation just don't seem worth it.
*/
return -ENOSPC;
}
ret = ext4_journal_get_write_access(handle, inode->i_sb,
oi->of_binfo[i].ob_bh, EXT4_JTR_ORPHAN_FILE);
if (ret) {
atomic_inc(&oi->of_binfo[i].ob_free_entries);
return ret;
}
bdata = (__le32 *)(oi->of_binfo[i].ob_bh->b_data);
/* Find empty slot in a block */
j = 0;
do {
if (looped) {
/*
* Did we walk through the block several times without
* finding free entry? It is theoretically possible
* if entries get constantly allocated and freed or
* if the block is corrupted. Avoid indefinite looping
* and bail. We'll use orphan list instead.
*/
if (looped > 3) {
atomic_inc(&oi->of_binfo[i].ob_free_entries);
return -ENOSPC;
}
cond_resched();
}
while (bdata[j]) {
if (++j >= inodes_per_ob) {
j = 0;
looped++;
}
}
} while (cmpxchg(&bdata[j], (__le32)0, cpu_to_le32(inode->i_ino)) !=
(__le32)0);
EXT4_I(inode)->i_orphan_idx = i * inodes_per_ob + j;
ext4_set_inode_state(inode, EXT4_STATE_ORPHAN_FILE);
return ext4_handle_dirty_metadata(handle, NULL, oi->of_binfo[i].ob_bh);
}
/*
* ext4_orphan_add() links an unlinked or truncated inode into a list of
* such inodes, starting at the superblock, in case we crash before the
* file is closed/deleted, or in case the inode truncate spans multiple
* transactions and the last transaction is not recovered after a crash.
*
* At filesystem recovery time, we walk this list deleting unlinked
* inodes and truncating linked inodes in ext4_orphan_cleanup().
*
* Orphan list manipulation functions must be called under i_rwsem unless
* we are just creating the inode or deleting it.
*/
int ext4_orphan_add(handle_t *handle, struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_iloc iloc;
int err = 0, rc;
bool dirty = false;
if (!sbi->s_journal || is_bad_inode(inode))
return 0;
WARN_ON_ONCE(!(inode_state_read_once(inode) & (I_NEW | I_FREEING)) &&
!inode_is_locked(inode));
if (ext4_inode_orphan_tracked(inode))
return 0;
/*
* Orphan handling is only valid for files with data blocks
* being truncated, or files being unlinked. Note that we either
* hold i_rwsem, or the inode can not be referenced from outside,
* so i_nlink should not be bumped due to race
*/
ASSERT((S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
S_ISLNK(inode->i_mode)) || inode->i_nlink == 0);
if (sbi->s_orphan_info.of_blocks) {
err = ext4_orphan_file_add(handle, inode);
/*
* Fallback to normal orphan list of orphan file is
* out of space
*/
if (err != -ENOSPC)
return err;
}
BUFFER_TRACE(sbi->s_sbh, "get_write_access");
err = ext4_journal_get_write_access(handle, sb, sbi->s_sbh,
EXT4_JTR_NONE);
if (err)
goto out;
err = ext4_reserve_inode_write(handle, inode, &iloc);
if (err)
goto out;
mutex_lock(&sbi->s_orphan_lock);
/*
* Due to previous errors inode may be already a part of on-disk
* orphan list. If so skip on-disk list modification.
*/
if (!NEXT_ORPHAN(inode) || NEXT_ORPHAN(inode) >
(le32_to_cpu(sbi->s_es->s_inodes_count))) {
/* Insert this inode at the head of the on-disk orphan list */
NEXT_ORPHAN(inode) = le32_to_cpu(sbi->s_es->s_last_orphan);
lock_buffer(sbi->s_sbh);
sbi->s_es->s_last_orphan = cpu_to_le32(inode->i_ino);
ext4_superblock_csum_set(sb);
unlock_buffer(sbi->s_sbh);
dirty = true;
}
list_add(&EXT4_I(inode)->i_orphan, &sbi->s_orphan);
mutex_unlock(&sbi->s_orphan_lock);
if (dirty) {
err = ext4_handle_dirty_metadata(handle, NULL, sbi->s_sbh);
rc = ext4_mark_iloc_dirty(handle, inode, &iloc);
if (!err)
err = rc;
if (err) {
/*
* We have to remove inode from in-memory list if
* addition to on disk orphan list failed. Stray orphan
* list entries can cause panics at unmount time.
*/
mutex_lock(&sbi->s_orphan_lock);
list_del_init(&EXT4_I(inode)->i_orphan);
mutex_unlock(&sbi->s_orphan_lock);
}
} else
brelse(iloc.bh);
ext4_debug("superblock will point to %llu\n", inode->i_ino);
ext4_debug("orphan inode %llu will point to %d\n",
inode->i_ino, NEXT_ORPHAN(inode));
out:
ext4_std_error(sb, err);
return err;
}
static int ext4_orphan_file_del(handle_t *handle, struct inode *inode)
{
struct ext4_orphan_info *oi = &EXT4_SB(inode->i_sb)->s_orphan_info;
__le32 *bdata;
int blk, off;
int inodes_per_ob = ext4_inodes_per_orphan_block(inode->i_sb);
int ret = 0;
if (!handle)
goto out;
blk = EXT4_I(inode)->i_orphan_idx / inodes_per_ob;
off = EXT4_I(inode)->i_orphan_idx % inodes_per_ob;
if (WARN_ON_ONCE(blk >= oi->of_blocks))
goto out;
ret = ext4_journal_get_write_access(handle, inode->i_sb,
oi->of_binfo[blk].ob_bh, EXT4_JTR_ORPHAN_FILE);
if (ret)
goto out;
bdata = (__le32 *)(oi->of_binfo[blk].ob_bh->b_data);
bdata[off] = 0;
atomic_inc(&oi->of_binfo[blk].ob_free_entries);
ret = ext4_handle_dirty_metadata(handle, NULL, oi->of_binfo[blk].ob_bh);
out:
ext4_clear_inode_state(inode, EXT4_STATE_ORPHAN_FILE);
INIT_LIST_HEAD(&EXT4_I(inode)->i_orphan);
return ret;
}
/*
* ext4_orphan_del() removes an unlinked or truncated inode from the list
* of such inodes stored on disk, because it is finally being cleaned up.
*/
int ext4_orphan_del(handle_t *handle, struct inode *inode)
{
struct list_head *prev;
struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
__u32 ino_next;
struct ext4_iloc iloc;
int err = 0;
if (!sbi->s_journal && !(sbi->s_mount_state & EXT4_ORPHAN_FS))
return 0;
WARN_ON_ONCE(!(inode_state_read_once(inode) & (I_NEW | I_FREEING)) &&
!inode_is_locked(inode));
if (ext4_test_inode_state(inode, EXT4_STATE_ORPHAN_FILE))
return ext4_orphan_file_del(handle, inode);
/* Do this quick check before taking global s_orphan_lock. */
if (list_empty(&ei->i_orphan))
return 0;
if (handle) {
/* Grab inode buffer early before taking global s_orphan_lock */
err = ext4_reserve_inode_write(handle, inode, &iloc);
}
mutex_lock(&sbi->s_orphan_lock);
ext4_debug("remove inode %llu from orphan list\n", inode->i_ino);
prev = ei->i_orphan.prev;
list_del_init(&ei->i_orphan);
/* If we're on an error path, we may not have a valid
* transaction handle with which to update the orphan list on
* disk, but we still need to remove the inode from the linked
* list in memory. */
if (!handle || err) {
mutex_unlock(&sbi->s_orphan_lock);
goto out_err;
}
ino_next = NEXT_ORPHAN(inode);
if (prev == &sbi->s_orphan) {
ext4_debug("superblock will point to %u\n", ino_next);
BUFFER_TRACE(sbi->s_sbh, "get_write_access");
err = ext4_journal_get_write_access(handle, inode->i_sb,
sbi->s_sbh, EXT4_JTR_NONE);
if (err) {
mutex_unlock(&sbi->s_orphan_lock);
goto out_brelse;
}
lock_buffer(sbi->s_sbh);
sbi->s_es->s_last_orphan = cpu_to_le32(ino_next);
ext4_superblock_csum_set(inode->i_sb);
unlock_buffer(sbi->s_sbh);
mutex_unlock(&sbi->s_orphan_lock);
err = ext4_handle_dirty_metadata(handle, NULL, sbi->s_sbh);
} else {
struct ext4_iloc iloc2;
struct inode *i_prev =
&list_entry(prev, struct ext4_inode_info, i_orphan)->vfs_inode;
ext4_debug("orphan inode %llu will point to %u\n",
i_prev->i_ino, ino_next);
err = ext4_reserve_inode_write(handle, i_prev, &iloc2);
if (err) {
mutex_unlock(&sbi->s_orphan_lock);
goto out_brelse;
}
NEXT_ORPHAN(i_prev) = ino_next;
err = ext4_mark_iloc_dirty(handle, i_prev, &iloc2);
mutex_unlock(&sbi->s_orphan_lock);
}
if (err)
goto out_brelse;
NEXT_ORPHAN(inode) = 0;
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
out_err:
ext4_std_error(inode->i_sb, err);
return err;
out_brelse:
brelse(iloc.bh);
goto out_err;
}
#ifdef CONFIG_QUOTA
static int ext4_quota_on_mount(struct super_block *sb, int type)
{
return dquot_quota_on_mount(sb,
rcu_dereference_protected(EXT4_SB(sb)->s_qf_names[type],
lockdep_is_held(&sb->s_umount)),
EXT4_SB(sb)->s_jquota_fmt, type);
}
#endif
static void ext4_process_orphan(struct inode *inode,
int *nr_truncates, int *nr_orphans)
{
struct super_block *sb = inode->i_sb;
int ret;
dquot_initialize(inode);
if (inode->i_nlink) {
if (test_opt(sb, DEBUG))
ext4_msg(sb, KERN_DEBUG,
"%s: truncating inode %llu to %lld bytes",
__func__, inode->i_ino, inode->i_size);
ext4_debug("truncating inode %llu to %lld bytes\n",
inode->i_ino, inode->i_size);
inode_lock(inode);
truncate_inode_pages(inode->i_mapping, inode->i_size);
ret = ext4_truncate(inode);
if (ret) {
/*
* We need to clean up the in-core orphan list
* manually if ext4_truncate() failed to get a
* transaction handle.
*/
ext4_orphan_del(NULL, inode);
ext4_std_error(inode->i_sb, ret);
}
inode_unlock(inode);
(*nr_truncates)++;
} else {
if (test_opt(sb, DEBUG))
ext4_msg(sb, KERN_DEBUG,
"%s: deleting unreferenced inode %llu",
__func__, inode->i_ino);
ext4_debug("deleting unreferenced inode %llu\n",
inode->i_ino);
(*nr_orphans)++;
}
iput(inode); /* The delete magic happens here! */
}
/* ext4_orphan_cleanup() walks a singly-linked list of inodes (starting at
* the superblock) which were deleted from all directories, but held open by
* a process at the time of a crash. We walk the list and try to delete these
* inodes at recovery time (only with a read-write filesystem).
*
* In order to keep the orphan inode chain consistent during traversal (in
* case of crash during recovery), we link each inode into the superblock
* orphan list_head and handle it the same way as an inode deletion during
* normal operation (which journals the operations for us).
*
* We only do an iget() and an iput() on each inode, which is very safe if we
* accidentally point at an in-use or already deleted inode. The worst that
* can happen in this case is that we get a "bit already cleared" message from
* ext4_free_inode(). The only reason we would point at a wrong inode is if
* e2fsck was run on this filesystem, and it must have already done the orphan
* inode cleanup for us, so we can safely abort without any further action.
*/
void ext4_orphan_cleanup(struct super_block *sb, struct ext4_super_block *es)
{
unsigned int s_flags = sb->s_flags;
int nr_orphans = 0, nr_truncates = 0;
struct inode *inode;
int i, j;
#ifdef CONFIG_QUOTA
int quota_update = 0;
#endif
__le32 *bdata;
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
int inodes_per_ob = ext4_inodes_per_orphan_block(sb);
if (!es->s_last_orphan && !oi->of_blocks) {
ext4_debug("no orphan inodes to clean up\n");
return;
}
if (bdev_read_only(sb->s_bdev)) {
ext4_msg(sb, KERN_ERR, "write access "
"unavailable, skipping orphan cleanup");
return;
}
/* Check if feature set would not allow a r/w mount */
if (!ext4_feature_set_ok(sb, 0)) {
ext4_msg(sb, KERN_INFO, "Skipping orphan cleanup due to "
"unknown ROCOMPAT features");
return;
}
if (EXT4_SB(sb)->s_mount_state & EXT4_ERROR_FS) {
/* don't clear list on RO mount w/ errors */
if (es->s_last_orphan && !(s_flags & SB_RDONLY)) {
ext4_msg(sb, KERN_INFO, "Errors on filesystem, "
"clearing orphan list.");
es->s_last_orphan = 0;
}
ext4_debug("Skipping orphan recovery on fs with errors.\n");
return;
}
if (s_flags & SB_RDONLY) {
ext4_msg(sb, KERN_INFO, "orphan cleanup on readonly fs");
sb->s_flags &= ~SB_RDONLY;
}
#ifdef CONFIG_QUOTA
/*
* Turn on quotas which were not enabled for read-only mounts if
* filesystem has quota feature, so that they are updated correctly.
*/
if (ext4_has_feature_quota(sb) && (s_flags & SB_RDONLY)) {
int ret = ext4_enable_quotas(sb);
if (!ret)
quota_update = 1;
else
ext4_msg(sb, KERN_ERR,
"Cannot turn on quotas: error %d", ret);
}
/* Turn on journaled quotas used for old sytle */
for (i = 0; i < EXT4_MAXQUOTAS; i++) {
if (EXT4_SB(sb)->s_qf_names[i]) {
int ret = ext4_quota_on_mount(sb, i);
if (!ret)
quota_update = 1;
else
ext4_msg(sb, KERN_ERR,
"Cannot turn on journaled "
"quota: type %d: error %d", i, ret);
}
}
#endif
while (es->s_last_orphan) {
/*
* We may have encountered an error during cleanup; if
* so, skip the rest.
*/
if (EXT4_SB(sb)->s_mount_state & EXT4_ERROR_FS) {
ext4_debug("Skipping orphan recovery on fs with errors.\n");
es->s_last_orphan = 0;
break;
}
inode = ext4_orphan_get(sb, le32_to_cpu(es->s_last_orphan));
if (IS_ERR(inode)) {
es->s_last_orphan = 0;
break;
}
list_add(&EXT4_I(inode)->i_orphan, &EXT4_SB(sb)->s_orphan);
ext4_process_orphan(inode, &nr_truncates, &nr_orphans);
}
for (i = 0; i < oi->of_blocks; i++) {
bdata = (__le32 *)(oi->of_binfo[i].ob_bh->b_data);
for (j = 0; j < inodes_per_ob; j++) {
if (!bdata[j])
continue;
inode = ext4_orphan_get(sb, le32_to_cpu(bdata[j]));
if (IS_ERR(inode))
continue;
ext4_set_inode_state(inode, EXT4_STATE_ORPHAN_FILE);
EXT4_I(inode)->i_orphan_idx = i * inodes_per_ob + j;
ext4_process_orphan(inode, &nr_truncates, &nr_orphans);
}
}
#define PLURAL(x) (x), ((x) == 1) ? "" : "s"
if (nr_orphans)
ext4_msg(sb, KERN_INFO, "%d orphan inode%s deleted",
PLURAL(nr_orphans));
if (nr_truncates)
ext4_msg(sb, KERN_INFO, "%d truncate%s cleaned up",
PLURAL(nr_truncates));
#ifdef CONFIG_QUOTA
/* Turn off quotas if they were enabled for orphan cleanup */
if (quota_update) {
for (i = 0; i < EXT4_MAXQUOTAS; i++) {
if (sb_dqopt(sb)->files[i])
dquot_quota_off(sb, i);
}
}
#endif
sb->s_flags = s_flags; /* Restore SB_RDONLY status */
}
void ext4_release_orphan_info(struct super_block *sb)
{
int i;
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
if (!oi->of_blocks)
return;
for (i = 0; i < oi->of_blocks; i++)
brelse(oi->of_binfo[i].ob_bh);
kvfree(oi->of_binfo);
}
static struct ext4_orphan_block_tail *ext4_orphan_block_tail(
struct super_block *sb,
struct buffer_head *bh)
{
return (struct ext4_orphan_block_tail *)(bh->b_data + sb->s_blocksize -
sizeof(struct ext4_orphan_block_tail));
}
static int ext4_orphan_file_block_csum_verify(struct super_block *sb,
struct buffer_head *bh)
{
__u32 calculated;
int inodes_per_ob = ext4_inodes_per_orphan_block(sb);
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
struct ext4_orphan_block_tail *ot;
__le64 dsk_block_nr = cpu_to_le64(bh->b_blocknr);
if (!ext4_has_feature_metadata_csum(sb))
return 1;
ot = ext4_orphan_block_tail(sb, bh);
calculated = ext4_chksum(oi->of_csum_seed, (__u8 *)&dsk_block_nr,
sizeof(dsk_block_nr));
calculated = ext4_chksum(calculated, (__u8 *)bh->b_data,
inodes_per_ob * sizeof(__u32));
return le32_to_cpu(ot->ob_checksum) == calculated;
}
/* This gets called only when checksumming is enabled */
void ext4_orphan_file_block_trigger(struct jbd2_buffer_trigger_type *triggers,
struct buffer_head *bh,
void *data, size_t size)
{
struct super_block *sb = EXT4_TRIGGER(triggers)->sb;
__u32 csum;
int inodes_per_ob = ext4_inodes_per_orphan_block(sb);
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
struct ext4_orphan_block_tail *ot;
__le64 dsk_block_nr = cpu_to_le64(bh->b_blocknr);
csum = ext4_chksum(oi->of_csum_seed, (__u8 *)&dsk_block_nr,
sizeof(dsk_block_nr));
csum = ext4_chksum(csum, (__u8 *)data, inodes_per_ob * sizeof(__u32));
ot = ext4_orphan_block_tail(sb, bh);
ot->ob_checksum = cpu_to_le32(csum);
}
int ext4_init_orphan_info(struct super_block *sb)
{
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
struct inode *inode;
int i, j;
int ret;
int free;
__le32 *bdata;
int inodes_per_ob = ext4_inodes_per_orphan_block(sb);
struct ext4_orphan_block_tail *ot;
ino_t orphan_ino = le32_to_cpu(EXT4_SB(sb)->s_es->s_orphan_file_inum);
if (!ext4_has_feature_orphan_file(sb))
return 0;
inode = ext4_iget(sb, orphan_ino, EXT4_IGET_SPECIAL);
if (IS_ERR(inode)) {
ext4_msg(sb, KERN_ERR, "get orphan inode failed");
return PTR_ERR(inode);
}
/*
* This is just an artificial limit to prevent corrupted fs from
* consuming absurd amounts of memory when pinning blocks of orphan
* file in memory.
*/
if (inode->i_size > (EXT4_MAX_ORPHAN_FILE_BLOCKS << inode->i_blkbits)) {
ext4_msg(sb, KERN_ERR, "orphan file too big: %llu",
(unsigned long long)inode->i_size);
ret = -EFSCORRUPTED;
goto out_put;
}
oi->of_blocks = inode->i_size >> sb->s_blocksize_bits;
oi->of_csum_seed = EXT4_I(inode)->i_csum_seed;
oi->of_binfo = kvmalloc_objs(struct ext4_orphan_block, oi->of_blocks);
if (!oi->of_binfo) {
ret = -ENOMEM;
goto out_put;
}
for (i = 0; i < oi->of_blocks; i++) {
oi->of_binfo[i].ob_bh = ext4_bread(NULL, inode, i, 0);
if (IS_ERR(oi->of_binfo[i].ob_bh)) {
ret = PTR_ERR(oi->of_binfo[i].ob_bh);
goto out_free;
}
if (!oi->of_binfo[i].ob_bh) {
ret = -EIO;
goto out_free;
}
ot = ext4_orphan_block_tail(sb, oi->of_binfo[i].ob_bh);
if (le32_to_cpu(ot->ob_magic) != EXT4_ORPHAN_BLOCK_MAGIC) {
ext4_error(sb, "orphan file block %d: bad magic", i);
ret = -EIO;
goto out_free;
}
if (!ext4_orphan_file_block_csum_verify(sb,
oi->of_binfo[i].ob_bh)) {
ext4_error(sb, "orphan file block %d: bad checksum", i);
ret = -EIO;
goto out_free;
}
bdata = (__le32 *)(oi->of_binfo[i].ob_bh->b_data);
free = 0;
for (j = 0; j < inodes_per_ob; j++)
if (bdata[j] == 0)
free++;
atomic_set(&oi->of_binfo[i].ob_free_entries, free);
}
iput(inode);
return 0;
out_free:
for (i--; i >= 0; i--)
brelse(oi->of_binfo[i].ob_bh);
kvfree(oi->of_binfo);
out_put:
iput(inode);
return ret;
}
int ext4_orphan_file_empty(struct super_block *sb)
{
struct ext4_orphan_info *oi = &EXT4_SB(sb)->s_orphan_info;
int i;
int inodes_per_ob = ext4_inodes_per_orphan_block(sb);
if (!ext4_has_feature_orphan_file(sb))
return 1;
for (i = 0; i < oi->of_blocks; i++)
if (atomic_read(&oi->of_binfo[i].ob_free_entries) !=
inodes_per_ob)
return 0;
return 1;
}
+613
View File
@@ -0,0 +1,613 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/page-io.c
*
* This contains the new page_io functions for ext4
*
* Written by Theodore Ts'o, 2010.
*/
#include <linux/blk-crypto.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/highuid.h>
#include <linux/pagemap.h>
#include <linux/quotaops.h>
#include <linux/string.h>
#include <linux/buffer_head.h>
#include <linux/writeback.h>
#include <linux/mpage.h>
#include <linux/namei.h>
#include <linux/uio.h>
#include <linux/bio.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/sched/mm.h>
#include "ext4_jbd2.h"
#include "xattr.h"
#include "acl.h"
static struct kmem_cache *io_end_cachep;
static struct kmem_cache *io_end_vec_cachep;
int __init ext4_init_pageio(void)
{
io_end_cachep = KMEM_CACHE(ext4_io_end, SLAB_RECLAIM_ACCOUNT);
if (io_end_cachep == NULL)
return -ENOMEM;
io_end_vec_cachep = KMEM_CACHE(ext4_io_end_vec, 0);
if (io_end_vec_cachep == NULL) {
kmem_cache_destroy(io_end_cachep);
return -ENOMEM;
}
return 0;
}
void ext4_exit_pageio(void)
{
kmem_cache_destroy(io_end_cachep);
kmem_cache_destroy(io_end_vec_cachep);
}
struct ext4_io_end_vec *ext4_alloc_io_end_vec(ext4_io_end_t *io_end)
{
struct ext4_io_end_vec *io_end_vec;
io_end_vec = kmem_cache_zalloc(io_end_vec_cachep, GFP_NOFS);
if (!io_end_vec)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&io_end_vec->list);
list_add_tail(&io_end_vec->list, &io_end->list_vec);
return io_end_vec;
}
static void ext4_free_io_end_vec(ext4_io_end_t *io_end)
{
struct ext4_io_end_vec *io_end_vec, *tmp;
if (list_empty(&io_end->list_vec))
return;
list_for_each_entry_safe(io_end_vec, tmp, &io_end->list_vec, list) {
list_del(&io_end_vec->list);
kmem_cache_free(io_end_vec_cachep, io_end_vec);
}
}
struct ext4_io_end_vec *ext4_last_io_end_vec(ext4_io_end_t *io_end)
{
BUG_ON(list_empty(&io_end->list_vec));
return list_last_entry(&io_end->list_vec, struct ext4_io_end_vec, list);
}
/*
* Print an buffer I/O error compatible with the fs/buffer.c. This
* provides compatibility with dmesg scrapers that look for a specific
* buffer I/O error message. We really need a unified error reporting
* structure to userspace ala Digital Unix's uerf system, but it's
* probably not going to happen in my lifetime, due to LKML politics...
*/
static void buffer_io_error(struct buffer_head *bh)
{
printk_ratelimited(KERN_ERR "Buffer I/O error on device %pg, logical block %llu\n",
bh->b_bdev,
(unsigned long long)bh->b_blocknr);
}
static void ext4_finish_bio(struct bio *bio)
{
struct folio_iter fi;
bio_for_each_folio_all(fi, bio) {
struct folio *folio = fi.folio;
struct folio *io_folio = NULL;
struct buffer_head *bh, *head;
size_t bio_start = fi.offset;
size_t bio_end = bio_start + fi.length;
unsigned under_io = 0;
unsigned long flags;
if (fscrypt_is_bounce_folio(folio)) {
io_folio = folio;
folio = fscrypt_pagecache_folio(folio);
}
if (bio->bi_status) {
int err = blk_status_to_errno(bio->bi_status);
mapping_set_error(folio->mapping, err);
}
bh = head = folio_buffers(folio);
/*
* We check all buffers in the folio under b_uptodate_lock
* to avoid races with other end io clearing async_write flags
*/
spin_lock_irqsave(&head->b_uptodate_lock, flags);
do {
if (bh_offset(bh) < bio_start ||
bh_offset(bh) + bh->b_size > bio_end) {
if (buffer_async_write(bh))
under_io++;
continue;
}
clear_buffer_async_write(bh);
if (bio->bi_status) {
set_buffer_write_io_error(bh);
buffer_io_error(bh);
}
} while ((bh = bh->b_this_page) != head);
spin_unlock_irqrestore(&head->b_uptodate_lock, flags);
if (!under_io) {
fscrypt_free_bounce_page(&io_folio->page);
folio_end_writeback(folio);
}
}
}
static void ext4_release_io_end(ext4_io_end_t *io_end)
{
struct bio *bio, *next_bio;
BUG_ON(!list_empty(&io_end->list));
BUG_ON(io_end->flag & EXT4_IO_END_UNWRITTEN);
WARN_ON(io_end->handle);
for (bio = io_end->bio; bio; bio = next_bio) {
next_bio = bio->bi_private;
ext4_finish_bio(bio);
bio_put(bio);
}
ext4_free_io_end_vec(io_end);
kmem_cache_free(io_end_cachep, io_end);
}
/*
* On successful IO, check a range of space and convert unwritten extents to
* written. On IO failure, check if journal abort is needed. Note that
* we are protected from truncate touching same part of extent tree by the
* fact that truncate code waits for all DIO to finish (thus exclusion from
* direct IO is achieved) and also waits for writeback to complete. Thus we
* cannot get to ext4_ext_truncate() before all IOs overlapping that range are
* completed (happens from ext4_free_ioend()).
*/
static int ext4_end_io_end(ext4_io_end_t *io_end)
{
struct inode *inode = io_end->inode;
handle_t *handle = io_end->handle;
struct super_block *sb = inode->i_sb;
int ret = 0;
ext4_debug("ext4_end_io_nolock: io_end 0x%p from inode %llu,list->next 0x%p,"
"list->prev 0x%p\n",
io_end, inode->i_ino, io_end->list.next, io_end->list.prev);
/*
* Do not convert the unwritten extents if data writeback fails,
* or stale data may be exposed.
*/
io_end->handle = NULL; /* Following call will use up the handle */
if (unlikely(io_end->flag & EXT4_IO_END_FAILED)) {
ret = -EIO;
if (handle)
jbd2_journal_free_reserved(handle);
if (test_opt(sb, DATA_ERR_ABORT))
jbd2_journal_abort(EXT4_SB(sb)->s_journal, ret);
} else {
ret = ext4_convert_unwritten_io_end_vec(handle, io_end);
}
if (ret < 0 && !ext4_emergency_state(sb) &&
io_end->flag & EXT4_IO_END_UNWRITTEN) {
ext4_msg(sb, KERN_EMERG,
"failed to convert unwritten extents to written "
"extents -- potential data loss! "
"(inode %llu, error %d)", inode->i_ino, ret);
}
ext4_clear_io_unwritten_flag(io_end);
ext4_release_io_end(io_end);
return ret;
}
static void dump_completed_IO(struct inode *inode, struct list_head *head)
{
#ifdef EXT4FS_DEBUG
struct list_head *cur, *before, *after;
ext4_io_end_t *io_end, *io_end0, *io_end1;
if (list_empty(head))
return;
ext4_debug("Dump inode %llu completed io list\n", inode->i_ino);
list_for_each_entry(io_end, head, list) {
cur = &io_end->list;
before = cur->prev;
io_end0 = container_of(before, ext4_io_end_t, list);
after = cur->next;
io_end1 = container_of(after, ext4_io_end_t, list);
ext4_debug("io 0x%p from inode %llu,prev 0x%p,next 0x%p\n",
io_end, inode->i_ino, io_end0, io_end1);
}
#endif
}
static bool ext4_io_end_defer_completion(ext4_io_end_t *io_end)
{
if (io_end->flag & EXT4_IO_END_UNWRITTEN &&
!list_empty(&io_end->list_vec))
return true;
if (test_opt(io_end->inode->i_sb, DATA_ERR_ABORT) &&
io_end->flag & EXT4_IO_END_FAILED &&
!ext4_emergency_state(io_end->inode->i_sb))
return true;
return false;
}
/* Add the io_end to per-inode completed end_io list. */
static void ext4_add_complete_io(ext4_io_end_t *io_end)
{
struct ext4_inode_info *ei = EXT4_I(io_end->inode);
struct ext4_sb_info *sbi = EXT4_SB(io_end->inode->i_sb);
struct workqueue_struct *wq;
unsigned long flags;
/* Only reserved conversions or pending IO errors will enter here. */
WARN_ON(!(io_end->flag & EXT4_IO_END_DEFER_COMPLETION));
WARN_ON(io_end->flag & EXT4_IO_END_UNWRITTEN &&
!io_end->handle && sbi->s_journal);
WARN_ON(!io_end->bio);
spin_lock_irqsave(&ei->i_completed_io_lock, flags);
wq = sbi->rsv_conversion_wq;
if (list_empty(&ei->i_rsv_conversion_list))
queue_work(wq, &ei->i_rsv_conversion_work);
list_add_tail(&io_end->list, &ei->i_rsv_conversion_list);
spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
}
static int ext4_do_flush_completed_IO(struct inode *inode,
struct list_head *head)
{
ext4_io_end_t *io_end;
struct list_head unwritten;
unsigned long flags;
struct ext4_inode_info *ei = EXT4_I(inode);
int err, ret = 0;
spin_lock_irqsave(&ei->i_completed_io_lock, flags);
dump_completed_IO(inode, head);
list_replace_init(head, &unwritten);
spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
while (!list_empty(&unwritten)) {
io_end = list_entry(unwritten.next, ext4_io_end_t, list);
BUG_ON(!(io_end->flag & EXT4_IO_END_DEFER_COMPLETION));
list_del_init(&io_end->list);
err = ext4_end_io_end(io_end);
if (unlikely(!ret && err))
ret = err;
}
return ret;
}
/*
* Used to convert unwritten extents to written extents upon IO completion,
* or used to abort the journal upon IO errors.
*/
void ext4_end_io_rsv_work(struct work_struct *work)
{
struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
i_rsv_conversion_work);
ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_rsv_conversion_list);
}
ext4_io_end_t *ext4_init_io_end(struct inode *inode, gfp_t flags)
{
ext4_io_end_t *io_end = kmem_cache_zalloc(io_end_cachep, flags);
if (io_end) {
io_end->inode = inode;
INIT_LIST_HEAD(&io_end->list);
INIT_LIST_HEAD(&io_end->list_vec);
refcount_set(&io_end->count, 1);
}
return io_end;
}
void ext4_put_io_end_defer(ext4_io_end_t *io_end)
{
if (refcount_dec_and_test(&io_end->count)) {
if (ext4_io_end_defer_completion(io_end))
return ext4_add_complete_io(io_end);
ext4_release_io_end(io_end);
}
}
int ext4_put_io_end(ext4_io_end_t *io_end)
{
if (refcount_dec_and_test(&io_end->count)) {
if (ext4_io_end_defer_completion(io_end))
return ext4_end_io_end(io_end);
ext4_release_io_end(io_end);
}
return 0;
}
ext4_io_end_t *ext4_get_io_end(ext4_io_end_t *io_end)
{
refcount_inc(&io_end->count);
return io_end;
}
/* BIO completion function for page writeback */
static void ext4_end_bio(struct bio *bio)
{
ext4_io_end_t *io_end = bio->bi_private;
sector_t bi_sector = bio->bi_iter.bi_sector;
if (WARN_ONCE(!io_end, "io_end is NULL: %pg: sector %Lu len %u err %d\n",
bio->bi_bdev,
(long long) bio->bi_iter.bi_sector,
(unsigned) bio_sectors(bio),
bio->bi_status)) {
ext4_finish_bio(bio);
bio_put(bio);
return;
}
bio->bi_end_io = NULL;
if (bio->bi_status) {
struct inode *inode = io_end->inode;
ext4_warning(inode->i_sb, "I/O error %d writing to inode %llu "
"starting block %llu)",
bio->bi_status, inode->i_ino,
(unsigned long long)
bi_sector >> (inode->i_blkbits - 9));
io_end->flag |= EXT4_IO_END_FAILED;
mapping_set_error(inode->i_mapping,
blk_status_to_errno(bio->bi_status));
}
if (ext4_io_end_defer_completion(io_end)) {
/*
* Link bio into list hanging from io_end. We have to do it
* atomically as bio completions can be racing against each
* other.
*/
bio->bi_private = xchg(&io_end->bio, bio);
ext4_put_io_end_defer(io_end);
} else {
/*
* Drop io_end reference early. Inode can get freed once
* we finish the bio.
*/
ext4_put_io_end_defer(io_end);
ext4_finish_bio(bio);
bio_put(bio);
}
}
void ext4_io_submit(struct ext4_io_submit *io)
{
struct bio *bio = io->io_bio;
if (bio) {
if (io->io_wbc->sync_mode == WB_SYNC_ALL)
io->io_bio->bi_opf |= REQ_SYNC;
blk_crypto_submit_bio(io->io_bio);
}
io->io_bio = NULL;
}
void ext4_io_submit_init(struct ext4_io_submit *io,
struct writeback_control *wbc)
{
io->io_wbc = wbc;
io->io_bio = NULL;
io->io_end = NULL;
}
static void io_submit_init_bio(struct ext4_io_submit *io,
struct inode *inode,
struct folio *folio,
struct buffer_head *bh)
{
struct bio *bio;
/*
* bio_alloc will _always_ be able to allocate a bio if
* __GFP_DIRECT_RECLAIM is set, see comments for bio_alloc_bioset().
*/
bio = bio_alloc(bh->b_bdev, BIO_MAX_VECS, REQ_OP_WRITE, GFP_NOIO);
fscrypt_set_bio_crypt_ctx(bio, inode, folio_pos(folio) + bh_offset(bh),
GFP_NOIO);
bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9);
bio->bi_end_io = ext4_end_bio;
bio->bi_private = ext4_get_io_end(io->io_end);
bio->bi_write_hint = inode->i_write_hint;
io->io_bio = bio;
io->io_next_block = bh->b_blocknr;
wbc_init_bio(io->io_wbc, bio);
}
static bool io_submit_need_new_bio(struct ext4_io_submit *io,
struct inode *inode,
struct folio *folio,
struct buffer_head *bh)
{
if (bh->b_blocknr != io->io_next_block)
return true;
if (!fscrypt_mergeable_bio(io->io_bio, inode,
folio_pos(folio) + bh_offset(bh)))
return true;
return false;
}
static void io_submit_add_bh(struct ext4_io_submit *io,
struct inode *inode,
struct folio *folio,
struct folio *io_folio,
struct buffer_head *bh)
{
if (io->io_bio && io_submit_need_new_bio(io, inode, folio, bh)) {
submit_and_retry:
ext4_io_submit(io);
}
if (io->io_bio == NULL)
io_submit_init_bio(io, inode, folio, bh);
if (!bio_add_folio(io->io_bio, io_folio, bh->b_size, bh_offset(bh)))
goto submit_and_retry;
wbc_account_cgroup_owner(io->io_wbc, folio, bh->b_size);
io->io_next_block++;
}
int ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *folio,
size_t len)
{
struct folio *io_folio = folio;
struct inode *inode = folio->mapping->host;
unsigned block_start;
struct buffer_head *bh, *head;
int ret = 0;
int nr_to_submit = 0;
struct writeback_control *wbc = io->io_wbc;
bool keep_towrite = false;
BUG_ON(!folio_test_locked(folio));
BUG_ON(folio_test_writeback(folio));
/*
* Comments copied from block_write_full_folio:
*
* The folio straddles i_size. It must be zeroed out on each and every
* writepage invocation because it may be mmapped. "A file is mapped
* in multiples of the page size. For a file that is not a multiple of
* the page size, the remaining memory is zeroed when mapped, and
* writes to that region are not written out to the file."
*/
if (len < folio_size(folio))
folio_zero_segment(folio, len, folio_size(folio));
/*
* In the first loop we prepare and mark buffers to submit. We have to
* mark all buffers in the folio before submitting so that
* folio_end_writeback() cannot be called from ext4_end_bio() when IO
* on the first buffer finishes and we are still working on submitting
* the second buffer.
*/
bh = head = folio_buffers(folio);
do {
block_start = bh_offset(bh);
if (block_start >= len) {
clear_buffer_dirty(bh);
set_buffer_uptodate(bh);
continue;
}
if (!buffer_dirty(bh) || buffer_delay(bh) ||
!buffer_mapped(bh) || buffer_unwritten(bh)) {
/* A hole? We can safely clear the dirty bit */
if (!buffer_mapped(bh))
clear_buffer_dirty(bh);
/*
* Keeping dirty some buffer we cannot write? Make sure
* to redirty the folio and keep TOWRITE tag so that
* racing WB_SYNC_ALL writeback does not skip the folio.
* This happens e.g. when doing writeout for
* transaction commit or when journalled data is not
* yet committed.
*/
if (buffer_dirty(bh) ||
(buffer_jbd(bh) && buffer_jbddirty(bh))) {
if (!folio_test_dirty(folio))
folio_redirty_for_writepage(wbc, folio);
keep_towrite = true;
}
continue;
}
if (buffer_new(bh))
clear_buffer_new(bh);
set_buffer_async_write(bh);
clear_buffer_dirty(bh);
nr_to_submit++;
} while ((bh = bh->b_this_page) != head);
if (!nr_to_submit) {
/*
* We have nothing to submit. Just cycle the folio through
* writeback state to properly update xarray tags.
*/
__folio_start_writeback(folio, keep_towrite);
folio_end_writeback(folio);
return 0;
}
bh = head = folio_buffers(folio);
/*
* If any blocks are being written to an encrypted file, encrypt them
* into a bounce page. For simplicity, just encrypt until the last
* block which might be needed. This may cause some unneeded blocks
* (e.g. holes) to be unnecessarily encrypted, but this is rare and
* can't happen in the common case of blocksize == PAGE_SIZE.
*/
if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
gfp_t gfp_flags = GFP_NOFS;
unsigned int enc_bytes = round_up(len, i_blocksize(inode));
struct page *bounce_page;
/*
* Since bounce page allocation uses a mempool, we can only use
* a waiting mask (i.e. request guaranteed allocation) on the
* first page of the bio. Otherwise it can deadlock.
*/
if (io->io_bio)
gfp_flags = GFP_NOWAIT;
retry_encrypt:
bounce_page = fscrypt_encrypt_pagecache_blocks(folio,
enc_bytes, 0, gfp_flags);
if (IS_ERR(bounce_page)) {
ret = PTR_ERR(bounce_page);
if (ret == -ENOMEM &&
(io->io_bio || wbc->sync_mode == WB_SYNC_ALL)) {
gfp_t new_gfp_flags = GFP_NOFS;
if (io->io_bio)
ext4_io_submit(io);
else
new_gfp_flags |= __GFP_NOFAIL;
memalloc_retry_wait(gfp_flags);
gfp_flags = new_gfp_flags;
goto retry_encrypt;
}
printk_ratelimited(KERN_ERR "%s: ret = %d\n", __func__, ret);
folio_redirty_for_writepage(wbc, folio);
do {
if (buffer_async_write(bh)) {
clear_buffer_async_write(bh);
set_buffer_dirty(bh);
}
bh = bh->b_this_page;
} while (bh != head);
return ret;
}
io_folio = page_folio(bounce_page);
}
__folio_start_writeback(folio, keep_towrite);
/* Now submit buffers to write */
do {
if (!buffer_async_write(bh))
continue;
io_submit_add_bh(io, inode, folio, io_folio, bh);
} while ((bh = bh->b_this_page) != head);
return 0;
}
+455
View File
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/readpage.c
*
* Copyright (C) 2002, Linus Torvalds.
* Copyright (C) 2015, Google, Inc.
*
* This was originally taken from fs/mpage.c
*
* The ext4_mpage_readpages() function here is intended to
* replace mpage_readahead() in the general case, not just for
* encrypted files. It has some limitations (see below), where it
* will fall back to read_block_full_page(), but these limitations
* should only be hit when page_size != block_size.
*
* This will allow us to attach a callback function to support ext4
* encryption.
*
* If anything unusual happens, such as:
*
* - encountering a page which has buffers
* - encountering a page which has a non-hole after a hole
* - encountering a page with non-contiguous blocks
*
* then this code just gives up and calls the buffer_head-based read function.
* It does handle a page which has holes at the end - that is a common case:
* the end-of-file on blocksize < PAGE_SIZE setups.
*
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/kdev_t.h>
#include <linux/gfp.h>
#include <linux/bio.h>
#include <linux/fs.h>
#include <linux/buffer_head.h>
#include <linux/blk-crypto.h>
#include <linux/blkdev.h>
#include <linux/highmem.h>
#include <linux/prefetch.h>
#include <linux/mpage.h>
#include <linux/writeback.h>
#include <linux/backing-dev.h>
#include "ext4.h"
#include <trace/events/ext4.h>
#define NUM_PREALLOC_POST_READ_CTXS 128
static struct kmem_cache *bio_post_read_ctx_cache;
static mempool_t *bio_post_read_ctx_pool;
/* postprocessing steps for read bios */
enum bio_post_read_step {
STEP_INITIAL = 0,
STEP_DECRYPT,
STEP_VERITY,
STEP_MAX,
};
struct bio_post_read_ctx {
struct bio *bio;
struct fsverity_info *vi;
struct work_struct work;
unsigned int cur_step;
unsigned int enabled_steps;
};
static void __read_end_io(struct bio *bio)
{
struct folio_iter fi;
bio_for_each_folio_all(fi, bio)
folio_end_read(fi.folio, bio->bi_status == 0);
if (bio->bi_private)
mempool_free(bio->bi_private, bio_post_read_ctx_pool);
bio_put(bio);
}
static void bio_post_read_processing(struct bio_post_read_ctx *ctx);
static void decrypt_work(struct work_struct *work)
{
struct bio_post_read_ctx *ctx =
container_of(work, struct bio_post_read_ctx, work);
struct bio *bio = ctx->bio;
if (fscrypt_decrypt_bio(bio))
bio_post_read_processing(ctx);
else
__read_end_io(bio);
}
static void verity_work(struct work_struct *work)
{
struct bio_post_read_ctx *ctx =
container_of(work, struct bio_post_read_ctx, work);
struct bio *bio = ctx->bio;
struct fsverity_info *vi = ctx->vi;
/*
* fsverity_verify_bio() may call readahead() again, and although verity
* will be disabled for that, decryption may still be needed, causing
* another bio_post_read_ctx to be allocated. So to guarantee that
* mempool_alloc() never deadlocks we must free the current ctx first.
* This is safe because verity is the last post-read step.
*/
BUILD_BUG_ON(STEP_VERITY + 1 != STEP_MAX);
mempool_free(ctx, bio_post_read_ctx_pool);
bio->bi_private = NULL;
fsverity_verify_bio(vi, bio);
__read_end_io(bio);
}
static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
{
/*
* We use different work queues for decryption and for verity because
* verity may require reading metadata pages that need decryption, and
* we shouldn't recurse to the same workqueue.
*/
switch (++ctx->cur_step) {
case STEP_DECRYPT:
if (ctx->enabled_steps & (1 << STEP_DECRYPT)) {
INIT_WORK(&ctx->work, decrypt_work);
fscrypt_enqueue_decrypt_work(&ctx->work);
return;
}
ctx->cur_step++;
fallthrough;
case STEP_VERITY:
if (IS_ENABLED(CONFIG_FS_VERITY) &&
ctx->enabled_steps & (1 << STEP_VERITY)) {
INIT_WORK(&ctx->work, verity_work);
fsverity_enqueue_verify_work(&ctx->work);
return;
}
ctx->cur_step++;
fallthrough;
default:
__read_end_io(ctx->bio);
}
}
static bool bio_post_read_required(struct bio *bio)
{
return bio->bi_private && !bio->bi_status;
}
/*
* I/O completion handler for multipage BIOs.
*
* The mpage code never puts partial pages into a BIO (except for end-of-file).
* If a page does not map to a contiguous run of blocks then it simply falls
* back to block_read_full_folio().
*
* Why is this? If a page's completion depends on a number of different BIOs
* which can complete in any order (or at the same time) then determining the
* status of that page is hard. See end_buffer_async_read() for the details.
* There is no point in duplicating all that complexity.
*/
static void mpage_end_io(struct bio *bio)
{
if (bio_post_read_required(bio)) {
struct bio_post_read_ctx *ctx = bio->bi_private;
ctx->cur_step = STEP_INITIAL;
bio_post_read_processing(ctx);
return;
}
__read_end_io(bio);
}
static void ext4_set_bio_post_read_ctx(struct bio *bio,
const struct inode *inode,
struct fsverity_info *vi)
{
unsigned int post_read_steps = 0;
if (fscrypt_inode_uses_fs_layer_crypto(inode))
post_read_steps |= 1 << STEP_DECRYPT;
if (vi)
post_read_steps |= 1 << STEP_VERITY;
if (post_read_steps) {
/* Due to the mempool, this never fails. */
struct bio_post_read_ctx *ctx =
mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
ctx->bio = bio;
ctx->vi = vi;
ctx->enabled_steps = post_read_steps;
bio->bi_private = ctx;
}
}
static inline loff_t ext4_readpage_limit(struct inode *inode)
{
if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
return inode->i_sb->s_maxbytes;
return i_size_read(inode);
}
static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
struct readahead_control *rac, struct folio *folio)
{
struct bio *bio = NULL;
sector_t last_block_in_bio = 0;
const unsigned blkbits = inode->i_blkbits;
const unsigned blocksize = 1 << blkbits;
sector_t block_in_file;
sector_t last_block;
sector_t last_block_in_file;
sector_t first_block;
loff_t pos;
unsigned page_block;
struct block_device *bdev = inode->i_sb->s_bdev;
int length;
unsigned relative_block = 0;
struct ext4_map_blocks map;
unsigned int nr_pages, folio_pages;
map.m_pblk = 0;
map.m_lblk = 0;
map.m_len = 0;
map.m_flags = 0;
nr_pages = rac ? readahead_count(rac) : folio_nr_pages(folio);
for (; nr_pages; nr_pages -= folio_pages) {
int fully_mapped = 1;
unsigned int first_hole;
unsigned int blocks_per_folio;
if (rac)
folio = readahead_folio(rac);
folio_pages = folio_nr_pages(folio);
prefetchw(&folio->flags);
if (folio_buffers(folio))
goto confused;
blocks_per_folio = folio_size(folio) >> blkbits;
first_hole = blocks_per_folio;
pos = folio_pos(folio);
block_in_file = pos >> blkbits;
last_block = EXT4_PG_TO_LBLK(inode, folio->index + nr_pages);
last_block_in_file = (ext4_readpage_limit(inode) +
blocksize - 1) >> blkbits;
if (last_block > last_block_in_file)
last_block = last_block_in_file;
page_block = 0;
/*
* Map blocks using the previous result first.
*/
if ((map.m_flags & EXT4_MAP_MAPPED) &&
block_in_file > map.m_lblk &&
block_in_file < (map.m_lblk + map.m_len)) {
unsigned map_offset = block_in_file - map.m_lblk;
unsigned last = map.m_len - map_offset;
first_block = map.m_pblk + map_offset;
for (relative_block = 0; ; relative_block++) {
if (relative_block == last) {
/* needed? */
map.m_flags &= ~EXT4_MAP_MAPPED;
break;
}
if (page_block == blocks_per_folio)
break;
page_block++;
block_in_file++;
}
}
/*
* Then do more ext4_map_blocks() calls until we are
* done with this folio.
*/
while (page_block < blocks_per_folio) {
if (block_in_file < last_block) {
map.m_lblk = block_in_file;
map.m_len = last_block - block_in_file;
if (ext4_map_blocks(NULL, inode, &map, 0) < 0) {
set_error_page:
folio_zero_segment(folio, 0,
folio_size(folio));
folio_unlock(folio);
goto next_page;
}
}
if ((map.m_flags & EXT4_MAP_MAPPED) == 0) {
fully_mapped = 0;
if (first_hole == blocks_per_folio)
first_hole = page_block;
page_block++;
block_in_file++;
continue;
}
if (first_hole != blocks_per_folio)
goto confused; /* hole -> non-hole */
/* Contiguous blocks? */
if (!page_block)
first_block = map.m_pblk;
else if (first_block + page_block != map.m_pblk)
goto confused;
for (relative_block = 0; ; relative_block++) {
if (relative_block == map.m_len) {
/* needed? */
map.m_flags &= ~EXT4_MAP_MAPPED;
break;
} else if (page_block == blocks_per_folio)
break;
page_block++;
block_in_file++;
}
}
if (first_hole != blocks_per_folio) {
folio_zero_segment(folio, first_hole << blkbits,
folio_size(folio));
if (first_hole == 0) {
if (vi && !fsverity_verify_folio(vi, folio))
goto set_error_page;
folio_end_read(folio, true);
continue;
}
} else if (fully_mapped) {
folio_set_mappedtodisk(folio);
}
/*
* This folio will go to BIO. Do we need to send this
* BIO off first?
*/
if (bio && (last_block_in_bio != first_block - 1 ||
!fscrypt_mergeable_bio(bio, inode, pos))) {
submit_and_realloc:
blk_crypto_submit_bio(bio);
bio = NULL;
}
if (bio == NULL) {
/*
* bio_alloc will _always_ be able to allocate a bio if
* __GFP_DIRECT_RECLAIM is set, see bio_alloc_bioset().
*/
bio = bio_alloc(bdev, bio_max_segs(nr_pages),
REQ_OP_READ, GFP_KERNEL);
fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_KERNEL);
ext4_set_bio_post_read_ctx(bio, inode, vi);
bio->bi_iter.bi_sector = first_block << (blkbits - 9);
bio->bi_end_io = mpage_end_io;
if (rac)
bio->bi_opf |= REQ_RAHEAD;
}
length = first_hole << blkbits;
if (!bio_add_folio(bio, folio, length, 0))
goto submit_and_realloc;
if (((map.m_flags & EXT4_MAP_BOUNDARY) &&
(relative_block == map.m_len)) ||
(first_hole != blocks_per_folio)) {
blk_crypto_submit_bio(bio);
bio = NULL;
} else
last_block_in_bio = first_block + blocks_per_folio - 1;
continue;
confused:
if (bio) {
blk_crypto_submit_bio(bio);
bio = NULL;
}
if (!folio_test_uptodate(folio))
block_read_full_folio(folio, ext4_get_block);
else
folio_unlock(folio);
next_page:
; /* A label shall be followed by a statement until C23 */
}
if (bio)
blk_crypto_submit_bio(bio);
return 0;
}
int ext4_read_folio(struct file *file, struct folio *folio)
{
struct inode *inode = folio->mapping->host;
struct fsverity_info *vi = NULL;
int ret;
trace_ext4_read_folio(inode, folio);
if (ext4_has_inline_data(inode)) {
ret = ext4_readpage_inline(inode, folio);
if (ret != -EAGAIN)
return ret;
}
if (folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
vi = fsverity_get_info(inode);
if (vi)
fsverity_readahead(vi, folio->index, folio_nr_pages(folio));
return ext4_mpage_readpages(inode, vi, NULL, folio);
}
void ext4_readahead(struct readahead_control *rac)
{
struct inode *inode = rac->mapping->host;
struct fsverity_info *vi = NULL;
/* If the file has inline data, no need to do readahead. */
if (ext4_has_inline_data(inode))
return;
if (readahead_index(rac) < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
vi = fsverity_get_info(inode);
if (vi)
fsverity_readahead(vi, readahead_index(rac),
readahead_count(rac));
ext4_mpage_readpages(inode, vi, rac, NULL);
}
int __init ext4_init_post_read_processing(void)
{
bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, SLAB_RECLAIM_ACCOUNT);
if (!bio_post_read_ctx_cache)
goto fail;
bio_post_read_ctx_pool =
mempool_create_slab_pool(NUM_PREALLOC_POST_READ_CTXS,
bio_post_read_ctx_cache);
if (!bio_post_read_ctx_pool)
goto fail_free_cache;
return 0;
fail_free_cache:
kmem_cache_destroy(bio_post_read_ctx_cache);
fail:
return -ENOMEM;
}
void ext4_exit_post_read_processing(void)
{
mempool_destroy(bio_post_read_ctx_pool);
kmem_cache_destroy(bio_post_read_ctx_cache);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/symlink.c
*
* Only fast symlinks left here - the rest is done by generic code. AV, 1999
*
* 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/symlink.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* ext4 symlink handling code
*/
#include <linux/fs.h>
#include <linux/namei.h>
#include "ext4.h"
#include "xattr.h"
static const char *ext4_encrypted_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
struct buffer_head *bh = NULL;
const void *caddr;
unsigned int max_size;
const char *paddr;
if (!dentry)
return ERR_PTR(-ECHILD);
if (ext4_inode_is_fast_symlink(inode)) {
caddr = EXT4_I(inode)->i_data;
max_size = sizeof(EXT4_I(inode)->i_data);
} else {
bh = ext4_bread(NULL, inode, 0, 0);
if (IS_ERR(bh))
return ERR_CAST(bh);
if (!bh) {
EXT4_ERROR_INODE(inode, "bad symlink.");
return ERR_PTR(-EFSCORRUPTED);
}
caddr = bh->b_data;
max_size = inode->i_sb->s_blocksize;
}
paddr = fscrypt_get_symlink(inode, caddr, max_size, done);
brelse(bh);
return paddr;
}
static int ext4_encrypted_symlink_getattr(struct mnt_idmap *idmap,
const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int query_flags)
{
ext4_getattr(idmap, path, stat, request_mask, query_flags);
return fscrypt_symlink_getattr(path, stat);
}
static void ext4_free_link(void *bh)
{
brelse(bh);
}
static const char *ext4_get_link(struct dentry *dentry, struct inode *inode,
struct delayed_call *callback)
{
struct buffer_head *bh;
char *inline_link;
/*
* Create a new inlined symlink is not supported, just provide a
* method to read the leftovers.
*/
if (ext4_has_inline_data(inode)) {
if (!dentry)
return ERR_PTR(-ECHILD);
inline_link = ext4_read_inline_link(inode);
if (!IS_ERR(inline_link))
set_delayed_call(callback, kfree_link, inline_link);
return inline_link;
}
if (!dentry) {
bh = ext4_getblk(NULL, inode, 0, EXT4_GET_BLOCKS_CACHED_NOWAIT);
if (IS_ERR_OR_NULL(bh))
return ERR_PTR(-ECHILD);
if (!ext4_buffer_uptodate(bh)) {
brelse(bh);
return ERR_PTR(-ECHILD);
}
} else {
bh = ext4_bread(NULL, inode, 0, 0);
if (IS_ERR(bh))
return ERR_CAST(bh);
if (!bh) {
EXT4_ERROR_INODE(inode, "bad symlink.");
return ERR_PTR(-EFSCORRUPTED);
}
}
set_delayed_call(callback, ext4_free_link, bh);
nd_terminate_link(bh->b_data, inode->i_size,
inode->i_sb->s_blocksize - 1);
return bh->b_data;
}
const struct inode_operations ext4_encrypted_symlink_inode_operations = {
.get_link = ext4_encrypted_get_link,
.setattr = ext4_setattr,
.getattr = ext4_encrypted_symlink_getattr,
.listxattr = ext4_listxattr,
};
const struct inode_operations ext4_symlink_inode_operations = {
.get_link = ext4_get_link,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
.listxattr = ext4_listxattr,
};
const struct inode_operations ext4_fast_symlink_inode_operations = {
.get_link = simple_get_link,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
.listxattr = ext4_listxattr,
};
+698
View File
@@ -0,0 +1,698 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/sysfs.c
*
* Copyright (C) 1992, 1993, 1994, 1995
* Remy Card (card@masi.ibp.fr)
* Theodore Ts'o (tytso@mit.edu)
*
*/
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/part_stat.h>
#include "ext4.h"
#include "ext4_jbd2.h"
typedef enum {
attr_noop,
attr_delayed_allocation_blocks,
attr_session_write_kbytes,
attr_lifetime_write_kbytes,
attr_reserved_clusters,
attr_sra_exceeded_retry_limit,
attr_inode_readahead,
attr_trigger_test_error,
attr_first_error_time,
attr_last_error_time,
attr_clusters_in_group,
attr_mb_order,
attr_feature,
attr_pointer_pi,
attr_pointer_ui,
attr_pointer_ul,
attr_pointer_u64,
attr_pointer_u8,
attr_pointer_string,
attr_pointer_atomic,
attr_journal_task,
attr_err_report_sec,
} attr_id_t;
typedef enum {
ptr_explicit,
ptr_ext4_sb_info_offset,
ptr_ext4_super_block_offset,
} attr_ptr_t;
static const char proc_dirname[] = "fs/ext4";
static struct proc_dir_entry *ext4_proc_root;
struct ext4_attr {
struct attribute attr;
short attr_id;
short attr_ptr;
unsigned short attr_size;
union {
int offset;
void *explicit_ptr;
} u;
};
static ssize_t session_write_kbytes_show(struct ext4_sb_info *sbi, char *buf)
{
struct super_block *sb = sbi->s_buddy_cache->i_sb;
return sysfs_emit(buf, "%lu\n",
(part_stat_read(sb->s_bdev, sectors[STAT_WRITE]) -
sbi->s_sectors_written_start) >> 1);
}
static ssize_t lifetime_write_kbytes_show(struct ext4_sb_info *sbi, char *buf)
{
struct super_block *sb = sbi->s_buddy_cache->i_sb;
return sysfs_emit(buf, "%llu\n",
(unsigned long long)(sbi->s_kbytes_written +
((part_stat_read(sb->s_bdev, sectors[STAT_WRITE]) -
EXT4_SB(sb)->s_sectors_written_start) >> 1)));
}
static ssize_t inode_readahead_blks_store(struct ext4_sb_info *sbi,
const char *buf, size_t count)
{
unsigned long t;
int ret;
ret = kstrtoul(skip_spaces(buf), 0, &t);
if (ret)
return ret;
if (t && (!is_power_of_2(t) || t > 0x40000000))
return -EINVAL;
sbi->s_inode_readahead_blks = t;
return count;
}
static ssize_t reserved_clusters_store(struct ext4_sb_info *sbi,
const char *buf, size_t count)
{
unsigned long long val;
ext4_fsblk_t clusters = (ext4_blocks_count(sbi->s_es) >>
sbi->s_cluster_bits);
int ret;
ret = kstrtoull(skip_spaces(buf), 0, &val);
if (ret || val >= clusters || (s64)val < 0)
return -EINVAL;
atomic64_set(&sbi->s_resv_clusters, val);
return count;
}
static ssize_t trigger_test_error(struct ext4_sb_info *sbi,
const char *buf, size_t count)
{
int len = count;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (len && buf[len-1] == '\n')
len--;
if (len)
ext4_error(sbi->s_sb, "%.*s", len, buf);
return count;
}
static ssize_t err_report_sec_store(struct ext4_sb_info *sbi,
const char *buf, size_t count)
{
unsigned long t;
int ret;
ret = kstrtoul(skip_spaces(buf), 0, &t);
if (ret)
return ret;
/*the maximum time interval must not exceed one year.*/
if (t > (365*24*60*60))
return -EINVAL;
if (sbi->s_err_report_sec == t) /*nothing to do*/
goto out;
else if (!sbi->s_err_report_sec && t) {
timer_setup(&sbi->s_err_report, print_daily_error_info, 0);
} else if (sbi->s_err_report_sec && !t) {
timer_delete_sync(&sbi->s_err_report);
goto out;
}
sbi->s_err_report_sec = t;
mod_timer(&sbi->s_err_report, jiffies + secs_to_jiffies(sbi->s_err_report_sec));
out:
return count;
}
static ssize_t journal_task_show(struct ext4_sb_info *sbi, char *buf)
{
if (!sbi->s_journal)
return sysfs_emit(buf, "<none>\n");
return sysfs_emit(buf, "%d\n",
task_pid_vnr(sbi->s_journal->j_task));
}
#define EXT4_ATTR(_name,_mode,_id) \
static struct ext4_attr ext4_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.attr_id = attr_##_id, \
}
#define EXT4_ATTR_FUNC(_name,_mode) EXT4_ATTR(_name,_mode,_name)
#define EXT4_ATTR_FEATURE(_name) EXT4_ATTR(_name, 0444, feature)
#define EXT4_ATTR_OFFSET(_name,_mode,_id,_struct,_elname) \
static struct ext4_attr ext4_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.attr_id = attr_##_id, \
.attr_ptr = ptr_##_struct##_offset, \
.u = { \
.offset = offsetof(struct _struct, _elname),\
}, \
}
#define EXT4_ATTR_STRING(_name,_mode,_size,_struct,_elname) \
static struct ext4_attr ext4_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.attr_id = attr_pointer_string, \
.attr_size = _size, \
.attr_ptr = ptr_##_struct##_offset, \
.u = { \
.offset = offsetof(struct _struct, _elname),\
}, \
}
#define EXT4_RO_ATTR_ES_UI(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0444, pointer_ui, ext4_super_block, _elname)
#define EXT4_RO_ATTR_ES_U8(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0444, pointer_u8, ext4_super_block, _elname)
#define EXT4_RO_ATTR_ES_U64(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0444, pointer_u64, ext4_super_block, _elname)
#define EXT4_RO_ATTR_ES_STRING(_name,_elname,_size) \
EXT4_ATTR_STRING(_name, 0444, _size, ext4_super_block, _elname)
#define EXT4_RW_ATTR_SBI_PI(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0644, pointer_pi, ext4_sb_info, _elname)
#define EXT4_RW_ATTR_SBI_UI(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0644, pointer_ui, ext4_sb_info, _elname)
#define EXT4_RW_ATTR_SBI_UL(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0644, pointer_ul, ext4_sb_info, _elname)
#define EXT4_RO_ATTR_SBI_ATOMIC(_name,_elname) \
EXT4_ATTR_OFFSET(_name, 0444, pointer_atomic, ext4_sb_info, _elname)
#define EXT4_ATTR_PTR(_name,_mode,_id,_ptr) \
static struct ext4_attr ext4_attr_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.attr_id = attr_##_id, \
.attr_ptr = ptr_explicit, \
.u = { \
.explicit_ptr = _ptr, \
}, \
}
#define ATTR_LIST(name) &ext4_attr_##name.attr
EXT4_ATTR_FUNC(delayed_allocation_blocks, 0444);
EXT4_ATTR_FUNC(session_write_kbytes, 0444);
EXT4_ATTR_FUNC(lifetime_write_kbytes, 0444);
EXT4_ATTR_FUNC(reserved_clusters, 0644);
EXT4_ATTR_FUNC(sra_exceeded_retry_limit, 0444);
EXT4_ATTR_OFFSET(inode_readahead_blks, 0644, inode_readahead,
ext4_sb_info, s_inode_readahead_blks);
EXT4_ATTR_OFFSET(mb_group_prealloc, 0644, clusters_in_group,
ext4_sb_info, s_mb_group_prealloc);
EXT4_ATTR_OFFSET(mb_best_avail_max_trim_order, 0644, mb_order,
ext4_sb_info, s_mb_best_avail_max_trim_order);
EXT4_ATTR_OFFSET(err_report_sec, 0644, err_report_sec, ext4_sb_info, s_err_report_sec);
EXT4_RW_ATTR_SBI_UI(inode_goal, s_inode_goal);
EXT4_RW_ATTR_SBI_UI(mb_stats, s_mb_stats);
EXT4_RW_ATTR_SBI_UI(mb_max_to_scan, s_mb_max_to_scan);
EXT4_RW_ATTR_SBI_UI(mb_min_to_scan, s_mb_min_to_scan);
EXT4_RW_ATTR_SBI_UI(mb_order2_req, s_mb_order2_reqs);
EXT4_RW_ATTR_SBI_UI(mb_stream_req, s_mb_stream_request);
EXT4_RW_ATTR_SBI_UI(mb_max_linear_groups, s_mb_max_linear_groups);
EXT4_RW_ATTR_SBI_UI(extent_max_zeroout_kb, s_extent_max_zeroout_kb);
EXT4_ATTR(trigger_fs_error, 0200, trigger_test_error);
EXT4_RW_ATTR_SBI_PI(err_ratelimit_interval_ms, s_err_ratelimit_state.interval);
EXT4_RW_ATTR_SBI_PI(err_ratelimit_burst, s_err_ratelimit_state.burst);
EXT4_RW_ATTR_SBI_PI(warning_ratelimit_interval_ms, s_warning_ratelimit_state.interval);
EXT4_RW_ATTR_SBI_PI(warning_ratelimit_burst, s_warning_ratelimit_state.burst);
EXT4_RW_ATTR_SBI_PI(msg_ratelimit_interval_ms, s_msg_ratelimit_state.interval);
EXT4_RW_ATTR_SBI_PI(msg_ratelimit_burst, s_msg_ratelimit_state.burst);
#ifdef CONFIG_EXT4_DEBUG
EXT4_RW_ATTR_SBI_UL(simulate_fail, s_simulate_fail);
#endif
EXT4_RO_ATTR_SBI_ATOMIC(warning_count, s_warning_count);
EXT4_RO_ATTR_SBI_ATOMIC(msg_count, s_msg_count);
EXT4_RO_ATTR_ES_UI(errors_count, s_error_count);
EXT4_RO_ATTR_ES_U8(first_error_errcode, s_first_error_errcode);
EXT4_RO_ATTR_ES_U8(last_error_errcode, s_last_error_errcode);
EXT4_RO_ATTR_ES_UI(first_error_ino, s_first_error_ino);
EXT4_RO_ATTR_ES_UI(last_error_ino, s_last_error_ino);
EXT4_RO_ATTR_ES_U64(first_error_block, s_first_error_block);
EXT4_RO_ATTR_ES_U64(last_error_block, s_last_error_block);
EXT4_RO_ATTR_ES_UI(first_error_line, s_first_error_line);
EXT4_RO_ATTR_ES_UI(last_error_line, s_last_error_line);
EXT4_RO_ATTR_ES_STRING(first_error_func, s_first_error_func, 32);
EXT4_RO_ATTR_ES_STRING(last_error_func, s_last_error_func, 32);
EXT4_ATTR(first_error_time, 0444, first_error_time);
EXT4_ATTR(last_error_time, 0444, last_error_time);
EXT4_ATTR(journal_task, 0444, journal_task);
EXT4_RW_ATTR_SBI_UI(mb_prefetch, s_mb_prefetch);
EXT4_RW_ATTR_SBI_UI(mb_prefetch_limit, s_mb_prefetch_limit);
EXT4_RW_ATTR_SBI_UL(last_trim_minblks, s_last_trim_minblks);
EXT4_RW_ATTR_SBI_UI(sb_update_sec, s_sb_update_sec);
EXT4_RW_ATTR_SBI_UI(sb_update_kb, s_sb_update_kb);
static unsigned int old_bump_val = 128;
EXT4_ATTR_PTR(max_writeback_mb_bump, 0444, pointer_ui, &old_bump_val);
static struct attribute *ext4_attrs[] = {
ATTR_LIST(delayed_allocation_blocks),
ATTR_LIST(session_write_kbytes),
ATTR_LIST(lifetime_write_kbytes),
ATTR_LIST(reserved_clusters),
ATTR_LIST(sra_exceeded_retry_limit),
ATTR_LIST(inode_readahead_blks),
ATTR_LIST(inode_goal),
ATTR_LIST(mb_stats),
ATTR_LIST(mb_max_to_scan),
ATTR_LIST(mb_min_to_scan),
ATTR_LIST(mb_order2_req),
ATTR_LIST(mb_stream_req),
ATTR_LIST(mb_group_prealloc),
ATTR_LIST(mb_max_linear_groups),
ATTR_LIST(max_writeback_mb_bump),
ATTR_LIST(extent_max_zeroout_kb),
ATTR_LIST(trigger_fs_error),
ATTR_LIST(err_ratelimit_interval_ms),
ATTR_LIST(err_ratelimit_burst),
ATTR_LIST(warning_ratelimit_interval_ms),
ATTR_LIST(warning_ratelimit_burst),
ATTR_LIST(msg_ratelimit_interval_ms),
ATTR_LIST(msg_ratelimit_burst),
ATTR_LIST(mb_best_avail_max_trim_order),
ATTR_LIST(errors_count),
ATTR_LIST(warning_count),
ATTR_LIST(msg_count),
ATTR_LIST(first_error_ino),
ATTR_LIST(last_error_ino),
ATTR_LIST(first_error_block),
ATTR_LIST(last_error_block),
ATTR_LIST(first_error_line),
ATTR_LIST(last_error_line),
ATTR_LIST(first_error_func),
ATTR_LIST(last_error_func),
ATTR_LIST(first_error_errcode),
ATTR_LIST(last_error_errcode),
ATTR_LIST(first_error_time),
ATTR_LIST(last_error_time),
ATTR_LIST(journal_task),
#ifdef CONFIG_EXT4_DEBUG
ATTR_LIST(simulate_fail),
#endif
ATTR_LIST(mb_prefetch),
ATTR_LIST(mb_prefetch_limit),
ATTR_LIST(last_trim_minblks),
ATTR_LIST(sb_update_sec),
ATTR_LIST(sb_update_kb),
ATTR_LIST(err_report_sec),
NULL,
};
ATTRIBUTE_GROUPS(ext4);
/* Features this copy of ext4 supports */
EXT4_ATTR_FEATURE(lazy_itable_init);
EXT4_ATTR_FEATURE(batched_discard);
EXT4_ATTR_FEATURE(meta_bg_resize);
#ifdef CONFIG_FS_ENCRYPTION
EXT4_ATTR_FEATURE(encryption);
EXT4_ATTR_FEATURE(test_dummy_encryption_v2);
#endif
#if IS_ENABLED(CONFIG_UNICODE)
EXT4_ATTR_FEATURE(casefold);
#endif
#ifdef CONFIG_FS_VERITY
EXT4_ATTR_FEATURE(verity);
#endif
EXT4_ATTR_FEATURE(metadata_csum_seed);
EXT4_ATTR_FEATURE(fast_commit);
#if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
EXT4_ATTR_FEATURE(encrypted_casefold);
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
EXT4_ATTR_FEATURE(blocksize_gt_pagesize);
#endif
static struct attribute *ext4_feat_attrs[] = {
ATTR_LIST(lazy_itable_init),
ATTR_LIST(batched_discard),
ATTR_LIST(meta_bg_resize),
#ifdef CONFIG_FS_ENCRYPTION
ATTR_LIST(encryption),
ATTR_LIST(test_dummy_encryption_v2),
#endif
#if IS_ENABLED(CONFIG_UNICODE)
ATTR_LIST(casefold),
#endif
#ifdef CONFIG_FS_VERITY
ATTR_LIST(verity),
#endif
ATTR_LIST(metadata_csum_seed),
ATTR_LIST(fast_commit),
#if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
ATTR_LIST(encrypted_casefold),
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
ATTR_LIST(blocksize_gt_pagesize),
#endif
NULL,
};
ATTRIBUTE_GROUPS(ext4_feat);
static void *calc_ptr(struct ext4_attr *a, struct ext4_sb_info *sbi)
{
switch (a->attr_ptr) {
case ptr_explicit:
return a->u.explicit_ptr;
case ptr_ext4_sb_info_offset:
return (void *) (((char *) sbi) + a->u.offset);
case ptr_ext4_super_block_offset:
return (void *) (((char *) sbi->s_es) + a->u.offset);
}
return NULL;
}
static ssize_t __print_tstamp(char *buf, __le32 lo, __u8 hi)
{
return sysfs_emit(buf, "%lld\n",
((time64_t)hi << 32) + le32_to_cpu(lo));
}
#define print_tstamp(buf, es, tstamp) \
__print_tstamp(buf, (es)->tstamp, (es)->tstamp ## _hi)
static ssize_t ext4_generic_attr_show(struct ext4_attr *a,
struct ext4_sb_info *sbi, char *buf)
{
void *ptr = calc_ptr(a, sbi);
if (!ptr)
return 0;
switch (a->attr_id) {
case attr_inode_readahead:
case attr_clusters_in_group:
case attr_mb_order:
case attr_pointer_pi:
case attr_pointer_ui:
if (a->attr_ptr == ptr_ext4_super_block_offset)
return sysfs_emit(buf, "%u\n", le32_to_cpup(ptr));
return sysfs_emit(buf, "%u\n", *((unsigned int *) ptr));
case attr_pointer_ul:
case attr_err_report_sec:
return sysfs_emit(buf, "%lu\n", *((unsigned long *) ptr));
case attr_pointer_u8:
return sysfs_emit(buf, "%u\n", *((unsigned char *) ptr));
case attr_pointer_u64:
if (a->attr_ptr == ptr_ext4_super_block_offset)
return sysfs_emit(buf, "%llu\n", le64_to_cpup(ptr));
return sysfs_emit(buf, "%llu\n", *((unsigned long long *) ptr));
case attr_pointer_string:
return sysfs_emit(buf, "%.*s\n", a->attr_size, (char *) ptr);
case attr_pointer_atomic:
return sysfs_emit(buf, "%d\n", atomic_read((atomic_t *) ptr));
}
return 0;
}
static ssize_t ext4_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct ext4_sb_info *sbi = container_of(kobj, struct ext4_sb_info,
s_kobj);
struct ext4_attr *a = container_of(attr, struct ext4_attr, attr);
switch (a->attr_id) {
case attr_delayed_allocation_blocks:
return sysfs_emit(buf, "%llu\n",
(s64) EXT4_C2B(sbi,
percpu_counter_sum(&sbi->s_dirtyclusters_counter)));
case attr_session_write_kbytes:
return session_write_kbytes_show(sbi, buf);
case attr_lifetime_write_kbytes:
return lifetime_write_kbytes_show(sbi, buf);
case attr_reserved_clusters:
return sysfs_emit(buf, "%llu\n",
(unsigned long long)
atomic64_read(&sbi->s_resv_clusters));
case attr_sra_exceeded_retry_limit:
return sysfs_emit(buf, "%llu\n",
(unsigned long long)
percpu_counter_sum(&sbi->s_sra_exceeded_retry_limit));
case attr_feature:
return sysfs_emit(buf, "supported\n");
case attr_first_error_time:
return print_tstamp(buf, sbi->s_es, s_first_error_time);
case attr_last_error_time:
return print_tstamp(buf, sbi->s_es, s_last_error_time);
case attr_journal_task:
return journal_task_show(sbi, buf);
default:
return ext4_generic_attr_show(a, sbi, buf);
}
}
static ssize_t ext4_generic_attr_store(struct ext4_attr *a,
struct ext4_sb_info *sbi,
const char *buf, size_t len)
{
int ret;
unsigned int t;
unsigned long lt;
void *ptr = calc_ptr(a, sbi);
if (!ptr)
return 0;
switch (a->attr_id) {
case attr_pointer_pi:
ret = kstrtouint(skip_spaces(buf), 0, &t);
if (ret)
return ret;
if ((int)t < 0)
return -EINVAL;
*((unsigned int *) ptr) = t;
return len;
case attr_pointer_ui:
ret = kstrtouint(skip_spaces(buf), 0, &t);
if (ret)
return ret;
if (a->attr_ptr == ptr_ext4_super_block_offset)
*((__le32 *) ptr) = cpu_to_le32(t);
else
*((unsigned int *) ptr) = t;
return len;
case attr_mb_order:
ret = kstrtouint(skip_spaces(buf), 0, &t);
if (ret)
return ret;
if (t > 64)
return -EINVAL;
*((unsigned int *) ptr) = t;
return len;
case attr_clusters_in_group:
ret = kstrtouint(skip_spaces(buf), 0, &t);
if (ret)
return ret;
if (t > sbi->s_clusters_per_group)
return -EINVAL;
*((unsigned int *) ptr) = t;
return len;
case attr_pointer_ul:
ret = kstrtoul(skip_spaces(buf), 0, &lt);
if (ret)
return ret;
*((unsigned long *) ptr) = lt;
return len;
}
return 0;
}
static ssize_t ext4_attr_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t len)
{
struct ext4_sb_info *sbi = container_of(kobj, struct ext4_sb_info,
s_kobj);
struct ext4_attr *a = container_of(attr, struct ext4_attr, attr);
switch (a->attr_id) {
case attr_reserved_clusters:
return reserved_clusters_store(sbi, buf, len);
case attr_inode_readahead:
return inode_readahead_blks_store(sbi, buf, len);
case attr_trigger_test_error:
return trigger_test_error(sbi, buf, len);
case attr_err_report_sec:
return err_report_sec_store(sbi, buf, len);
default:
return ext4_generic_attr_store(a, sbi, buf, len);
}
}
static void ext4_sb_release(struct kobject *kobj)
{
struct ext4_sb_info *sbi = container_of(kobj, struct ext4_sb_info,
s_kobj);
complete(&sbi->s_kobj_unregister);
}
static void ext4_feat_release(struct kobject *kobj)
{
kfree(kobj);
}
static const struct sysfs_ops ext4_attr_ops = {
.show = ext4_attr_show,
.store = ext4_attr_store,
};
static const struct kobj_type ext4_sb_ktype = {
.default_groups = ext4_groups,
.sysfs_ops = &ext4_attr_ops,
.release = ext4_sb_release,
};
static const struct kobj_type ext4_feat_ktype = {
.default_groups = ext4_feat_groups,
.sysfs_ops = &ext4_attr_ops,
.release = ext4_feat_release,
};
void ext4_notify_error_sysfs(struct ext4_sb_info *sbi)
{
mutex_lock(&sbi->s_error_notify_mutex);
if (sbi->s_kobj.state_in_sysfs)
sysfs_notify(&sbi->s_kobj, NULL, "errors_count");
mutex_unlock(&sbi->s_error_notify_mutex);
}
static struct kobject *ext4_root;
static struct kobject *ext4_feat;
int ext4_register_sysfs(struct super_block *sb)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
int err;
init_completion(&sbi->s_kobj_unregister);
mutex_lock(&sbi->s_error_notify_mutex);
err = kobject_init_and_add(&sbi->s_kobj, &ext4_sb_ktype, ext4_root,
"%s", sb->s_id);
mutex_unlock(&sbi->s_error_notify_mutex);
if (err) {
kobject_put(&sbi->s_kobj);
wait_for_completion(&sbi->s_kobj_unregister);
return err;
}
if (ext4_proc_root)
sbi->s_proc = proc_mkdir(sb->s_id, ext4_proc_root);
if (sbi->s_proc) {
proc_create_single_data("options", S_IRUGO, sbi->s_proc,
ext4_seq_options_show, sb);
proc_create_single_data("es_shrinker_info", S_IRUGO,
sbi->s_proc, ext4_seq_es_shrinker_info_show,
sb);
proc_create_single_data("fc_info", 0444, sbi->s_proc,
ext4_fc_info_show, sb);
proc_create_seq_data("mb_groups", S_IRUGO, sbi->s_proc,
&ext4_mb_seq_groups_ops, sb);
proc_create_single_data("mb_stats", 0444, sbi->s_proc,
ext4_seq_mb_stats_show, sb);
proc_create_seq_data("mb_structs_summary", 0444, sbi->s_proc,
&ext4_mb_seq_structs_summary_ops, sb);
}
return 0;
}
void ext4_unregister_sysfs(struct super_block *sb)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
if (sbi->s_proc)
remove_proc_subtree(sb->s_id, ext4_proc_root);
mutex_lock(&sbi->s_error_notify_mutex);
kobject_del(&sbi->s_kobj);
mutex_unlock(&sbi->s_error_notify_mutex);
}
int __init ext4_init_sysfs(void)
{
int ret;
ext4_root = kobject_create_and_add("ext4", fs_kobj);
if (!ext4_root)
return -ENOMEM;
ext4_feat = kzalloc_obj(*ext4_feat);
if (!ext4_feat) {
ret = -ENOMEM;
goto root_err;
}
ret = kobject_init_and_add(ext4_feat, &ext4_feat_ktype,
ext4_root, "features");
if (ret)
goto feat_err;
ext4_proc_root = proc_mkdir(proc_dirname, NULL);
return ret;
feat_err:
kobject_put(ext4_feat);
ext4_feat = NULL;
root_err:
kobject_put(ext4_root);
ext4_root = NULL;
return ret;
}
void ext4_exit_sysfs(void)
{
kobject_put(ext4_feat);
ext4_feat = NULL;
kobject_put(ext4_root);
ext4_root = NULL;
remove_proc_entry(proc_dirname, NULL);
ext4_proc_root = NULL;
}
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/truncate.h
*
* Common inline functions needed for truncate support
*/
/*
* Truncate blocks that were not used by write. We have to truncate the
* pagecache as well so that corresponding buffers get properly unmapped.
*/
static inline void ext4_truncate_failed_write(struct inode *inode)
{
struct address_space *mapping = inode->i_mapping;
/*
* We don't need to call ext4_break_layouts() because the blocks we
* are truncating were never visible to userspace.
*/
filemap_invalidate_lock(mapping);
truncate_inode_pages(mapping, inode->i_size);
ext4_truncate(inode);
filemap_invalidate_unlock(mapping);
}
/*
* Work out how many blocks we need to proceed with the next chunk of a
* truncate transaction.
*/
static inline unsigned long ext4_blocks_for_truncate(struct inode *inode)
{
ext4_lblk_t needed;
needed = inode->i_blocks >> (inode->i_sb->s_blocksize_bits - 9);
/* Give ourselves just enough room to cope with inodes in which
* i_blocks is corrupt: we've seen disk corruptions in the past
* which resulted in random data in an inode which looked enough
* like a regular file for ext4 to try to delete it. Things
* will go a bit crazy if that happens, but at least we should
* try not to panic the whole kernel. */
if (needed < 2)
needed = 2;
/* But we need to bound the transaction so we don't overflow the
* journal. */
if (needed > EXT4_MAX_TRANS_DATA)
needed = EXT4_MAX_TRANS_DATA;
return EXT4_DATA_TRANS_BLOCKS(inode->i_sb) + needed;
}
+391
View File
@@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0
/*
* fs/ext4/verity.c: fs-verity support for ext4
*
* Copyright 2019 Google LLC
*/
/*
* Implementation of fsverity_operations for ext4.
*
* ext4 stores the verity metadata (Merkle tree and fsverity_descriptor) past
* the end of the file, starting at the first 64K boundary beyond i_size. This
* approach works because (a) verity files are readonly, and (b) pages fully
* beyond i_size aren't visible to userspace but can be read/written internally
* by ext4 with only some relatively small changes to ext4. This approach
* avoids having to depend on the EA_INODE feature and on rearchitecturing
* ext4's xattr support to support paging multi-gigabyte xattrs into memory, and
* to support encrypting xattrs. Note that the verity metadata *must* be
* encrypted when the file is, since it contains hashes of the plaintext data.
*
* Using a 64K boundary rather than a 4K one keeps things ready for
* architectures with 64K pages, and it doesn't necessarily waste space on-disk
* since there can be a hole between i_size and the start of the Merkle tree.
*/
#include <linux/quotaops.h>
#include "ext4.h"
#include "ext4_extents.h"
#include "ext4_jbd2.h"
static inline loff_t ext4_verity_metadata_pos(const struct inode *inode)
{
return round_up(inode->i_size, 65536);
}
/*
* Read some verity metadata from the inode. __vfs_read() can't be used because
* we need to read beyond i_size.
*/
static int pagecache_read(struct inode *inode, void *buf, size_t count,
loff_t pos)
{
while (count) {
struct folio *folio;
size_t n;
folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT,
NULL);
if (IS_ERR(folio))
return PTR_ERR(folio);
n = memcpy_from_file_folio(buf, folio, pos, count);
folio_put(folio);
buf += n;
pos += n;
count -= n;
}
return 0;
}
/*
* Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
* kernel_write() can't be used because the file descriptor is readonly.
*/
static int pagecache_write(struct inode *inode, const void *buf, size_t count,
loff_t pos)
{
struct address_space *mapping = inode->i_mapping;
const struct address_space_operations *aops = mapping->a_ops;
if (pos + count > inode->i_sb->s_maxbytes)
return -EFBIG;
while (count) {
size_t n = min_t(size_t, count,
PAGE_SIZE - offset_in_page(pos));
struct folio *folio;
void *fsdata = NULL;
int res;
res = aops->write_begin(NULL, mapping, pos, n, &folio, &fsdata);
if (res)
return res;
memcpy_to_folio(folio, offset_in_folio(folio, pos), buf, n);
res = aops->write_end(NULL, mapping, pos, n, n, folio, fsdata);
if (res < 0)
return res;
if (res != n)
return -EIO;
buf += n;
pos += n;
count -= n;
}
return 0;
}
static int ext4_begin_enable_verity(struct file *filp)
{
struct inode *inode = file_inode(filp);
const int credits = 2; /* superblock and inode for ext4_orphan_add() */
handle_t *handle;
int err;
if (IS_DAX(inode) || ext4_test_inode_flag(inode, EXT4_INODE_DAX))
return -EINVAL;
if (ext4_verity_in_progress(inode))
return -EBUSY;
/*
* Since the file was opened readonly, we have to initialize the jbd
* inode and quotas here and not rely on ->open() doing it. This must
* be done before evicting the inline data.
*/
err = ext4_inode_attach_jinode(inode);
if (err)
return err;
err = dquot_initialize(inode);
if (err)
return err;
err = ext4_convert_inline_data(inode);
if (err)
return err;
if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
ext4_warning_inode(inode,
"verity is only allowed on extent-based files");
return -EOPNOTSUPP;
}
/*
* ext4 uses the last allocated block to find the verity descriptor, so
* we must remove any other blocks past EOF which might confuse things.
*/
err = ext4_truncate(inode);
if (err)
return err;
handle = ext4_journal_start(inode, EXT4_HT_INODE, credits);
if (IS_ERR(handle))
return PTR_ERR(handle);
err = ext4_orphan_add(handle, inode);
if (err == 0)
ext4_set_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
ext4_journal_stop(handle);
return err;
}
/*
* ext4 stores the verity descriptor beginning on the next filesystem block
* boundary after the Merkle tree. Then, the descriptor size is stored in the
* last 4 bytes of the last allocated filesystem block --- which is either the
* block in which the descriptor ends, or the next block after that if there
* weren't at least 4 bytes remaining.
*
* We can't simply store the descriptor in an xattr because it *must* be
* encrypted when ext4 encryption is used, but ext4 encryption doesn't encrypt
* xattrs. Also, if the descriptor includes a large signature blob it may be
* too large to store in an xattr without the EA_INODE feature.
*/
static int ext4_write_verity_descriptor(struct inode *inode, const void *desc,
size_t desc_size, u64 merkle_tree_size)
{
const u64 desc_pos = round_up(ext4_verity_metadata_pos(inode) +
merkle_tree_size, i_blocksize(inode));
const u64 desc_end = desc_pos + desc_size;
const __le32 desc_size_disk = cpu_to_le32(desc_size);
const u64 desc_size_pos = round_up(desc_end + sizeof(desc_size_disk),
i_blocksize(inode)) -
sizeof(desc_size_disk);
int err;
err = pagecache_write(inode, desc, desc_size, desc_pos);
if (err)
return err;
return pagecache_write(inode, &desc_size_disk, sizeof(desc_size_disk),
desc_size_pos);
}
static int ext4_end_enable_verity(struct file *filp, const void *desc,
size_t desc_size, u64 merkle_tree_size)
{
struct inode *inode = file_inode(filp);
const int credits = 2; /* superblock and inode for ext4_orphan_del() */
handle_t *handle;
struct ext4_iloc iloc;
int err = 0;
/*
* If an error already occurred (which fs/verity/ signals by passing
* desc == NULL), then only clean-up is needed.
*/
if (desc == NULL)
goto cleanup;
/* Append the verity descriptor. */
err = ext4_write_verity_descriptor(inode, desc, desc_size,
merkle_tree_size);
if (err)
goto cleanup;
/*
* Write all pages (both data and verity metadata). Note that this must
* happen before clearing EXT4_STATE_VERITY_IN_PROGRESS; otherwise pages
* beyond i_size won't be written properly. For crash consistency, this
* also must happen before the verity inode flag gets persisted.
*/
err = filemap_write_and_wait(inode->i_mapping);
if (err)
goto cleanup;
/*
* Finally, set the verity inode flag and remove the inode from the
* orphan list (in a single transaction).
*/
handle = ext4_journal_start(inode, EXT4_HT_INODE, credits);
if (IS_ERR(handle)) {
err = PTR_ERR(handle);
goto cleanup;
}
ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_VERITY, handle);
err = ext4_orphan_del(handle, inode);
if (err)
goto stop_and_cleanup;
err = ext4_reserve_inode_write(handle, inode, &iloc);
if (err)
goto stop_and_cleanup;
ext4_set_inode_flag(inode, EXT4_INODE_VERITY);
ext4_set_inode_flags(inode, false);
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
if (err)
goto stop_and_cleanup;
ext4_journal_stop(handle);
ext4_clear_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
return 0;
stop_and_cleanup:
ext4_journal_stop(handle);
cleanup:
/*
* Verity failed to be enabled, so clean up by truncating any verity
* metadata that was written beyond i_size (both from cache and from
* disk), removing the inode from the orphan list (if it wasn't done
* already), and clearing EXT4_STATE_VERITY_IN_PROGRESS.
*/
truncate_inode_pages(inode->i_mapping, inode->i_size);
ext4_truncate(inode);
ext4_orphan_del(NULL, inode);
ext4_clear_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
return err;
}
static int ext4_get_verity_descriptor_location(struct inode *inode,
size_t *desc_size_ret,
u64 *desc_pos_ret)
{
struct ext4_ext_path *path;
struct ext4_extent *last_extent;
u32 end_lblk;
u64 desc_size_pos;
__le32 desc_size_disk;
u32 desc_size;
u64 desc_pos;
int err;
/*
* Descriptor size is in last 4 bytes of last allocated block.
* See ext4_write_verity_descriptor().
*/
if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
EXT4_ERROR_INODE(inode, "verity file doesn't use extents");
return -EFSCORRUPTED;
}
path = ext4_find_extent(inode, EXT_MAX_BLOCKS - 1, NULL, 0);
if (IS_ERR(path))
return PTR_ERR(path);
last_extent = path[path->p_depth].p_ext;
if (!last_extent) {
EXT4_ERROR_INODE(inode, "verity file has no extents");
ext4_free_ext_path(path);
return -EFSCORRUPTED;
}
end_lblk = le32_to_cpu(last_extent->ee_block) +
ext4_ext_get_actual_len(last_extent);
desc_size_pos = EXT4_LBLK_TO_B(inode, end_lblk);
ext4_free_ext_path(path);
if (desc_size_pos < sizeof(desc_size_disk))
goto bad;
desc_size_pos -= sizeof(desc_size_disk);
err = pagecache_read(inode, &desc_size_disk, sizeof(desc_size_disk),
desc_size_pos);
if (err)
return err;
desc_size = le32_to_cpu(desc_size_disk);
/*
* The descriptor is stored just before the desc_size_disk, but starting
* on a filesystem block boundary.
*/
if (desc_size > INT_MAX || desc_size > desc_size_pos)
goto bad;
desc_pos = round_down(desc_size_pos - desc_size, i_blocksize(inode));
if (desc_pos < ext4_verity_metadata_pos(inode))
goto bad;
*desc_size_ret = desc_size;
*desc_pos_ret = desc_pos;
return 0;
bad:
EXT4_ERROR_INODE(inode, "verity file corrupted; can't find descriptor");
return -EFSCORRUPTED;
}
static int ext4_get_verity_descriptor(struct inode *inode, void *buf,
size_t buf_size)
{
size_t desc_size = 0;
u64 desc_pos = 0;
int err;
err = ext4_get_verity_descriptor_location(inode, &desc_size, &desc_pos);
if (err)
return err;
if (buf_size) {
if (desc_size > buf_size)
return -ERANGE;
err = pagecache_read(inode, buf, desc_size, desc_pos);
if (err)
return err;
}
return desc_size;
}
static struct page *ext4_read_merkle_tree_page(struct inode *inode,
pgoff_t index)
{
index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
return generic_read_merkle_tree_page(inode, index);
}
static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,
unsigned long nr_pages)
{
index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
generic_readahead_merkle_tree(inode, index, nr_pages);
}
static int ext4_write_merkle_tree_block(struct file *file, const void *buf,
u64 pos, unsigned int size)
{
pos += ext4_verity_metadata_pos(file_inode(file));
return pagecache_write(file_inode(file), buf, size, pos);
}
const struct fsverity_operations ext4_verityops = {
.begin_enable_verity = ext4_begin_enable_verity,
.end_enable_verity = ext4_end_enable_verity,
.get_verity_descriptor = ext4_get_verity_descriptor,
.read_merkle_tree_page = ext4_read_merkle_tree_page,
.readahead_merkle_tree = ext4_readahead_merkle_tree,
.write_merkle_tree_block = ext4_write_merkle_tree_block,
};
File diff suppressed because it is too large Load Diff
+236
View File
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0
/*
File: fs/ext4/xattr.h
On-disk format of extended attributes for the ext4 filesystem.
(C) 2001 Andreas Gruenbacher, <a.gruenbacher@computer.org>
*/
#include <linux/xattr.h>
/* Magic value in attribute blocks */
#define EXT4_XATTR_MAGIC 0xEA020000
/* Maximum number of references to one attribute block */
#define EXT4_XATTR_REFCOUNT_MAX 1024
/* Name indexes */
#define EXT4_XATTR_INDEX_USER 1
#define EXT4_XATTR_INDEX_POSIX_ACL_ACCESS 2
#define EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT 3
#define EXT4_XATTR_INDEX_TRUSTED 4
#define EXT4_XATTR_INDEX_LUSTRE 5
#define EXT4_XATTR_INDEX_SECURITY 6
#define EXT4_XATTR_INDEX_SYSTEM 7
#define EXT4_XATTR_INDEX_RICHACL 8
#define EXT4_XATTR_INDEX_ENCRYPTION 9
#define EXT4_XATTR_INDEX_HURD 10 /* Reserved for Hurd */
struct ext4_xattr_header {
__le32 h_magic; /* magic number for identification */
__le32 h_refcount; /* reference count */
__le32 h_blocks; /* number of disk blocks used */
__le32 h_hash; /* hash value of all attributes */
__le32 h_checksum; /* crc32c(uuid+blknum+xattrblock) */
__u32 h_reserved[3]; /* zero right now */
};
struct ext4_xattr_ibody_header {
__le32 h_magic; /* magic number for identification */
};
struct ext4_xattr_entry {
__u8 e_name_len; /* length of name */
__u8 e_name_index; /* attribute name index */
__le16 e_value_offs; /* offset in disk block of value */
__le32 e_value_inum; /* inode in which the value is stored */
__le32 e_value_size; /* size of attribute value */
__le32 e_hash; /* hash value of name and value */
char e_name[]; /* attribute name */
};
#define EXT4_XATTR_PAD_BITS 2
#define EXT4_XATTR_PAD (1<<EXT4_XATTR_PAD_BITS)
#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD-1)
#define EXT4_XATTR_LEN(name_len) \
(((name_len) + EXT4_XATTR_ROUND + \
sizeof(struct ext4_xattr_entry)) & ~EXT4_XATTR_ROUND)
#define EXT4_XATTR_NEXT(entry) \
((struct ext4_xattr_entry *)( \
(char *)(entry) + EXT4_XATTR_LEN((entry)->e_name_len)))
#define EXT4_XATTR_SIZE(size) \
(((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND)
#define IHDR(inode, raw_inode) \
((struct ext4_xattr_ibody_header *) \
((void *)raw_inode + \
EXT4_GOOD_OLD_INODE_SIZE + \
EXT4_I(inode)->i_extra_isize))
#define ITAIL(inode, raw_inode) \
((void *)(raw_inode) + \
EXT4_SB((inode)->i_sb)->s_inode_size)
#define IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr)+1))
/*
* XATTR_SIZE_MAX is currently 64k, but for the purposes of checking
* for file system consistency errors, we use a somewhat bigger value.
* This allows XATTR_SIZE_MAX to grow in the future, but by using this
* instead of INT_MAX for certain consistency checks, we don't need to
* worry about arithmetic overflows. (Actually XATTR_SIZE_MAX is
* defined in include/uapi/linux/limits.h, so changing it is going
* not going to be trivial....)
*/
#define EXT4_XATTR_SIZE_MAX (1 << 24)
/*
* The minimum size of EA value when you start storing it in an external inode
* size of block - size of header - size of 1 entry - 4 null bytes
*/
#define EXT4_XATTR_MIN_LARGE_EA_SIZE(b) \
((b) - EXT4_XATTR_LEN(3) - sizeof(struct ext4_xattr_header) - 4)
#define BHDR(bh) ((struct ext4_xattr_header *)((bh)->b_data))
#define ENTRY(ptr) ((struct ext4_xattr_entry *)(ptr))
#define BFIRST(bh) ENTRY(BHDR(bh)+1)
#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
#define EXT4_ZERO_XATTR_VALUE ((void *)-1)
/*
* If we want to add an xattr to the inode, we should make sure that
* i_extra_isize is not 0 and that the inode size is not less than
* EXT4_GOOD_OLD_INODE_SIZE + extra_isize + pad.
* EXT4_GOOD_OLD_INODE_SIZE extra_isize header entry pad data
* |--------------------------|------------|------|---------|---|-------|
*/
#define EXT4_INODE_HAS_XATTR_SPACE(inode) \
((EXT4_I(inode)->i_extra_isize != 0) && \
(EXT4_GOOD_OLD_INODE_SIZE + EXT4_I(inode)->i_extra_isize + \
sizeof(struct ext4_xattr_ibody_header) + EXT4_XATTR_PAD <= \
EXT4_INODE_SIZE((inode)->i_sb)))
struct ext4_xattr_info {
const char *name;
const void *value;
size_t value_len;
int name_index;
int in_inode;
};
struct ext4_xattr_search {
struct ext4_xattr_entry *first;
void *base;
void *end;
struct ext4_xattr_entry *here;
int not_found;
};
struct ext4_xattr_ibody_find {
struct ext4_xattr_search s;
struct ext4_iloc iloc;
};
struct ext4_xattr_inode_array {
unsigned int count;
struct inode *inodes[] __counted_by(count);
};
extern const struct xattr_handler ext4_xattr_user_handler;
extern const struct xattr_handler ext4_xattr_trusted_handler;
extern const struct xattr_handler ext4_xattr_security_handler;
extern const struct xattr_handler ext4_xattr_hurd_handler;
#define EXT4_XATTR_NAME_ENCRYPTION_CONTEXT "c"
/*
* The EXT4_STATE_NO_EXPAND is overloaded and used for two purposes.
* The first is to signal that there the inline xattrs and data are
* taking up so much space that we might as well not keep trying to
* expand it. The second is that xattr_sem is taken for writing, so
* we shouldn't try to recurse into the inode expansion. For this
* second case, we need to make sure that we take save and restore the
* NO_EXPAND state flag appropriately.
*/
static inline void ext4_write_lock_xattr(struct inode *inode, int *save)
{
down_write(&EXT4_I(inode)->xattr_sem);
*save = ext4_test_inode_state(inode, EXT4_STATE_NO_EXPAND);
ext4_set_inode_state(inode, EXT4_STATE_NO_EXPAND);
}
static inline int ext4_write_trylock_xattr(struct inode *inode, int *save)
{
if (down_write_trylock(&EXT4_I(inode)->xattr_sem) == 0)
return 0;
*save = ext4_test_inode_state(inode, EXT4_STATE_NO_EXPAND);
ext4_set_inode_state(inode, EXT4_STATE_NO_EXPAND);
return 1;
}
static inline void ext4_write_unlock_xattr(struct inode *inode, int *save)
{
if (*save == 0)
ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
up_write(&EXT4_I(inode)->xattr_sem);
}
extern ssize_t ext4_listxattr(struct dentry *, char *, size_t);
extern int ext4_xattr_get(struct inode *, int, const char *, void *, size_t);
extern int ext4_xattr_set(struct inode *, int, const char *, const void *, size_t, int);
extern int ext4_xattr_set_handle(handle_t *, struct inode *, int, const char *, const void *, size_t, int);
extern int ext4_xattr_set_credits(struct inode *inode, size_t value_len,
bool is_create, int *credits);
extern int __ext4_xattr_set_credits(struct super_block *sb, struct inode *inode,
struct buffer_head *block_bh, size_t value_len,
bool is_create);
extern int ext4_xattr_delete_inode(handle_t *handle, struct inode *inode,
struct ext4_xattr_inode_array **array,
int extra_credits);
extern void ext4_xattr_inode_array_free(struct ext4_xattr_inode_array *array);
extern int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
struct ext4_inode *raw_inode, handle_t *handle);
extern void ext4_evict_ea_inode(struct inode *inode);
extern const struct xattr_handler * const ext4_xattr_handlers[];
extern int ext4_xattr_ibody_find(struct inode *inode, struct ext4_xattr_info *i,
struct ext4_xattr_ibody_find *is);
extern int ext4_xattr_ibody_get(struct inode *inode, int name_index,
const char *name,
void *buffer, size_t buffer_size);
extern int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode,
struct ext4_xattr_info *i,
struct ext4_xattr_ibody_find *is);
extern struct mb_cache *ext4_xattr_create_cache(void);
extern void ext4_xattr_destroy_cache(struct mb_cache *);
extern int
__xattr_check_inode(struct inode *inode, struct ext4_xattr_ibody_header *header,
void *end, const char *function, unsigned int line);
#define xattr_check_inode(inode, header, end) \
__xattr_check_inode((inode), (header), (end), __func__, __LINE__)
#ifdef CONFIG_EXT4_FS_SECURITY
extern int ext4_init_security(handle_t *handle, struct inode *inode,
struct inode *dir, const struct qstr *qstr);
#else
static inline int ext4_init_security(handle_t *handle, struct inode *inode,
struct inode *dir, const struct qstr *qstr)
{
return 0;
}
#endif
#ifdef CONFIG_LOCKDEP
extern void ext4_xattr_inode_set_class(struct inode *ea_inode);
#else
static inline void ext4_xattr_inode_set_class(struct inode *ea_inode) { }
#endif
extern int ext4_get_inode_usage(struct inode *inode, qsize_t *usage);
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/xattr_hurd.c
* Handler for extended gnu attributes for the Hurd.
*
* Copyright (C) 2001 by Andreas Gruenbacher, <a.gruenbacher@computer.org>
* Copyright (C) 2020 by Jan (janneke) Nieuwenhuizen, <janneke@gnu.org>
*/
#include <linux/init.h>
#include <linux/string.h>
#include "ext4.h"
#include "xattr.h"
static bool
ext4_xattr_hurd_list(struct dentry *dentry)
{
return test_opt(dentry->d_sb, XATTR_USER);
}
static int
ext4_xattr_hurd_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
if (!test_opt(inode->i_sb, XATTR_USER))
return -EOPNOTSUPP;
return ext4_xattr_get(inode, EXT4_XATTR_INDEX_HURD,
name, buffer, size);
}
static int
ext4_xattr_hurd_set(const struct xattr_handler *handler,
struct mnt_idmap *idmap,
struct dentry *unused, struct inode *inode,
const char *name, const void *value,
size_t size, int flags)
{
if (!test_opt(inode->i_sb, XATTR_USER))
return -EOPNOTSUPP;
return ext4_xattr_set(inode, EXT4_XATTR_INDEX_HURD,
name, value, size, flags);
}
const struct xattr_handler ext4_xattr_hurd_handler = {
.prefix = XATTR_HURD_PREFIX,
.list = ext4_xattr_hurd_list,
.get = ext4_xattr_hurd_get,
.set = ext4_xattr_hurd_set,
};
+66
View File
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/xattr_security.c
* Handler for storing security labels as extended attributes.
*/
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/security.h>
#include <linux/slab.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
static int
ext4_xattr_security_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
return ext4_xattr_get(inode, EXT4_XATTR_INDEX_SECURITY,
name, buffer, size);
}
static int
ext4_xattr_security_set(const struct xattr_handler *handler,
struct mnt_idmap *idmap,
struct dentry *unused, struct inode *inode,
const char *name, const void *value,
size_t size, int flags)
{
return ext4_xattr_set(inode, EXT4_XATTR_INDEX_SECURITY,
name, value, size, flags);
}
static int
ext4_initxattrs(struct inode *inode, const struct xattr *xattr_array,
void *fs_info)
{
const struct xattr *xattr;
handle_t *handle = fs_info;
int err = 0;
for (xattr = xattr_array; xattr->name != NULL; xattr++) {
err = ext4_xattr_set_handle(handle, inode,
EXT4_XATTR_INDEX_SECURITY,
xattr->name, xattr->value,
xattr->value_len, XATTR_CREATE);
if (err < 0)
break;
}
return err;
}
int
ext4_init_security(handle_t *handle, struct inode *inode, struct inode *dir,
const struct qstr *qstr)
{
return security_inode_init_security(inode, dir, qstr,
&ext4_initxattrs, handle);
}
const struct xattr_handler ext4_xattr_security_handler = {
.prefix = XATTR_SECURITY_PREFIX,
.get = ext4_xattr_security_get,
.set = ext4_xattr_security_set,
};
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/xattr_trusted.c
* Handler for trusted extended attributes.
*
* Copyright (C) 2003 by Andreas Gruenbacher, <a.gruenbacher@computer.org>
*/
#include <linux/string.h>
#include <linux/capability.h>
#include <linux/fs.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
static bool
ext4_xattr_trusted_list(struct dentry *dentry)
{
return capable(CAP_SYS_ADMIN);
}
static int
ext4_xattr_trusted_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
return ext4_xattr_get(inode, EXT4_XATTR_INDEX_TRUSTED,
name, buffer, size);
}
static int
ext4_xattr_trusted_set(const struct xattr_handler *handler,
struct mnt_idmap *idmap,
struct dentry *unused, struct inode *inode,
const char *name, const void *value,
size_t size, int flags)
{
return ext4_xattr_set(inode, EXT4_XATTR_INDEX_TRUSTED,
name, value, size, flags);
}
const struct xattr_handler ext4_xattr_trusted_handler = {
.prefix = XATTR_TRUSTED_PREFIX,
.list = ext4_xattr_trusted_list,
.get = ext4_xattr_trusted_get,
.set = ext4_xattr_trusted_set,
};
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext4/xattr_user.c
* Handler for extended user attributes.
*
* Copyright (C) 2001 by Andreas Gruenbacher, <a.gruenbacher@computer.org>
*/
#include <linux/string.h>
#include <linux/fs.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
static bool
ext4_xattr_user_list(struct dentry *dentry)
{
return test_opt(dentry->d_sb, XATTR_USER);
}
static int
ext4_xattr_user_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
if (!test_opt(inode->i_sb, XATTR_USER))
return -EOPNOTSUPP;
return ext4_xattr_get(inode, EXT4_XATTR_INDEX_USER,
name, buffer, size);
}
static int
ext4_xattr_user_set(const struct xattr_handler *handler,
struct mnt_idmap *idmap,
struct dentry *unused, struct inode *inode,
const char *name, const void *value,
size_t size, int flags)
{
if (!test_opt(inode->i_sb, XATTR_USER))
return -EOPNOTSUPP;
return ext4_xattr_set(inode, EXT4_XATTR_INDEX_USER,
name, value, size, flags);
}
const struct xattr_handler ext4_xattr_user_handler = {
.prefix = XATTR_USER_PREFIX,
.list = ext4_xattr_user_list,
.get = ext4_xattr_user_get,
.set = ext4_xattr_user_set,
};