Re: [f2fs-dev] [PATCH v4] f2fs: support dynamic reserve/release for device aliasing
Daeho Jeong <[email protected]> Tue, 21 Jul 2026 10:53:26 -0700
| Newsgroups | net.sourceforge.lists.linux-f2fs-devel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CACOAw_ydhbDQLUmkpB5-1P9_z5uEn1TZHcHuNmz0SKBfLFyR2g@mail.gmail.com> |
On Sun, Jul 19, 2026 at 1:54 AM Chao Yu <[email protected]> wrote: > > On 7/14/26 00:05, Daeho Jeong wrote: > > From: Daeho Jeong <[email protected]> > > > > This patch adds a dynamic management feature to the existing device > > aliasing functionality. It allows users to dynamically reserve or > > release specific devices from the filesystem's free pool at runtime > > through new ioctls. > > > > To support this, three new ioctls are introduced: > > - F2FS_IOC_RESERVE_DEV_ALIAS: This reclaims the space occupied by a > > device aliasing file. It first performs a capacity check, resets GC > > victim information for the target range, marks the segments as in-use > > to prevent new allocations, and then triggers 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. > > > > - F2FS_IOC_RELEASE_DEV_ALIAS: This releases the reserved space of a > > previously reserved device aliasing file. It truncates the blocks > > associated with the file, which makes them available for general > > filesystem allocation again. > > > > - F2FS_IOC_GET_DEV_ALIAS_STATUS: This retrieves the current aliasing > > status of a device aliasing file, returning whether the file is > > released (inactive alias) or reserved (active alias, with blocks > > fully allocated on the device). > > > > Signed-off-by: Daeho Jeong <[email protected]> > > --- > > v4: renamed interfaces. > > fixed race conditions between checkpoint=disable mount and ioctls. > > refactored segment reservation part. > > modified lock usage. > > v3: add CAP_SYS_ADMIN and checkpoint=disabled check. > > remove a f2fs specific flag exposed with getflags. > > v2: prevent operations during checkpoint=disabled. > > --- > > Documentation/filesystems/f2fs.rst | 35 ++++ > > fs/f2fs/f2fs.h | 9 +- > > fs/f2fs/file.c | 289 ++++++++++++++++++++++++++++- > > fs/f2fs/gc.c | 30 +-- > > fs/f2fs/namei.c | 14 ++ > > fs/f2fs/segment.c | 180 +++++++++++++----- > > fs/f2fs/segment.h | 26 +++ > > fs/f2fs/super.c | 34 ++++ > > include/uapi/linux/f2fs.h | 7 + > > 9 files changed, 562 insertions(+), 62 deletions(-) > > > > diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst > > index 8c4a14ae444f..1a5fd4afe609 100644 > > --- a/Documentation/filesystems/f2fs.rst > > +++ b/Documentation/filesystems/f2fs.rst > > @@ -1045,6 +1045,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/f2fs.h b/fs/f2fs/f2fs.h > > index f1774d4e18d2..0f9b8b66cef9 100644 > > --- a/fs/f2fs/f2fs.h > > +++ b/fs/f2fs/f2fs.h > > @@ -1404,6 +1404,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 */ > > @@ -4009,7 +4011,10 @@ int f2fs_create_flush_cmd_control(struct f2fs_sb_info *sbi); > > 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); > > + 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); > > @@ -4231,6 +4236,8 @@ 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); > > +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 4b52c56d71f0..9077c091c2d6 100644 > > --- a/fs/f2fs/file.c > > +++ b/fs/f2fs/file.c > > @@ -813,13 +813,19 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock) > > > > if (IS_DEVICE_ALIASING(inode)) { > > struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; > > - struct extent_info ei = et->largest; > > + struct extent_info ei; > > + > > + read_lock(&et->lock); > > + ei = et->largest; > > + read_unlock(&et->lock); > > > > f2fs_invalidate_blocks(sbi, ei.blk, ei.len); > > > > dec_valid_block_count(sbi, inode, ei.len); > > f2fs_update_time(sbi, REQ_TIME); > > > > + f2fs_drop_extent_tree(inode); > > + > > f2fs_folio_put(ifolio, true); > > goto out; > > } > > @@ -1100,8 +1106,9 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, > > if ((attr->ia_valid & ATTR_SIZE)) { > > if (mapping_large_folio_support(inode->i_mapping)) > > return -EOPNOTSUPP; > > - if (!f2fs_is_compress_backend_ready(inode) || > > - IS_DEVICE_ALIASING(inode)) > > + if (IS_DEVICE_ALIASING(inode)) > > + return -EPERM; > > + if (!f2fs_is_compress_backend_ready(inode)) > > return -EOPNOTSUPP; > > if (is_inode_flag_set(inode, FI_COMPRESS_RELEASED) && > > !IS_ALIGNED(attr->ia_size, > > @@ -2130,6 +2137,9 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask) > > if (IS_NOQUOTA(inode)) > > return -EPERM; > > > > + if (IS_DEVICE_ALIASING(inode)) > > + return -EPERM; > > + > > if ((iflags ^ masked_flags) & F2FS_CASEFOLD_FL) { > > if (!f2fs_sb_has_casefold(F2FS_I_SB(inode))) > > return -EOPNOTSUPP; > > @@ -2678,6 +2688,17 @@ static int f2fs_ioc_get_encryption_policy(struct file *filp, unsigned long arg) > > return fscrypt_ioctl_get_policy(filp, (void __user *)arg); > > } > > > > +static int f2fs_ioc_get_dev_alias_status(struct file *filp, unsigned long arg) > > +{ > > + struct inode *inode = file_inode(filp); > > + > > + if (!IS_DEVICE_ALIASING(inode)) > > + return -EINVAL; > > + > > + return put_user(F2FS_HAS_BLOCKS(inode) ? F2FS_DEV_ALIAS_STATUS_RESERVED : > > + F2FS_DEV_ALIAS_STATUS_RELEASED, (u32 __user *)arg); > > +} > > + > > static int f2fs_ioc_get_encryption_pwsalt(struct file *filp, unsigned long arg) > > { > > struct inode *inode = file_inode(filp); > > @@ -3616,6 +3637,259 @@ static int f2fs_ioc_get_dev_alias_file(struct file *filp, unsigned long arg) > > (u32 __user *)arg); > > } > > > > +static int f2fs_ioc_reserve_dev_alias(struct file *filp) > > +{ > > + struct inode *inode = file_inode(filp); > > + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); > > + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; > > + struct extent_info ei; > > + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; > > + struct f2fs_lock_context lc, glc; > > + blkcnt_t count; > > + unsigned int start, end, segno; > > + int type, i, err; > > + > > + if (!capable(CAP_SYS_ADMIN)) > > + return -EPERM; > > + > > + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) > > + return -EINVAL; > > + > > + err = mnt_want_write_file(filp); > > + if (err) > > + return err; > > + > > + inode_lock(inode); > > + > > + if (!IS_DEVICE_ALIASING(inode)) { > > + err = -EINVAL; > > + goto out_inode_unlock; > > + } > > + > > + if (F2FS_HAS_BLOCKS(inode)) { > > + err = 0; > > + goto out_inode_unlock; > > + } > > + > > + for (i = 1; i < sbi->s_ndevs; i++) { > > + char *name = strrchr(FDEV(i).path, '/'); > > + > > + name = name ? name + 1 : FDEV(i).path; > > + if (!strcmp(name, filp->f_path.dentry->d_name.name)) { > > + ei.blk = FDEV(i).start_blk; > > + ei.len = FDEV(i).total_segments << sbi->log_blocks_per_seg; > > + ei.fofs = 0; > > + break; > > + } > > + } > > + > > + if (i == sbi->s_ndevs) { > > + f2fs_warn(sbi, "device alias file (%s, ino=%llu) has no matching device", > > + filp->f_path.dentry->d_name.name, inode->i_ino); > > + set_sbi_flag(sbi, SBI_NEED_FSCK); > > + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); > > + err = -EFSCORRUPTED; > > + goto out_inode_unlock; > > + } > > + > > + f2fs_down_write_trace(&sbi->gc_lock, &glc); > > + f2fs_lock_op(sbi, &lc); > > + > > + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { > > + err = -EINVAL; > > + } else { > > + count = ei.len; > > + err = inc_valid_block_count(sbi, inode, &count, false); > > + } > > + if (err) { > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + goto out_inode_unlock; > > + } > > + > > + spin_lock(&FREE_I(sbi)->segmap_lock); > > + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = true; > > + spin_unlock(&FREE_I(sbi)->segmap_lock); > > + > > + start = GET_SEGNO(sbi, ei.blk); > > + end = GET_SEGNO(sbi, ei.blk + ei.len - 1); > > + > > + /* Reset the victim information to prevent GC from targeting the range */ > > + f2fs_reset_gc_victim_resource(sbi, start, end); > > + > > + /* Move out cursegs from the target range */ > > + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { > > + err = f2fs_allocate_segment_for_resize(sbi, type, start, end); > > + if (err) { > > + f2fs_unlock_op(sbi, &lc); > > + goto out_gc_unlock; > > + } > > + } > > + > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + > > + /* Write checkpoint synchronously to flush all pending writes and free space */ > > + err = f2fs_write_checkpoint(sbi, &cpc); > > + if (err) { > > + f2fs_down_write_trace(&sbi->gc_lock, &glc); > > + goto out_gc_unlock; > > + } > > + > > + /* Re-acquire gc_lock and cp_rwsem read lock for the entire range GC */ > > + f2fs_down_write_trace(&sbi->gc_lock, &glc); > > + f2fs_lock_op(sbi, &lc); > > + > > + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { > > + err = -EINVAL; > > + f2fs_unlock_op(sbi, &lc); > > + goto out_gc_unlock; > > + } > > + > > + /* do GC to move out valid blocks in the range all at once! */ > > + err = f2fs_gc_range(sbi, start, end, false, 0); > > + if (err) { > > + f2fs_unlock_op(sbi, &lc); > > + goto out_gc_unlock; > > + } > > + > > + if (et) { > > + write_lock(&et->lock); > > + et->largest = ei; > > + write_unlock(&et->lock); > > + } > > + clear_inode_flag(inode, FI_NO_EXTENT); > > + > > + f2fs_reserve_device_alias(sbi, ei.blk, ei.len); > > + > > + i_size_write(inode, (loff_t)ei.len << PAGE_SHIFT); > > + f2fs_update_inode_page(inode); > > + > > + spin_lock(&FREE_I(sbi)->segmap_lock); > > + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; > > + spin_unlock(&FREE_I(sbi)->segmap_lock); > > + > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + > > + inode_unlock(inode); > > + mnt_drop_write_file(filp); > > + > > + err = f2fs_write_checkpoint(sbi, &cpc); > > + return err; > > + > > +out_gc_unlock: > > + spin_lock(&FREE_I(sbi)->segmap_lock); > > + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; > > + spin_unlock(&FREE_I(sbi)->segmap_lock); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > Seems, once we have reserved blocks above, and then unlock cp_rwsem and > gc_lock, a following CP and sudden power-cut recovery will make device alias > file corrupted? Could you please elaborate on the specific scenario or race condition you are concerned about? In the current implementation: - Both the SIT entry updates (f2fs_reserve_device_alias) and the inode page updates (f2fs_update_inode_page) are performed under cp_rwsem (f2fs_lock_op). - Once cp_rwsem is unlocked, both dirty SIT entries and the dirty inode page will be committed together atomically by any following Checkpoint. - If a sudden power-cut happens before the final Checkpoint completes, the filesystem safely recovers to the prior Checkpoint (where the alias file remains in the released/unreserved state with i_size = 0). If it happens after, it recovers to the new Checkpoint (where the alias file is fully reserved). Is there a specific edge case or inconsistency during recovery that I might have missed? > > > Maybe we can reserve blocks for device alias file w/ in-memory variable, something > like reserve_root? > > > + > > + /* > > + * Put successfully GC'ed segments back into PRE list so checkpoint > > + * commits and frees them! > > + */ > > I didn't get it, why do we need to set empty segment as prefree one? You mean > f2fs_reserve_device_alias() has removed all prefreed segments? seems after > f2fs_reserve_device_alias() there is no error path. You are completely right. f2fs_reserve_device_alias() is only called on the success path, so prefreed segments are never removed from the PRE list on the error path (out_gc_unlock). Furthermore, f2fs_gc_range() already handles tracking invalidated segments in the PRE list automatically through standard block invalidation. Therefore, this manual loop adding segments back to dirty_segmap[PRE] in out_gc_unlock is redundant. I will remove this logic in the next version. Thank you for catching this! > > > + f2fs_lock_op(sbi, &lc); > > + for (segno = start; segno <= end; segno++) { > > + if (get_valid_blocks(sbi, segno, false) == 0) { > > + mutex_lock(&DIRTY_I(sbi)->seglist_lock); > > + if (!test_and_set_bit(segno, DIRTY_I(sbi)->dirty_segmap[PRE])) > > + DIRTY_I(sbi)->nr_dirty[PRE]++; > > + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); > > + } > > + } > > + count = ei.len; > > + dec_valid_block_count(sbi, inode, count); > > + f2fs_unlock_op(sbi, &lc); > > + > > + inode_unlock(inode); > > + mnt_drop_write_file(filp); > > + > > + f2fs_write_checkpoint(sbi, &cpc); > > + return err; > > + > > +out_inode_unlock: > > + inode_unlock(inode); > > + mnt_drop_write_file(filp); > > + return err; > > +} > > + > > +static int f2fs_ioc_release_dev_alias(struct file *filp) > > +{ > > + struct inode *inode = file_inode(filp); > > + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); > > + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; > > + struct extent_info ei = {0, }; > > + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; > > + struct f2fs_lock_context lc, glc; > > + int err; > > + > > + if (!capable(CAP_SYS_ADMIN)) > > + return -EPERM; > > + > > + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) > > + return -EINVAL; > > + > > + err = mnt_want_write_file(filp); > > + if (err) > > + return err; > > + > > + inode_lock(inode); > > + > > + if (!IS_DEVICE_ALIASING(inode)) { > > + err = -EINVAL; > > + goto out_inode_unlock; > > + } > > + > > + if (!F2FS_HAS_BLOCKS(inode)) { > > + err = 0; > > + goto out_inode_unlock; > > + } > > + > > + err = filemap_write_and_wait(inode->i_mapping); > > + if (err) > > + goto out_inode_unlock; > > + > > + read_lock(&et->lock); > > + ei = et->largest; > > + read_unlock(&et->lock); > > + > > + f2fs_down_write_trace(&sbi->gc_lock, &glc); > > + f2fs_lock_op(sbi, &lc); > > + > > + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { > > + err = -EINVAL; > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + goto out_inode_unlock; > > + } > > + > > + truncate_setsize(inode, 0); > > + > > + err = f2fs_truncate_blocks(inode, 0, false); > > + if (err) { > > + i_size_write(inode, (loff_t)ei.len << PAGE_SHIFT); > > If f2fs_truncate_blocks() partially truncated blocks of device alias file, > it will be corrupted, since a pinned file may has partial valid blocks in > section? > > Can fsck detect such status of device alias for repair? > That's a great question, but for device alias files, block truncation is strictly an all-or-nothing operation. As implemented in f2fs_truncate_blocks() (file.c:814): For IS_DEVICE_ALIASING(inode), we do not iterate over dnodes or truncate blocks partially. Instead, all ei.len blocks are invalidated at once in a single call via f2fs_invalidate_blocks(sbi, ei.blk, ei.len). The only error paths in f2fs_truncate_blocks() occur before f2fs_invalidate_blocks() is called, when zero blocks have been invalidated. Once f2fs_invalidate_blocks() is reached, all blocks are invalidated together and the function succeeds. Therefore, partial block truncation never happens for device alias files: If f2fs_truncate_blocks() fails, 0 blocks have been freed, so restoring i_size safely preserves the fully-reserved file without any holes. If it succeeds, 100% of the blocks are freed. > Thanks, > > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + goto out_inode_unlock; > > + } > > + > > + f2fs_update_inode_page(inode); > > + > > + f2fs_unlock_op(sbi, &lc); > > + f2fs_up_write_trace(&sbi->gc_lock, &glc); > > + > > + inode_unlock(inode); > > + mnt_drop_write_file(filp); > > + > > + err = f2fs_write_checkpoint(sbi, &cpc); > > + return err; > > + > > +out_inode_unlock: > > + inode_unlock(inode); > > + mnt_drop_write_file(filp); > > + return err; > > +} > > + > > static int f2fs_ioc_io_prio(struct file *filp, unsigned long arg) > > { > > struct inode *inode = file_inode(filp); > > @@ -4742,8 +5016,14 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) > > return f2fs_ioc_compress_file(filp); > > case F2FS_IOC_GET_DEV_ALIAS_FILE: > > return f2fs_ioc_get_dev_alias_file(filp, arg); > > + case F2FS_IOC_GET_DEV_ALIAS_STATUS: > > + return f2fs_ioc_get_dev_alias_status(filp, arg); > > case F2FS_IOC_IO_PRIO: > > return f2fs_ioc_io_prio(filp, arg); > > + case F2FS_IOC_RESERVE_DEV_ALIAS: > > + return f2fs_ioc_reserve_dev_alias(filp); > > + case F2FS_IOC_RELEASE_DEV_ALIAS: > > + return f2fs_ioc_release_dev_alias(filp); > > default: > > return -ENOTTY; > > } > > @@ -5530,7 +5810,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) > > case F2FS_IOC_DECOMPRESS_FILE: > > case F2FS_IOC_COMPRESS_FILE: > > case F2FS_IOC_GET_DEV_ALIAS_FILE: > > + case F2FS_IOC_GET_DEV_ALIAS_STATUS: > > case F2FS_IOC_IO_PRIO: > > + case F2FS_IOC_RESERVE_DEV_ALIAS: > > + case F2FS_IOC_RELEASE_DEV_ALIAS: > > break; > > default: > > return -ENOIOCTLCMD; > > diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c > > index ffaa7ba76a1b..93bcb35a5b5d 100644 > > --- a/fs/f2fs/gc.c > > +++ b/fs/f2fs/gc.c > > @@ -2197,29 +2197,37 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, > > return 0; > > } > > > > +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, > > + unsigned int start, unsigned int end) > > +{ > > + int i; > > + > > + mutex_lock(&DIRTY_I(sbi)->seglist_lock); > > + for (i = 0; i < MAX_GC_POLICY; i++) > > + if (SIT_I(sbi)->last_victim[i] >= start && > > + SIT_I(sbi)->last_victim[i] <= end) > > + SIT_I(sbi)->last_victim[i] = 0; > > + > > + for (i = BG_GC; i <= FG_GC; i++) > > + if (sbi->next_victim_seg[i] >= start && > > + sbi->next_victim_seg[i] <= end) > > + sbi->next_victim_seg[i] = NULL_SEGNO; > > + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); > > +} > > + > > static int free_segment_range(struct f2fs_sb_info *sbi, > > unsigned int secs, bool dry_run) > > { > > unsigned int next_inuse, start, end; > > struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; > > - int gc_mode, gc_type; > > int err = 0; > > int type; > > > > - /* Force block allocation for GC */ > > MAIN_SECS(sbi) -= secs; > > start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); > > end = MAIN_SEGS(sbi) - 1; > > > > - mutex_lock(&DIRTY_I(sbi)->seglist_lock); > > - for (gc_mode = 0; gc_mode < MAX_GC_POLICY; gc_mode++) > > - if (SIT_I(sbi)->last_victim[gc_mode] >= start) > > - SIT_I(sbi)->last_victim[gc_mode] = 0; > > - > > - for (gc_type = BG_GC; gc_type <= FG_GC; gc_type++) > > - if (sbi->next_victim_seg[gc_type] >= start) > > - sbi->next_victim_seg[gc_type] = NULL_SEGNO; > > - mutex_unlock(&DIRTY_I(sbi)->seglist_lock); > > + f2fs_reset_gc_victim_resource(sbi, start, end); > > > > /* Move out cursegs from the target range */ > > for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { > > diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c > > index cac03b8e91a1..8c3b57987f6c 100644 > > --- a/fs/f2fs/namei.c > > +++ b/fs/f2fs/namei.c > > @@ -425,6 +425,9 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, > > if (!f2fs_is_checkpoint_ready(sbi)) > > return -ENOSPC; > > > > + if (IS_DEVICE_ALIASING(inode)) > > + return -EPERM; > > + > > err = fscrypt_prepare_link(old_dentry, dir, dentry); > > if (err) > > return err; > > @@ -568,6 +571,9 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) > > > > trace_f2fs_unlink_enter(dir, dentry); > > > > + if (IS_DEVICE_ALIASING(inode)) > > + return -EPERM; > > + > > if (unlikely(f2fs_cp_error(sbi))) { > > err = -EIO; > > goto out; > > @@ -946,6 +952,9 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, > > bool old_is_dir = S_ISDIR(old_inode->i_mode); > > int err; > > > > + if (IS_DEVICE_ALIASING(old_inode)) > > + return -EPERM; > > + > > if (unlikely(f2fs_cp_error(sbi))) > > return -EIO; > > if (!f2fs_is_checkpoint_ready(sbi)) > > @@ -1016,6 +1025,8 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, > > } > > > > if (new_inode) { > > + if (IS_DEVICE_ALIASING(new_inode)) > > + return -EPERM; > > > > err = -ENOTEMPTY; > > if (old_is_dir && !f2fs_empty_dir(new_inode)) > > @@ -1143,6 +1154,9 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, > > int old_nlink = 0, new_nlink = 0; > > int err; > > > > + if (IS_DEVICE_ALIASING(old_inode) || IS_DEVICE_ALIASING(new_inode)) > > + return -EPERM; > > + > > if (unlikely(f2fs_cp_error(sbi))) > > return -EIO; > > if (!f2fs_is_checkpoint_ready(sbi)) > > diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c > > index d71ddb3ee918..1a986f1884f2 100644 > > --- a/fs/f2fs/segment.c > > +++ b/fs/f2fs/segment.c > > @@ -2502,35 +2502,42 @@ static int update_sit_entry_for_alloc(struct f2fs_sb_info *sbi, struct seg_entry > > unsigned int segno, block_t blkaddr, unsigned int offset, int del) > > { > > bool exist; > > + int del_count = del; > > + int i; > > > > - exist = f2fs_test_and_set_bit(offset, se->cur_valid_map); > > - if (unlikely(exist)) { > > - f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr); > > - f2fs_bug_on(sbi, 1); > > - se->valid_blocks--; > > - del = 0; > > - } > > + f2fs_bug_on(sbi, GET_SEGNO(sbi, blkaddr) != GET_SEGNO(sbi, blkaddr + del_count - 1)); > > > > - if (f2fs_block_unit_discard(sbi) && > > - !f2fs_test_and_set_bit(offset, se->discard_map)) > > - sbi->discard_blks--; > > + for (i = 0; i < del_count; i++) { > > + exist = f2fs_test_and_set_bit(offset + i, se->cur_valid_map); > > + if (unlikely(exist)) { > > + f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr + i); > > + f2fs_bug_on(sbi, 1); > > + se->valid_blocks--; > > + del -= 1; > > + continue; > > + } > > > > - /* > > - * SSR should never reuse block which is checkpointed > > - * or newly invalidated. > > - */ > > - if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { > > - if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) { > > - se->ckpt_valid_blocks++; > > - if (__is_large_section(sbi)) > > - get_sec_entry(sbi, segno)->ckpt_valid_blocks++; > > + if (f2fs_block_unit_discard(sbi) && > > + !f2fs_test_and_set_bit(offset + i, se->discard_map)) > > + sbi->discard_blks--; > > + > > + /* > > + * SSR should never reuse block which is checkpointed > > + * or newly invalidated. > > + */ > > + if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { > > + if (!f2fs_test_and_set_bit(offset + i, se->ckpt_valid_map)) { > > + se->ckpt_valid_blocks++; > > + if (__is_large_section(sbi)) > > + get_sec_entry(sbi, segno)->ckpt_valid_blocks++; > > + } > > } > > - } > > > > - if (!f2fs_test_bit(offset, se->ckpt_valid_map)) { > > - se->ckpt_valid_blocks += del; > > - if (__is_large_section(sbi)) > > - get_sec_entry(sbi, segno)->ckpt_valid_blocks += del; > > + if (!f2fs_test_bit(offset + i, se->ckpt_valid_map)) { > > + se->ckpt_valid_blocks += 1; > > + if (__is_large_section(sbi)) > > + get_sec_entry(sbi, segno)->ckpt_valid_blocks += 1; > > + } > > } > > > > if (__is_large_section(sbi)) > > @@ -2585,9 +2592,14 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, > > unsigned int segno = GET_SEGNO(sbi, addr); > > struct sit_info *sit_i = SIT_I(sbi); > > block_t addr_start = addr, addr_end = addr + len - 1; > > - unsigned int seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; > > + unsigned int seg_num; > > unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; > > > > + if (len == 0) > > + return; > > + > > + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; > > + > > f2fs_bug_on(sbi, addr == NULL_ADDR); > > if (addr == NEW_ADDR || addr == COMPRESS_ADDR) > > return; > > @@ -2620,6 +2632,52 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, > > up_write(&sit_i->sentry_lock); > > } > > > > +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, > > + unsigned int len) > > +{ > > + unsigned int segno = GET_SEGNO(sbi, addr); > > + struct sit_info *sit_i = SIT_I(sbi); > > + block_t addr_start = addr, addr_end = addr + len - 1; > > + unsigned int seg_num; > > + unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; > > + > > + if (len == 0) > > + return; > > + > > + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; > > + > > + down_write(&sit_i->sentry_lock); > > + > > + if (seg_num == 1) > > + cnt = len; > > + else > > + cnt = max_blocks - GET_BLKOFF_FROM_SEG0(sbi, addr); > > + > > + do { > > + update_segment_mtime(sbi, addr_start, 0); > > + update_sit_entry(sbi, addr_start, cnt); > > + __set_test_and_inuse(sbi, segno); > > + > > + /* Remove the segment from PRE (prefree) to prevent checkpoint from freeing it! */ > > + mutex_lock(&DIRTY_I(sbi)->seglist_lock); > > + if (test_and_clear_bit(segno, DIRTY_I(sbi)->dirty_segmap[PRE])) > > + DIRTY_I(sbi)->nr_dirty[PRE]--; > > + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); > > + > > + /* add it into dirty seglist */ > > + locate_dirty_segment(sbi, segno); > > + > > + /* update @addr_start and @cnt and @segno */ > > + addr_start = START_BLOCK(sbi, ++segno); > > + if (++i == seg_num) > > + cnt = GET_BLKOFF_FROM_SEG0(sbi, addr_end) + 1; > > + else > > + cnt = max_blocks; > > + } while (i <= seg_num); > > + > > + up_write(&sit_i->sentry_lock); > > +} > > + > > bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr) > > { > > struct sit_info *sit_i = SIT_I(sbi); > > @@ -2758,8 +2816,13 @@ static int is_next_segment_free(struct f2fs_sb_info *sbi, > > unsigned int segno = curseg->segno + 1; > > struct free_segmap_info *free_i = FREE_I(sbi); > > > > - if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) > > + if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) { > > + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); > > + > > + if (f2fs_dev_is_reserving(sbi, devi)) > > + return 0; > > return !test_bit(segno, free_i->free_segmap); > > + } > > return 0; > > } > > > > @@ -2778,6 +2841,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, > > unsigned int alloc_policy = sbi->allocate_section_policy; > > unsigned int alloc_hint = sbi->allocate_section_hint; > > bool init = true; > > + bool looped = false; > > int i; > > int ret = 0; > > > > @@ -2791,8 +2855,13 @@ static int get_new_segment(struct f2fs_sb_info *sbi, > > if (!new_sec && ((*newseg + 1) % SEGS_PER_SEC(sbi))) { > > segno = find_next_zero_bit(free_i->free_segmap, > > GET_SEG_FROM_SEC(sbi, hint + 1), *newseg + 1); > > - if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) > > + if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) { > > + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); > > + > > + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) > > + goto find_other_zone; > > goto got_it; > > + } > > } > > > > #ifdef CONFIG_BLK_DEV_ZONED > > @@ -2828,33 +2897,50 @@ static int get_new_segment(struct f2fs_sb_info *sbi, > > find_other_zone: > > secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); > > > > -#ifdef CONFIG_BLK_DEV_ZONED > > - if (secno >= MAIN_SECS(sbi) && f2fs_sb_has_blkzoned(sbi)) { > > - /* Write only to sequential zones */ > > - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) { > > - hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); > > - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); > > - } else > > - secno = find_first_zero_bit(free_i->free_secmap, > > - MAIN_SECS(sbi)); > > - if (secno >= MAIN_SECS(sbi)) { > > - ret = -ENOSPC; > > - f2fs_bug_on(sbi, 1); > > - goto out_unlock; > > - } > > - } > > -#endif > > - > > if (secno >= MAIN_SECS(sbi)) { > > - secno = find_first_zero_bit(free_i->free_secmap, > > - MAIN_SECS(sbi)); > > - if (secno >= MAIN_SECS(sbi)) { > > + if (looped) { > > ret = -ENOSPC; > > f2fs_bug_on(sbi, !pinning); > > goto out_unlock; > > } > > +#ifdef CONFIG_BLK_DEV_ZONED > > + /* Write only to sequential zones */ > > + if (f2fs_sb_has_blkzoned(sbi) && > > + sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) > > + hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); > > + else > > +#endif > > + hint = 0; > > + looped = true; > > + goto find_other_zone; > > } > > + > > segno = GET_SEG_FROM_SEC(sbi, secno); > > + > > + if (f2fs_sb_has_device_alias(sbi) && f2fs_is_multi_device(sbi)) { > > + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); > > + > > + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { > > + unsigned int end_segno; > > + > > + while (devi < sbi->s_ndevs && > > + f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { > > + block_t next_blk; > > + > > + end_segno = GET_SEGNO(sbi, FDEV(devi).end_blk); > > + hint = GET_SEC_FROM_SEG(sbi, end_segno) + 1; > > + > > + if (hint >= MAIN_SECS(sbi) || ++devi >= sbi->s_ndevs) > > + break; > > + > > + next_blk = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, hint)); > > + if (next_blk < FDEV(devi).start_blk || > > + next_blk > FDEV(devi).end_blk) > > + break; > > + } > > + goto find_other_zone; > > + } > > + } > > zoneno = GET_ZONE_FROM_SEC(sbi, secno); > > > > /* give up on finding another zone */ > > diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h > > index b0c06b3580b4..259e4a29b494 100644 > > --- a/fs/f2fs/segment.h > > +++ b/fs/f2fs/segment.h > > @@ -954,10 +954,36 @@ static inline block_t sum_blk_addr(struct f2fs_sb_info *sbi, int base, int type) > > - (base + 1) + type; > > } > > > > +static inline bool f2fs_dev_is_reserving(struct f2fs_sb_info *sbi, int devi) > > +{ > > + if (!f2fs_sb_has_device_alias(sbi) || !f2fs_is_multi_device(sbi)) > > + return false; > > + return FDEV(devi).is_reserving; > > +} > > + > > +static inline bool f2fs_dev_is_alloc_blocked(struct f2fs_sb_info *sbi, > > + int devi, bool pinning) > > +{ > > + if (!f2fs_sb_has_device_alias(sbi) || !f2fs_is_multi_device(sbi)) > > + return false; > > + return (pinning && FDEV(devi).has_alias) || FDEV(devi).is_reserving; > > +} > > + > > static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno) > > { > > if (is_cursec(sbi, secno) || (sbi->cur_victim_sec == secno)) > > return true; > > + if (f2fs_sb_has_device_alias(sbi) && f2fs_is_multi_device(sbi)) { > > + int i; > > + block_t start_blk = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, secno)); > > + > > + for (i = 0; i < sbi->s_ndevs; i++) { > > + if (f2fs_dev_is_reserving(sbi, i) && > > + start_blk >= FDEV(i).start_blk && > > + start_blk <= FDEV(i).end_blk) > > + return true; > > + } > > + } > > return false; > > } > > > > diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c > > index c448d992ff2a..8359eed903be 100644 > > --- a/fs/f2fs/super.c > > +++ b/fs/f2fs/super.c > > @@ -5001,6 +5001,38 @@ static void f2fs_tuning_parameters(struct f2fs_sb_info *sbi) > > sbi->readdir_ra = true; > > } > > > > +static void f2fs_restore_device_alias(struct f2fs_sb_info *sbi) > > +{ > > + struct inode *root = d_inode(sbi->sb->s_root); > > + struct f2fs_dir_entry *de; > > + struct folio *folio; > > + int i; > > + > > + if (!f2fs_sb_has_device_alias(sbi)) > > + return; > > + > > + for (i = 1; i < sbi->s_ndevs; i++) { > > + char *name = strrchr(FDEV(i).path, '/'); > > + struct qstr qstr; > > + > > + name = name ? name + 1 : FDEV(i).path; > > + qstr.name = name; > > + qstr.len = strlen(name); > > + > > + de = f2fs_find_entry(root, &qstr, &folio); > > + if (de) { > > + struct inode *inode = f2fs_iget(sbi->sb, le32_to_cpu(de->ino)); > > + > > + if (!IS_ERR(inode)) { > > + if (IS_DEVICE_ALIASING(inode)) > > + FDEV(i).has_alias = true; > > + iput(inode); > > + } > > + f2fs_folio_put(folio, 0); > > + } > > + } > > +} > > + > > static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) > > { > > struct f2fs_fs_context *ctx = fc->fs_private; > > @@ -5436,6 +5468,8 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) > > f2fs_update_time(sbi, REQ_TIME); > > clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); > > > > + f2fs_restore_device_alias(sbi); > > + > > sbi->umount_lock_holder = NULL; > > return 0; > > > > diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h > > index 795e26258355..4409ada2fecb 100644 > > --- a/include/uapi/linux/f2fs.h > > +++ b/include/uapi/linux/f2fs.h > > @@ -45,6 +45,9 @@ > > #define F2FS_IOC_START_ATOMIC_REPLACE _IO(F2FS_IOCTL_MAGIC, 25) > > #define F2FS_IOC_GET_DEV_ALIAS_FILE _IOR(F2FS_IOCTL_MAGIC, 26, __u32) > > #define F2FS_IOC_IO_PRIO _IOW(F2FS_IOCTL_MAGIC, 27, __u32) > > +#define F2FS_IOC_RESERVE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 28) > > +#define F2FS_IOC_RELEASE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 29) > > +#define F2FS_IOC_GET_DEV_ALIAS_STATUS _IOR(F2FS_IOCTL_MAGIC, 30, __u32) > > > > /* > > * should be same as XFS_IOC_GOINGDOWN. > > @@ -70,6 +73,10 @@ enum { > > F2FS_IOPRIO_MAX, > > }; > > > > +/* for F2FS_IOC_GET_DEV_ALIAS_STATUS */ > > +#define F2FS_DEV_ALIAS_STATUS_RELEASED 0 > > +#define F2FS_DEV_ALIAS_STATUS_RESERVED 1 > > + > > struct f2fs_gc_range { > > __u32 sync; > > __u64 start; > _______________________________________________ Linux-f2fs-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel