aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-28 10:48:48 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-28 10:48:48 -0700
commit115bd364ab20b2dbef22824ec80d15901847dbe2 (patch)
treedc4e57a6e9047e8efc745b9b737363015ae49099
parentc20313e98b04ce543936431b6122dd639d3a8346 (diff)
parentc966d29e01bbf829f8bb4a39a49811c56cdb49c3 (diff)
Merge tag 'f2fs-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs
Pull f2fs updates from Jaegeuk Kim: "In this round, key enhancements focus on reducing inode management memory overhead, introducing resizable tail sections with unified pinned allocation, and boosting I/O throughput via parallel multi-device flushes and asynchronous f2fs_write_end_io() execution. We also add dynamic device alias reservations to allow on-the-fly space donation from user partitions. Alongside these features, critical bug fixes resolve folio race conditions, lingering dirty flags, dentry and block counter leaks, and potential deadloops in f2fs_fsync_node_pages(). Additional stability patches address error-path handling across symlink, sync, and rename/unlink operations, prevent pinned file fragmentation, and correct segment migration and free section accounting in free_segment_range. Enhancements: - reduce memory footprint of ino management - support dynamic reserve/release for device aliasing - issue multi-device flushes in parallel - add a way to run f2fs_write_end_io() asynchronously - support resizable tail section and unify pinned allocation Bug fixes: - fix to pass folio->index to f2fs_sanity_check_node_footer() - fix folio_nr_pages() race after put in large folio invalidate - fix to clear dirty flag on folio in error path - accurately adjust free_sections during free_segment_range - fix to avoid potential deadloop in f2fs_fsync_node_pages() - fix the error path in symlink, device alias in rename/unlink, f2fs_sync_fs - fix to migrate all curseg types during free_segment_range - fix to avoid pinfile fragment on fragment:{block, segment} mode - fix valid block count leak on data block allocation failure - fix dentry folio leak in find_in_level - reject overlapping move range after len expansion - fix some bugs related to file pinning, GC functions, i_size And, the series includes a number of minor bug fixes" * tag 'f2fs-for-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs: (51 commits) f2fs: support resizable tail section and unify pinned allocation f2fs: don't leave the hashed inode while it's unlinked f2fs: accurately adjust free_sections during free_segment_range f2fs: fix to avoid potential deadloop in f2fs_fsync_node_pages() f2fs: use adjusted write range after f2fs_write_checks() f2fs: fix to propagate error from f2fs_sync_fs() f2fs: return symlink writeback errors f2fs: fix error handling on device alias check in rename and unlink f2fs: fix to reset all pinned status during fggc f2fs: use f2fs_{down, up}_(read, write}_trace() for nat_tree_lock f2fs: reduce memory footprint of ino management f2fs: fix i_size when pinned fallocate partially fails f2fs: fix to migrate all curseg types during free_segment_range f2fs: avoid setting SBI_NEED_FSCK on transient resize failure f2fs: fix to avoid pinfile fragment on fragment:{block, segment} mode f2fs: cleanup w/ f2fs_need_rand_{blk, seg, seg_blk} f2fs: fix to shrink gc_lock coverage in f2fs_gc_range() f2fs: fix to reclaim space in f2fs_allocate_pinning_section() f2fs: unify add/remove ino entry API for all ino types f2fs: fix to zero post-EOF data when extending file size ...
-rw-r--r--Documentation/ABI/testing/sysfs-fs-f2fs18
-rw-r--r--Documentation/filesystems/f2fs.rst43
-rw-r--r--fs/f2fs/checkpoint.c123
-rw-r--r--fs/f2fs/data.c48
-rw-r--r--fs/f2fs/debug.c4
-rw-r--r--fs/f2fs/dir.c7
-rw-r--r--fs/f2fs/extent_cache.c9
-rw-r--r--fs/f2fs/f2fs.h90
-rw-r--r--fs/f2fs/file.c460
-rw-r--r--fs/f2fs/gc.c129
-rw-r--r--fs/f2fs/gc.h27
-rw-r--r--fs/f2fs/inline.c2
-rw-r--r--fs/f2fs/inode.c234
-rw-r--r--fs/f2fs/iostat.c6
-rw-r--r--fs/f2fs/namei.c168
-rw-r--r--fs/f2fs/node.c81
-rw-r--r--fs/f2fs/recovery.c55
-rw-r--r--fs/f2fs/segment.c279
-rw-r--r--fs/f2fs/segment.h37
-rw-r--r--fs/f2fs/shrinker.c3
-rw-r--r--fs/f2fs/super.c99
-rw-r--r--fs/f2fs/sysfs.c34
-rw-r--r--fs/f2fs/xattr.c2
-rw-r--r--include/trace/events/f2fs.h23
-rw-r--r--include/uapi/linux/f2fs.h7
25 files changed, 1532 insertions, 456 deletions
diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 1b58c029abd0..0cebc89799dd 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -986,6 +986,7 @@ Description: This sysfs entry can be used to enable/disable to adjust priority f
0x00000008 gc_lock
0x00000010 cp_global
0x00000020 io_rwsem
+ 0x00000040 nat_tree_lock
========== ==================
What: /sys/fs/f2fs/<disk>/lock_duration_priority
@@ -1002,3 +1003,20 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck
threads, limitation as below:
- it requires user has CAP_SYS_NICE capability.
- the range is [100, 139], by default the value is 120.
+
+What: /sys/fs/f2fs/<disk>/max_atc_write_bio_size
+Date: June 2026
+Contact: Bart Van Assche <bvanassche@acm.org>
+Description: Every time a write operation completes f2fs_write_end_io() is
+ called. This function may be called from an atomic context,
+ e.g. from inside an interrupt handler. This attribute controls
+ the maximum size of a write bio that is completed in atomic
+ (atc) context. The default value for this attribute is UINT_MAX
+ which means that this functionality is disabled by default.
+
+What: /sys/fs/f2fs/<disk>/pinned_area_max_secno
+Date: August 2026
+Contact: "Daeho Jeong" <daehojeong@google.com>
+Description: This is a read-only entry to show the upper bound section number
+ for pinned files. Pinned files will only be allocated within
+ sections 0 to pinned_area_max_secno - 1.
diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst
index b45d7a687625..771216f45207 100644
--- a/Documentation/filesystems/f2fs.rst
+++ b/Documentation/filesystems/f2fs.rst
@@ -415,7 +415,13 @@ lookup_mode=%s Control the directory lookup behavior for casefolded
auto F2FS determines the mode based on the
on-disk `SB_ENC_NO_COMPAT_FALLBACK_FL`
flag.
- ================== ========================================
+resizable_tail_secno=%u Control the number of sections at the tail of the
+ filesystem reserved for online resizing. Pinned files
+ will only be allocated within sections 0 to
+ (MAIN_SECS - resizable_tail_secno) - 1. If set to 0
+ (default), there is no tail restriction unless running
+ on a zoned block device where conventional zones are
+ used.
======================== ============================================================
Debugfs Entries
@@ -1043,6 +1049,41 @@ So, the key idea is, user can do any file operations on /dev/vdc, and
reclaim the space after the use, while the space is counted as /data.
That doesn't require modifying partition size and filesystem format.
+Dynamic Device Aliasing Management
+----------------------------------
+
+In addition to static device aliasing by deleting the aliasing file, F2FS
+supports dynamic management of device aliasing. This mechanism allows the system
+to dynamically transition partition ownership between F2FS userdata and external
+entities (e.g., zRAM, raw partition) based on system requirements without
+deleting the master aliasing file or requiring unmount/remount.
+
+The master aliasing file is created during the initial format of the file system
+and remains as a persistent control entity (ioctl gateway) in the root directory.
+
+- Partition Reservation (In-service to Aliased)
+ When a specific partition needs to be dedicated to external services (e.g., zRAM),
+ a user can reserve the device alias range via ioctl. The kernel resets GC victim
+ information for the target range, marks segments as in-use to prevent new
+ allocations, and triggers forced GC to migrate existing valid data out of the
+ range. Finally, it reserves these blocks in the SIT to effectively exclude the
+ device from the usable capacity.
+
+- Partition Release (Aliased to In-service)
+ When external usage concludes, the space is reclaimed not by deleting the file,
+ but through the release ioctl. The kernel truncates blocks associated with
+ the file, releasing them back to general filesystem allocation.
+
+.. code-block::
+
+ # f2fs_io dev_alias release /mnt/f2fs/vdc.file
+ # df -h
+ /dev/vdb 64G 753M 64G 2% /mnt/f2fs
+
+ # f2fs_io dev_alias reserve /mnt/f2fs/vdc.file
+ # df -h
+ /dev/vdb 64G 33G 32G 52% /mnt/f2fs
+
Per-file Read-Only Large Folio Support
--------------------------------------
diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c
index 064f5b537423..4b59f30ef45d 100644
--- a/fs/f2fs/checkpoint.c
+++ b/fs/f2fs/checkpoint.c
@@ -107,6 +107,7 @@ static bool need_uplift_priority(struct f2fs_rwsem *sem, bool is_write)
case LOCK_NAME_GC_LOCK:
case LOCK_NAME_CP_GLOBAL:
case LOCK_NAME_IO_RWSEM:
+ case LOCK_NAME_NAT_TREE_LOCK:
return true;
default:
f2fs_bug_on(sem->sbi, 1);
@@ -766,28 +767,116 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type)
spin_unlock(&im->ino_lock);
}
+static void __set_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type)
+{
+ struct inode_management *im = &sbi->im[type];
+ unsigned long index = INO_SLOT_INDEX(ino);
+ unsigned int ofs = INO_BIT_OFFSET(ino);
+ void **slot, *entry;
+ unsigned long bitmap = 0;
+ int ret;
+
+ ret = radix_tree_preload(GFP_NOFS | __GFP_NOFAIL);
+ f2fs_bug_on(sbi, ret);
+
+ spin_lock(&im->ino_lock);
+ slot = radix_tree_lookup_slot(&im->ino_root, index);
+ if (slot) {
+ entry = radix_tree_deref_slot_protected(slot, &im->ino_lock);
+ bitmap = xa_to_value(entry);
+ if (!(bitmap & (1UL << ofs))) {
+ bitmap |= (1UL << ofs);
+ entry = xa_mk_value(bitmap);
+ radix_tree_replace_slot(&im->ino_root, slot, entry);
+ }
+ } else {
+ bitmap |= (1UL << ofs);
+ entry = xa_mk_value(bitmap);
+ if (unlikely(radix_tree_insert(&im->ino_root, index, entry)))
+ f2fs_bug_on(sbi, 1);
+ }
+ spin_unlock(&im->ino_lock);
+ radix_tree_preload_end();
+}
+
+static void __clear_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type)
+{
+ struct inode_management *im = &sbi->im[type];
+ unsigned long index = INO_SLOT_INDEX(ino);
+ unsigned int ofs = INO_BIT_OFFSET(ino);
+ void **slot, *entry;
+ unsigned long bitmap;
+
+ spin_lock(&im->ino_lock);
+ slot = radix_tree_lookup_slot(&im->ino_root, index);
+ if (slot) {
+ entry = radix_tree_deref_slot_protected(slot, &im->ino_lock);
+ bitmap = xa_to_value(entry);
+ if (bitmap & (1UL << ofs))
+ bitmap &= ~(1UL << ofs);
+
+ if (bitmap) {
+ entry = xa_mk_value(bitmap);
+ radix_tree_replace_slot(&im->ino_root, slot, entry);
+ } else {
+ radix_tree_delete(&im->ino_root, index);
+ }
+ }
+ spin_unlock(&im->ino_lock);
+}
+
+static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode)
+{
+ if (mode != APPEND_INO && mode != UPDATE_INO)
+ return;
+
+ /* Let's wait for some pending updates for APPEND_INO and UPDATE_INO. */
+ flush_workqueue(sbi->evict_wq);
+}
+
+static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino,
+ unsigned int devidx, int type)
+{
+ if (type <= FLUSH_INO)
+ __add_ino_entry(sbi, ino, devidx, type);
+ else
+ __set_ino_bitmap(sbi, ino, type);
+}
+
void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type)
{
- /* add new dirty ino entry into list */
- __add_ino_entry(sbi, ino, 0, type);
+ __f2fs_add_ino_entry(sbi, ino, 0, type);
}
void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type)
{
- /* remove dirty ino entry from list */
- __remove_ino_entry(sbi, ino, type);
+ if (type <= FLUSH_INO)
+ __remove_ino_entry(sbi, ino, type);
+ else
+ __clear_ino_bitmap(sbi, ino, type);
}
-/* mode should be APPEND_INO, UPDATE_INO or TRANS_DIR_INO */
+/* mode should be APPEND_INO, UPDATE_INO, TRANS_DIR_INO and XATTR_DIR_INO */
bool f2fs_exist_written_data(struct f2fs_sb_info *sbi, nid_t ino, int mode)
{
struct inode_management *im = &sbi->im[mode];
- struct ino_entry *e;
+ unsigned long index = INO_SLOT_INDEX(ino);
+ unsigned int ofs = INO_BIT_OFFSET(ino);
+ void *entry;
+ unsigned long bitmap;
+
+ f2fs_bug_on(sbi, mode <= FLUSH_INO);
spin_lock(&im->ino_lock);
- e = radix_tree_lookup(&im->ino_root, ino);
+ entry = radix_tree_lookup(&im->ino_root, index);
+ if (!entry) {
+ spin_unlock(&im->ino_lock);
+ return false;
+ }
+ bitmap = xa_to_value(entry);
spin_unlock(&im->ino_lock);
- return e ? true : false;
+
+ return bitmap & (1UL << ofs);
}
void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all)
@@ -795,9 +884,11 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all)
struct ino_entry *e, *tmp;
int i;
- for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) {
+ for (i = all ? ORPHAN_INO : FLUSH_INO; i <= FLUSH_INO; i++) {
struct inode_management *im = &sbi->im[i];
+ f2fs_wait_for_inode_record(sbi, i);
+
spin_lock(&im->ino_lock);
list_for_each_entry_safe(e, tmp, &im->ino_list, list) {
list_del(&e->list);
@@ -807,12 +898,20 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all)
}
spin_unlock(&im->ino_lock);
}
+
+ for (i = APPEND_INO; i < MAX_INO_ENTRY; i++) {
+ struct inode_management *im = &sbi->im[i];
+
+ spin_lock(&im->ino_lock);
+ xa_destroy(&im->ino_root);
+ spin_unlock(&im->ino_lock);
+ }
}
void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino,
unsigned int devidx, int type)
{
- __add_ino_entry(sbi, ino, devidx, type);
+ __f2fs_add_ino_entry(sbi, ino, devidx, type);
}
bool f2fs_is_dirty_device(struct f2fs_sb_info *sbi, nid_t ino,
@@ -864,14 +963,14 @@ void f2fs_release_orphan_inode(struct f2fs_sb_info *sbi)
void f2fs_add_orphan_inode(struct inode *inode)
{
/* add new orphan ino entry into list */
- __add_ino_entry(F2FS_I_SB(inode), inode->i_ino, 0, ORPHAN_INO);
+ f2fs_add_ino_entry(F2FS_I_SB(inode), inode->i_ino, ORPHAN_INO);
f2fs_update_inode_page(inode);
}
void f2fs_remove_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino)
{
/* remove orphan entry from orphan list */
- __remove_ino_entry(sbi, ino, ORPHAN_INO);
+ f2fs_remove_ino_entry(sbi, ino, ORPHAN_INO);
}
static int recover_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index dad5c2bd5888..21f396ebe22c 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -384,11 +384,30 @@ static void f2fs_write_end_bio(struct bio *bio)
bio_put(bio);
}
+static void f2fs_write_end_io_work(struct work_struct *work)
+{
+ struct bio *bio = &container_of(work, struct f2fs_bio, work)->bio;
+
+ f2fs_write_end_bio(bio);
+}
+
static void f2fs_write_end_io(struct bio *bio)
{
+ struct f2fs_sb_info *sbi;
+
iostat_update_and_unbind_ctx(bio);
- f2fs_write_end_bio(bio);
+ sbi = bio->bi_private;
+
+ if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) {
+ struct work_struct *w;
+
+ w = &container_of(bio, struct f2fs_bio, bio)->work;
+ INIT_WORK(w, f2fs_write_end_io_work);
+ queue_work(sbi->wq, w);
+ } else {
+ f2fs_write_end_bio(bio);
+ }
}
#ifdef CONFIG_BLK_DEV_ZONED
@@ -1216,7 +1235,7 @@ int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count)
if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC)))
return -EPERM;
- err = inc_valid_block_count(sbi, dn->inode, &count, true);
+ err = inc_valid_block_count(sbi, dn->inode, &count, true, false);
if (unlikely(err))
return err;
@@ -1288,10 +1307,11 @@ retry:
if (folio_test_large(folio)) {
pgoff_t folio_index = mapping_align_index(mapping, index);
+ unsigned long nr_pages = folio_nr_pages(folio);
f2fs_folio_put(folio, true);
invalidate_inode_pages2_range(mapping, folio_index,
- folio_index + folio_nr_pages(folio) - 1);
+ folio_index + nr_pages - 1);
f2fs_schedule_timeout(DEFAULT_SCHEDULE_TIMEOUT);
goto retry;
}
@@ -1488,7 +1508,7 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type)
dn->data_blkaddr = f2fs_data_blkaddr(dn);
if (dn->data_blkaddr == NULL_ADDR) {
- err = inc_valid_block_count(sbi, dn->inode, &count, true);
+ err = inc_valid_block_count(sbi, dn->inode, &count, true, false);
if (unlikely(err))
return err;
}
@@ -1497,8 +1517,11 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type)
old_blkaddr = dn->data_blkaddr;
err = f2fs_allocate_data_block(sbi, NULL, old_blkaddr,
&dn->data_blkaddr, &sum, seg_type, NULL);
- if (err)
+ if (err) {
+ if (old_blkaddr == NULL_ADDR)
+ dec_valid_block_count(sbi, dn->inode, count);
return err;
+ }
if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)
f2fs_invalidate_internal_cache(sbi, old_blkaddr, 1);
@@ -3900,7 +3923,7 @@ repeat:
* Will wait that below with our IO control.
*/
folio = f2fs_filemap_get_folio(mapping, index,
- FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_NOFS,
+ FGP_LOCK | FGP_WRITE | FGP_CREAT,
mapping_gfp_mask(mapping));
if (IS_ERR(folio)) {
err = PTR_ERR(folio);
@@ -4460,13 +4483,24 @@ int f2fs_init_wq(struct f2fs_sb_info *sbi)
{
sbi->wq = alloc_workqueue("f2fs_wq", WQ_UNBOUND | WQ_HIGHPRI,
num_online_cpus());
- return sbi->wq ? 0 : -ENOMEM;
+ if (!sbi->wq)
+ return -ENOMEM;
+
+ sbi->evict_wq = alloc_workqueue("f2fs_evict_wq",
+ WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus());
+ if (!sbi->evict_wq) {
+ destroy_workqueue(sbi->wq);
+ return -ENOMEM;
+ }
+ return 0;
}
void f2fs_destroy_wq(struct f2fs_sb_info *sbi)
{
if (sbi->wq)
destroy_workqueue(sbi->wq);
+ if (sbi->evict_wq)
+ destroy_workqueue(sbi->evict_wq);
}
int __init f2fs_init_bio_entry_cache(void)
diff --git a/fs/f2fs/debug.c b/fs/f2fs/debug.c
index af88db8fdb71..ff379aff4472 100644
--- a/fs/f2fs/debug.c
+++ b/fs/f2fs/debug.c
@@ -352,10 +352,6 @@ static void update_mem_info(struct f2fs_sb_info *sbi)
get_cache:
si->cache_mem = 0;
- /* build gc */
- if (sbi->gc_thread)
- si->cache_mem += sizeof(struct f2fs_gc_kthread);
-
/* build merge flush thread */
if (SM_I(sbi)->fcc_info)
si->cache_mem += sizeof(struct flush_cmd_control);
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index a9563f7fcd88..fd0e2cd31a81 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -320,6 +320,7 @@ start_find_bucket:
de = find_in_block(dir, dentry_folio, fname, &max_slots, use_hash);
if (IS_ERR(de)) {
+ f2fs_folio_put(dentry_folio, false);
*res_folio = ERR_CAST(de);
de = NULL;
break;
@@ -460,7 +461,7 @@ void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de,
folio_mark_dirty(folio);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
- f2fs_mark_inode_dirty_sync(dir, false);
+ f2fs_mark_inode_dirty_sync(dir, true);
f2fs_folio_put(folio, true);
}
@@ -615,7 +616,7 @@ void f2fs_update_parent_metadata(struct inode *dir, struct inode *inode,
clear_inode_flag(inode, FI_NEW_INODE);
}
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
- f2fs_mark_inode_dirty_sync(dir, false);
+ f2fs_mark_inode_dirty_sync(dir, true);
if (F2FS_I(dir)->i_current_depth != current_depth)
f2fs_i_depth_write(dir, current_depth);
@@ -927,7 +928,7 @@ void f2fs_delete_entry(struct f2fs_dir_entry *dentry, struct folio *folio,
f2fs_folio_put(folio, true);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
- f2fs_mark_inode_dirty_sync(dir, false);
+ f2fs_mark_inode_dirty_sync(dir, true);
if (inode)
f2fs_drop_nlink(dir, inode);
diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c
index 61f6b9714366..37cf9fa8d537 100644
--- a/fs/f2fs/extent_cache.c
+++ b/fs/f2fs/extent_cache.c
@@ -17,6 +17,7 @@
#include "f2fs.h"
#include "node.h"
+#include "segment.h"
#include <trace/events/f2fs.h>
bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio)
@@ -62,6 +63,14 @@ bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio)
__func__, inode->i_ino, ei.blk, ei.fofs, ei.len);
return false;
}
+
+ if ((GET_SEGOFF_FROM_SEG0(sbi, ei.blk) % BLKS_PER_SEC(sbi)) ||
+ (ei.len % BLKS_PER_SEC(sbi))) {
+ f2fs_warn(sbi, "%s: device alias inode (ino=%llx)'s extent info [%u, %u, %u] is not aligned to section size %u",
+ __func__, inode->i_ino, ei.blk, ei.fofs, ei.len,
+ BLKS_PER_SEC(sbi));
+ return false;
+ }
return true;
}
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 8376bbe58ee3..9940a6cecf1a 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -193,6 +193,7 @@ enum f2fs_lock_name {
LOCK_NAME_GC_LOCK,
LOCK_NAME_CP_GLOBAL,
LOCK_NAME_IO_RWSEM,
+ LOCK_NAME_NAT_TREE_LOCK,
LOCK_NAME_MAX,
};
@@ -254,6 +255,7 @@ struct f2fs_mount_info {
block_t unusable_cap; /* Amount of space allowed to be
* unusable when disabling checkpoint
*/
+ unsigned int resizable_tail_secno; /* number of resizable tail sections */
/* For compression */
unsigned char compress_algorithm; /* algorithm type */
@@ -388,14 +390,18 @@ enum {
/* for the list of ino */
enum {
ORPHAN_INO, /* for orphan ino list */
+ FLUSH_INO, /* for multiple device flushing */
APPEND_INO, /* for append ino list */
UPDATE_INO, /* for update ino list */
TRANS_DIR_INO, /* for transactions dir ino list */
XATTR_DIR_INO, /* for xattr updated dir ino list */
- FLUSH_INO, /* for multiple device flushing */
MAX_INO_ENTRY, /* max. list */
};
+#define INO_BITS_PER_SLOT BITS_PER_XA_VALUE
+#define INO_SLOT_INDEX(ino) ((ino) / INO_BITS_PER_SLOT)
+#define INO_BIT_OFFSET(ino) ((ino) % INO_BITS_PER_SLOT)
+
struct ino_entry {
struct list_head list; /* list head */
nid_t ino; /* inode number */
@@ -1403,6 +1409,8 @@ struct f2fs_dev_info {
unsigned int total_segments;
block_t start_blk;
block_t end_blk;
+ bool has_alias;
+ bool is_reserving;
#ifdef CONFIG_BLK_DEV_ZONED
unsigned int nr_blkz; /* Total number of zones */
unsigned long *blkz_seq; /* Bitmap indicating sequential zones */
@@ -1747,6 +1755,33 @@ struct decompress_io_ctx {
#define MAX_COMPRESS_LOG_SIZE 8
#define MAX_COMPRESS_WINDOW_SIZE(log_size) ((PAGE_SIZE) << (log_size))
+struct f2fs_gc_kthread {
+ struct task_struct *f2fs_gc_task;
+ wait_queue_head_t gc_wait_queue_head;
+
+ /* for gc sleep time */
+ unsigned int urgent_sleep_time;
+ unsigned int min_sleep_time;
+ unsigned int max_sleep_time;
+ unsigned int no_gc_sleep_time;
+
+ /* for changing gc mode */
+ bool gc_wake;
+
+ /* for GC_MERGE mount option */
+ wait_queue_head_t fggc_wq; /*
+ * caller of f2fs_balance_fs()
+ * will wait on this wait queue.
+ */
+
+ /* for gc control for zoned devices */
+ unsigned int no_zoned_gc_percent;
+ unsigned int boost_zoned_gc_percent;
+ unsigned int valid_thresh_ratio;
+ unsigned int boost_gc_multiple;
+ unsigned int boost_gc_greedy;
+};
+
struct f2fs_sb_info {
struct super_block *sb; /* pointer to VFS super block */
struct proc_dir_entry *s_proc; /* proc entry */
@@ -1772,6 +1807,8 @@ struct f2fs_sb_info {
struct f2fs_sm_info *sm_info; /* segment manager */
/* for bio operations */
+ /* Largest write bio size completed in atomic context (atc). */
+ u32 max_atc_write_bio_size;
struct f2fs_bio_info *write_io[NR_PAGE_TYPE]; /* for write bios */
/* keep migration IO order for LFS mode */
struct f2fs_rwsem io_order_lock;
@@ -1854,6 +1891,7 @@ struct f2fs_sb_info {
block_t last_valid_block_count; /* for recovery */
block_t reserved_blocks; /* configurable reserved blocks */
block_t current_reserved_blocks; /* current reserved blocks */
+ block_t alias_reserved_blocks; /* reserved blocks for device alias */
/* Additional tracking for no checkpoint mode */
block_t unusable_block_count; /* # of blocks saved by last cp */
@@ -1882,7 +1920,7 @@ struct f2fs_sb_info {
* semaphore for GC, avoid
* race between GC and GC or CP
*/
- struct f2fs_gc_kthread *gc_thread; /* GC thread */
+ struct f2fs_gc_kthread gc_thread; /* GC thread */
struct atgc_management am; /* atgc management */
unsigned int cur_victim_sec; /* current victim section num */
unsigned int gc_mode; /* current GC state */
@@ -1967,6 +2005,7 @@ struct f2fs_sb_info {
spinlock_t dev_lock; /* protect dirty_device */
bool aligned_blksize; /* all devices has the same logical blksize */
unsigned int first_seq_zone_segno; /* first segno in sequential zone */
+ unsigned int pinned_area_max_secno; /* upper bound section for pinned files */
unsigned int bggc_io_aware; /* For adjust the BG_GC priority when pending IO */
unsigned int allocate_section_hint; /* the boundary position between devices */
unsigned int allocate_section_policy; /* determine the section writing priority */
@@ -1980,6 +2019,8 @@ struct f2fs_sb_info {
struct workqueue_struct *wq; /* bio completion workqueue */
+ struct workqueue_struct *evict_wq; /* inode eviction workqueue */
+
/*
* If we are in irq context, let's update error information into
* on-disk superblock in the work.
@@ -2556,7 +2597,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi,
block_t avail_user_block_count;
avail_user_block_count = sbi->user_block_count -
- sbi->current_reserved_blocks;
+ sbi->current_reserved_blocks -
+ sbi->alias_reserved_blocks;
if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap))
avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks;
@@ -2573,7 +2615,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi,
static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool);
static inline int inc_valid_block_count(struct f2fs_sb_info *sbi,
- struct inode *inode, blkcnt_t *count, bool partial)
+ struct inode *inode, blkcnt_t *count,
+ bool partial, bool alias_reserved)
{
long long diff = 0, release = 0;
block_t avail_user_block_count;
@@ -2596,10 +2639,16 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi,
spin_lock(&sbi->stat_lock);
+ if (alias_reserved)
+ sbi->alias_reserved_blocks -= *count;
+
avail_user_block_count = get_available_block_count(sbi, inode, true);
diff = (long long)sbi->total_valid_block_count + *count -
avail_user_block_count;
if (unlikely(diff > 0)) {
+ if (alias_reserved)
+ sbi->alias_reserved_blocks += *count;
+
if (!partial) {
spin_unlock(&sbi->stat_lock);
release = *count;
@@ -3830,7 +3879,10 @@ void f2fs_update_inode_page(struct inode *inode);
int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc);
void f2fs_remove_donate_inode(struct inode *inode);
void f2fs_evict_inode(struct inode *inode);
-void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc);
+void f2fs_handle_failed_inode(struct inode *inode,
+ struct f2fs_lock_context *lc, bool add_orphan);
+int f2fs_init_evict_inode_work(void);
+void f2fs_destroy_evict_inode_work(void);
/*
* namei.c
@@ -4007,6 +4059,8 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi);
void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free);
void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr,
unsigned int len);
+void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr,
+ unsigned int len);
bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr);
int f2fs_start_discard_thread(struct f2fs_sb_info *sbi);
void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi);
@@ -4095,10 +4149,26 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio)
#define MIN_FRAGMENT_SIZE 1
#define MAX_FRAGMENT_SIZE 512
-static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi)
+static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi,
+ enum log_type type)
+{
+ if (type == CURSEG_COLD_DATA_PINNED)
+ return false;
+ return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK;
+}
+
+static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi,
+ enum log_type type)
+{
+ if (type == CURSEG_COLD_DATA_PINNED)
+ return false;
+ return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG;
+}
+
+static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi,
+ enum log_type type)
{
- return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG ||
- F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK;
+ return f2fs_need_rand_blk(sbi, type) || f2fs_need_rand_seg(sbi, type);
}
/*
@@ -4226,7 +4296,9 @@ int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control);
void f2fs_build_gc_manager(struct f2fs_sb_info *sbi);
int f2fs_gc_range(struct f2fs_sb_info *sbi,
unsigned int start_seg, unsigned int end_seg,
- bool dry_run, unsigned int dry_run_sections);
+ bool dry_run, unsigned int dry_run_sections, bool lock);
+void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi,
+ unsigned int start, unsigned int end);
int f2fs_resize_fs(struct file *filp, __u64 block_count);
int __init f2fs_create_garbage_collection_cache(void);
void f2fs_destroy_garbage_collection_cache(void);
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 089759366cdc..edc352569e87 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -36,23 +36,69 @@
#include <trace/events/f2fs.h>
#include <uapi/linux/f2fs.h>
-static void f2fs_zero_post_eof_page(struct inode *inode,
- loff_t new_size, bool lock)
+static int fill_zero(struct inode *inode, pgoff_t index,
+ loff_t start, loff_t len);
+
+static int do_zero_post_eof_page(struct inode *inode, loff_t new_size)
{
loff_t old_size = i_size_read(inode);
+ unsigned int offset, len;
+ pgoff_t index;
+ int err;
+
+ offset = old_size & (PAGE_SIZE - 1);
+
+ if (!offset)
+ return 0;
+
+ len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size);
+ index = old_size >> PAGE_SHIFT;
+
+ if (f2fs_has_inline_data(inode)) {
+ /* data post eof should be always zero */
+ if (new_size <= MAX_INLINE_DATA(inode))
+ return 0;
+ err = f2fs_convert_inline_inode(inode);
+ if (err)
+ return err;
+ }
+
+ err = fill_zero(inode, index, offset, len);
+ if (err)
+ return err;
+ return filemap_write_and_wait_range(inode->i_mapping,
+ old_size, old_size + len - 1);
+}
+
+static int f2fs_zero_post_eof_page(struct inode *inode,
+ loff_t new_size, bool lock, bool writeback)
+{
+ loff_t old_size = i_size_read(inode);
+ bool strict =
+ F2FS_OPTION(F2FS_I_SB(inode)).fsync_mode == FSYNC_MODE_STRICT;
if (old_size >= new_size)
- return;
+ return 0;
- if (mapping_empty(inode->i_mapping))
- return;
+ if (!strict && mapping_empty(inode->i_mapping))
+ return 0;