]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
f2fs: introduce device aliasing file
authorDaeho Jeong <daehojeong@google.com>
Thu, 17 Oct 2024 17:31:53 +0000 (10:31 -0700)
committerJaegeuk Kim <jaegeuk@kernel.org>
Fri, 1 Nov 2024 01:19:00 +0000 (01:19 +0000)
F2FS should understand how the device aliasing file works and support
deleting the file after use. A device aliasing file can be created by
mkfs.f2fs tool and it can map the whole device with an extent, not
using node blocks. The file space should be pinned and normally used for
read-only usages.

Signed-off-by: Daeho Jeong <daehojeong@google.com>
Signed-off-by: Chao Yu <chao@kernel.org>
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
Documentation/filesystems/f2fs.rst
fs/f2fs/data.c
fs/f2fs/extent_cache.c
fs/f2fs/f2fs.h
fs/f2fs/file.c
fs/f2fs/inode.c
fs/f2fs/super.c
fs/f2fs/sysfs.c
include/uapi/linux/f2fs.h

index 68a0885fb5e69eaaf797f4ed17faa4a6654de2fc..fb7d2ee022bc06f5dfb3e45e339e353bc5d3b5cd 100644 (file)
@@ -943,3 +943,47 @@ NVMe Zoned Namespace devices
   can start before the zone-capacity and span across zone-capacity boundary.
   Such spanning segments are also considered as usable segments. All blocks
   past the zone-capacity are considered unusable in these segments.
+
+Device aliasing feature
+-----------------------
+
+f2fs can utilize a special file called a "device aliasing file." This file allows
+the entire storage device to be mapped with a single, large extent, not using
+the usual f2fs node structures. This mapped area is pinned and primarily intended
+for holding the space.
+
+Essentially, this mechanism allows a portion of the f2fs area to be temporarily
+reserved and used by another filesystem or for different purposes. Once that
+external usage is complete, the device aliasing file can be deleted, releasing
+the reserved space back to F2FS for its own use.
+
+<use-case>
+
+# ls /dev/vd*
+/dev/vdb (32GB) /dev/vdc (32GB)
+# mkfs.ext4 /dev/vdc
+# mkfs.f2fs -c /dev/vdc@vdc.file /dev/vdb
+# mount /dev/vdb /mnt/f2fs
+# ls -l /mnt/f2fs
+vdc.file
+# df -h
+/dev/vdb                            64G   33G   32G  52% /mnt/f2fs
+
+# mount -o loop /dev/vdc /mnt/ext4
+# df -h
+/dev/vdb                            64G   33G   32G  52% /mnt/f2fs
+/dev/loop7                          32G   24K   30G   1% /mnt/ext4
+# umount /mnt/ext4
+
+# f2fs_io getflags /mnt/f2fs/vdc.file
+get a flag on /mnt/f2fs/vdc.file ret=0, flags=nocow(pinned),immutable
+# f2fs_io setflags noimmutable /mnt/f2fs/vdc.file
+get a flag on noimmutable ret=0, flags=800010
+set a flag on /mnt/f2fs/vdc.file ret=0, flags=noimmutable
+# rm /mnt/f2fs/vdc.file
+# df -h
+/dev/vdb                            64G  753M   64G   2% /mnt/f2fs
+
+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.
index 94f7b084f6016490caba8a328b060f7dfc8a9e15..90fa8ab851943e3caddab7a624e80a97aa247fc5 100644 (file)
@@ -3441,6 +3441,11 @@ restart:
 
        if (!f2fs_lookup_read_extent_cache_block(inode, index,
                                                 &dn.data_blkaddr)) {
+               if (IS_DEVICE_ALIASING(inode)) {
+                       err = -ENODATA;
+                       goto out;
+               }
+
                if (locked) {
                        err = f2fs_reserve_block(&dn, index);
                        goto out;
index 62ac440d94168a452512274209fbf3d57cbbf59a..019c1f7b7fa53fd55075d58451f0b20eb5579257 100644 (file)
@@ -24,6 +24,7 @@ bool sanity_check_extent_cache(struct inode *inode, struct page *ipage)
        struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
        struct f2fs_extent *i_ext = &F2FS_INODE(ipage)->i_ext;
        struct extent_info ei;
+       int devi;
 
        get_read_extent_info(&ei, i_ext);
 
@@ -38,7 +39,36 @@ bool sanity_check_extent_cache(struct inode *inode, struct page *ipage)
                          ei.blk, ei.fofs, ei.len);
                return false;
        }
-       return true;
+
+       if (!IS_DEVICE_ALIASING(inode))
+               return true;
+
+       for (devi = 0; devi < sbi->s_ndevs; devi++) {
+               if (FDEV(devi).start_blk != ei.blk ||
+                               FDEV(devi).end_blk != ei.blk + ei.len - 1)
+                       continue;
+
+               if (devi == 0) {
+                       f2fs_warn(sbi,
+                           "%s: inode (ino=%lx) is an alias of meta device",
+                           __func__, inode->i_ino);
+                       return false;
+               }
+
+               if (bdev_is_zoned(FDEV(devi).bdev)) {
+                       f2fs_warn(sbi,
+                           "%s: device alias inode (ino=%lx)'s extent info "
+                           "[%u, %u, %u] maps to zoned block device",
+                           __func__, inode->i_ino, ei.blk, ei.fofs, ei.len);
+                       return false;
+               }
+               return true;
+       }
+
+       f2fs_warn(sbi, "%s: device alias inode (ino=%lx)'s extent info "
+                       "[%u, %u, %u] is inconsistent w/ any devices",
+                       __func__, inode->i_ino, ei.blk, ei.fofs, ei.len);
+       return false;
 }
 
 static void __set_extent_info(struct extent_info *ei,
@@ -76,6 +106,9 @@ static bool __init_may_extent_tree(struct inode *inode, enum extent_type type)
 
 static bool __may_extent_tree(struct inode *inode, enum extent_type type)
 {
+       if (IS_DEVICE_ALIASING(inode) && type == EX_READ)
+               return true;
+
        /*
         * for recovered files during mount do not create extents
         * if shrinker is not registered.
@@ -401,6 +434,11 @@ void f2fs_init_read_extent_tree(struct inode *inode, struct page *ipage)
        if (atomic_read(&et->node_cnt) || !ei.len)
                goto skip;
 
+       if (IS_DEVICE_ALIASING(inode)) {
+               et->largest = ei;
+               goto skip;
+       }
+
        en = __attach_extent_node(sbi, et, &ei, NULL,
                                &et->root.rb_root.rb_node, true);
        if (en) {
@@ -463,6 +501,11 @@ static bool __lookup_extent_tree(struct inode *inode, pgoff_t pgofs,
                goto out;
        }
 
+       if (IS_DEVICE_ALIASING(inode)) {
+               ret = false;
+               goto out;
+       }
+
        en = __lookup_extent_node(&et->root, et->cached_en, pgofs);
        if (!en)
                goto out;
index dd47dbf6d9e684eb7a9640301cc0ce13863599b1..f3ef4dc509921cb1a3eec15eb73c357a1dc2b14f 100644 (file)
@@ -213,6 +213,7 @@ struct f2fs_mount_info {
 #define F2FS_FEATURE_CASEFOLD                  0x00001000
 #define F2FS_FEATURE_COMPRESSION               0x00002000
 #define F2FS_FEATURE_RO                                0x00004000
+#define F2FS_FEATURE_DEVICE_ALIAS              0x00008000
 
 #define __F2FS_HAS_FEATURE(raw_super, mask)                            \
        ((raw_super->feature & cpu_to_le32(mask)) != 0)
@@ -3046,6 +3047,7 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
 #define F2FS_DIRSYNC_FL                        0x00010000 /* dirsync behaviour (directories only) */
 #define F2FS_PROJINHERIT_FL            0x20000000 /* Create with parents projid */
 #define F2FS_CASEFOLD_FL               0x40000000 /* Casefolded file */
+#define F2FS_DEVICE_ALIAS_FL           0x80000000 /* File for aliasing a device */
 
 #define F2FS_QUOTA_DEFAULT_FL          (F2FS_NOATIME_FL | F2FS_IMMUTABLE_FL)
 
@@ -3061,6 +3063,8 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
 /* Flags that are appropriate for non-directories/regular files. */
 #define F2FS_OTHER_FLMASK      (F2FS_NODUMP_FL | F2FS_NOATIME_FL)
 
+#define IS_DEVICE_ALIASING(inode)      (F2FS_I(inode)->i_flags & F2FS_DEVICE_ALIAS_FL)
+
 static inline __u32 f2fs_mask_flags(umode_t mode, __u32 flags)
 {
        if (S_ISDIR(mode))
@@ -4526,6 +4530,7 @@ F2FS_FEATURE_FUNCS(sb_chksum, SB_CHKSUM);
 F2FS_FEATURE_FUNCS(casefold, CASEFOLD);
 F2FS_FEATURE_FUNCS(compression, COMPRESSION);
 F2FS_FEATURE_FUNCS(readonly, RO);
+F2FS_FEATURE_FUNCS(device_alias, DEVICE_ALIAS);
 
 #ifdef CONFIG_BLK_DEV_ZONED
 static inline bool f2fs_blkz_is_seq(struct f2fs_sb_info *sbi, int devi,
index 348ef73bf8dd3770d69470ec78ce0ec9b009d961..75a8b22da664d69d66678f06b855e47f33a9cc18 100644 (file)
@@ -725,6 +725,11 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock)
 
        trace_f2fs_truncate_blocks_enter(inode, from);
 
+       if (IS_DEVICE_ALIASING(inode) && from) {
+               err = -EINVAL;
+               goto out_err;
+       }
+
        free_from = (pgoff_t)F2FS_BLK_ALIGN(from);
 
        if (free_from >= max_file_blocks(inode))
@@ -739,6 +744,21 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock)
                goto out;
        }
 
+       if (IS_DEVICE_ALIASING(inode)) {
+               struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ];
+               struct extent_info ei = et->largest;
+               unsigned int i;
+
+               for (i = 0; i < ei.len; i++)
+                       f2fs_invalidate_blocks(sbi, ei.blk + i);
+
+               dec_valid_block_count(sbi, inode, ei.len);
+               f2fs_update_time(sbi, REQ_TIME);
+
+               f2fs_put_page(ipage, 1);
+               goto out;
+       }
+
        if (f2fs_has_inline_data(inode)) {
                f2fs_truncate_inline_inode(inode, ipage, from);
                f2fs_put_page(ipage, 1);
@@ -774,7 +794,7 @@ free_partial:
        /* lastly zero out the first data page */
        if (!err)
                err = truncate_partial_data_page(inode, from, truncate_page);
-
+out_err:
        trace_f2fs_truncate_blocks_exit(inode, err);
        return err;
 }
@@ -992,7 +1012,8 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                return -EPERM;
 
        if ((attr->ia_valid & ATTR_SIZE)) {
-               if (!f2fs_is_compress_backend_ready(inode))
+               if (!f2fs_is_compress_backend_ready(inode) ||
+                               IS_DEVICE_ALIASING(inode))
                        return -EOPNOTSUPP;
                if (is_inode_flag_set(inode, FI_COMPRESS_RELEASED) &&
                        !IS_ALIGNED(attr->ia_size,
@@ -1861,7 +1882,7 @@ static long f2fs_fallocate(struct file *file, int mode,
                return -EIO;
        if (!f2fs_is_checkpoint_ready(F2FS_I_SB(inode)))
                return -ENOSPC;
-       if (!f2fs_is_compress_backend_ready(inode))
+       if (!f2fs_is_compress_backend_ready(inode) || IS_DEVICE_ALIASING(inode))
                return -EOPNOTSUPP;
 
        /* f2fs only support ->fallocate for regular file */
@@ -3297,6 +3318,9 @@ int f2fs_pin_file_control(struct inode *inode, bool inc)
        struct f2fs_inode_info *fi = F2FS_I(inode);
        struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 
+       if (IS_DEVICE_ALIASING(inode))
+               return -EINVAL;
+
        if (fi->i_gc_failures >= sbi->gc_pin_file_threshold) {
                f2fs_warn(sbi, "%s: Enable GC = ino %lx after %x GC trials",
                          __func__, inode->i_ino, fi->i_gc_failures);
@@ -3327,6 +3351,9 @@ static int f2fs_ioc_set_pin_file(struct file *filp, unsigned long arg)
        if (f2fs_readonly(sbi->sb))
                return -EROFS;
 
+       if (!pin && IS_DEVICE_ALIASING(inode))
+               return -EOPNOTSUPP;
+
        ret = mnt_want_write_file(filp);
        if (ret)
                return ret;
@@ -3392,6 +3419,12 @@ static int f2fs_ioc_get_pin_file(struct file *filp, unsigned long arg)
        return put_user(pin, (u32 __user *)arg);
 }
 
+static int f2fs_ioc_get_dev_alias_file(struct file *filp, unsigned long arg)
+{
+       return put_user(IS_DEVICE_ALIASING(file_inode(filp)) ? 1 : 0,
+                       (u32 __user *)arg);
+}
+
 int f2fs_precache_extents(struct inode *inode)
 {
        struct f2fs_inode_info *fi = F2FS_I(inode);
@@ -4491,6 +4524,8 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return f2fs_ioc_decompress_file(filp);
        case F2FS_IOC_COMPRESS_FILE:
                return f2fs_ioc_compress_file(filp);
+       case F2FS_IOC_GET_DEV_ALIAS_FILE:
+               return f2fs_ioc_get_dev_alias_file(filp, arg);
        default:
                return -ENOTTY;
        }
@@ -4766,7 +4801,8 @@ static int f2fs_preallocate_blocks(struct kiocb *iocb, struct iov_iter *iter,
        else
                return 0;
 
-       map.m_may_create = true;
+       if (!IS_DEVICE_ALIASING(inode))
+               map.m_may_create = true;
        if (dio) {
                map.m_seg_type = f2fs_rw_hint_to_seg_type(sbi,
                                                inode->i_write_hint);
@@ -5203,6 +5239,7 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        case F2FS_IOC_SET_COMPRESS_OPTION:
        case F2FS_IOC_DECOMPRESS_FILE:
        case F2FS_IOC_COMPRESS_FILE:
+       case F2FS_IOC_GET_DEV_ALIAS_FILE:
                break;
        default:
                return -ENOIOCTLCMD;
index 10780e37fc7b68226e2a63e51461b2486a2fb75c..282fd320bdb352e51b3c1044b8afb90b3fb2e6b8 100644 (file)
@@ -372,6 +372,19 @@ static bool sanity_check_inode(struct inode *inode, struct page *node_page)
                return false;
        }
 
+       if (IS_DEVICE_ALIASING(inode)) {
+               if (!f2fs_sb_has_device_alias(sbi)) {
+                       f2fs_warn(sbi, "%s: inode (ino=%lx) has device alias flag, but the feature is off",
+                                 __func__, inode->i_ino);
+                       return false;
+               }
+               if (!f2fs_is_pinned_file(inode)) {
+                       f2fs_warn(sbi, "%s: inode (ino=%lx) has device alias flag, but is not pinned",
+                                 __func__, inode->i_ino);
+                       return false;
+               }
+       }
+
        return true;
 }
 
@@ -825,7 +838,8 @@ void f2fs_evict_inode(struct inode *inode)
        f2fs_bug_on(sbi, get_dirty_pages(inode));
        f2fs_remove_dirty_inode(inode);
 
-       f2fs_destroy_extent_tree(inode);
+       if (!IS_DEVICE_ALIASING(inode))
+               f2fs_destroy_extent_tree(inode);
 
        if (inode->i_nlink || is_bad_inode(inode))
                goto no_delete;
@@ -881,6 +895,9 @@ retry:
                goto retry;
        }
 
+       if (IS_DEVICE_ALIASING(inode))
+               f2fs_destroy_extent_tree(inode);
+
        if (err) {
                f2fs_update_inode_page(inode);
                if (dquot_initialize_needed(inode))
index 8d4ecb2e855e6719299d5c2460aff81bb2acfbde..aa14c8fce7d9f8d56fb64a8b1fe69cdc7723fe33 100644 (file)
@@ -834,6 +834,10 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
                        set_opt(sbi, READ_EXTENT_CACHE);
                        break;
                case Opt_noextent_cache:
+                       if (F2FS_HAS_FEATURE(sbi, F2FS_FEATURE_DEVICE_ALIAS)) {
+                               f2fs_err(sbi, "device aliasing requires extent cache");
+                               return -EINVAL;
+                       }
                        clear_opt(sbi, READ_EXTENT_CACHE);
                        break;
                case Opt_noinline_data:
index c56e8c8739352337ecaedecba5c90e147c6a7f1e..e51304bc65eae8c0c978677b45a8f7e5031ad232 100644 (file)
@@ -1313,6 +1313,7 @@ F2FS_SB_FEATURE_RO_ATTR(sb_checksum, SB_CHKSUM);
 F2FS_SB_FEATURE_RO_ATTR(casefold, CASEFOLD);
 F2FS_SB_FEATURE_RO_ATTR(compression, COMPRESSION);
 F2FS_SB_FEATURE_RO_ATTR(readonly, RO);
+F2FS_SB_FEATURE_RO_ATTR(device_alias, DEVICE_ALIAS);
 
 static struct attribute *f2fs_sb_feat_attrs[] = {
        ATTR_LIST(sb_encryption),
@@ -1329,6 +1330,7 @@ static struct attribute *f2fs_sb_feat_attrs[] = {
        ATTR_LIST(sb_casefold),
        ATTR_LIST(sb_compression),
        ATTR_LIST(sb_readonly),
+       ATTR_LIST(sb_device_alias),
        NULL,
 };
 ATTRIBUTE_GROUPS(f2fs_sb_feat);
index 955d440be1046b428ef7000e328da689069d561d..f7aaf8d23e20c2064fd785e7629eeb0357d5e102 100644 (file)
@@ -43,6 +43,7 @@
 #define F2FS_IOC_DECOMPRESS_FILE       _IO(F2FS_IOCTL_MAGIC, 23)
 #define F2FS_IOC_COMPRESS_FILE         _IO(F2FS_IOCTL_MAGIC, 24)
 #define F2FS_IOC_START_ATOMIC_REPLACE  _IO(F2FS_IOCTL_MAGIC, 25)
+#define F2FS_IOC_GET_DEV_ALIAS_FILE    _IOR(F2FS_IOCTL_MAGIC, 26, __u32)
 
 /*
  * should be same as XFS_IOC_GOINGDOWN.