From 3f8bbbb5801ce4eca6d0b177781679067209b3dc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 25 Aug 2026 23:30:02 -0700 Subject: [PATCH] ceph: disable inline data support Inline data support has been deprecated in the CephFS MDS and is being formally removed. Existing inline data can be migrated to data pool objects via MDS scrub (see PR #44359). Add CONFIG_CEPH_FS_INLINE_DATA (default n) to gate all inline data code in the kclient. When disabled, CEPH_FEATURE_MDS_INLINE_DATA is not advertised to the MDS, which will then refuse to open files that still have inline data, returning -EPERM and preventing silent data corruption that would occur if the kclient tried to read inline data from OSD objects that do not contain it. The conditional compilation is centralized in fs/ceph/super.h, where static inline helpers make all the inline data code compile out cleanly when the option is disabled, so that removing the option later only touches that one place. ceph_inline_version() returns CEPH_INLINE_NONE when the option is disabled, which makes every version comparison in the disabled build constant-fold to false; the stubbed out ceph_uninline_data() and ceph_fill_inline_data() are then unreachable. The MDS still carries inline_version + inline_data on the wire, so the reply and cap grant parsers keep consuming those fields even with the option disabled, only skipping them, to keep the decode pointer in sync for the fields that follow. This config option will be kept for a transitional period to allow existing CephFS deployments to migrate inline data via MDS scrub. Once the migration period ends, the option and all inline data code will be permanently removed. Users upgrading from an older Ceph release should ensure all inline data has been migrated before deploying a kclient with this option disabled: 1. Enable and run MDS scrub to uninline existing data 2. Disable inline_data on the filesystem: ceph fs set inline_data false --yes-i-really-really-mean-it Link: https://tracker.ceph.com/issues/67769 Signed-off-by: Xiubo Li Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/Kconfig | 13 +++++++ fs/ceph/addr.c | 13 ++++++- fs/ceph/caps.c | 42 ++++++++++++++++----- fs/ceph/inode.c | 6 +-- fs/ceph/mds_client.c | 45 +++++++++++++++++------ fs/ceph/snap.c | 2 +- fs/ceph/super.h | 59 +++++++++++++++++++++++++++--- include/linux/ceph/ceph_features.h | 14 ++++++- include/linux/ceph/ceph_fs.h | 5 +++ 9 files changed, 167 insertions(+), 32 deletions(-) diff --git a/fs/ceph/Kconfig b/fs/ceph/Kconfig index 3d64a316ca31..3652e55ce36f 100644 --- a/fs/ceph/Kconfig +++ b/fs/ceph/Kconfig @@ -17,6 +17,19 @@ config CEPH_FS If unsure, say N. if CEPH_FS + +config CEPH_FS_INLINE_DATA + bool "Enable CephFS inline data support (deprecated)" + depends on CEPH_FS + default n + help + Inline data support has been deprecated in the CephFS MDS + and is being formally removed. Existing inline data can be + migrated to data pool objects via MDS scrub. Choose N + unless you have a legacy filesystem that requires it, + and ensure all inline data has been migrated (see + ceph fs set inline_data false) before enabling. + config CEPH_FSCACHE bool "Enable Ceph client caching support" depends on CEPH_FS=m && FSCACHE || CEPH_FS=y && FSCACHE=y diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 18060dac8d92..31a3175fdd7a 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -277,6 +277,7 @@ static void finish_netfs_read(struct ceph_osd_request *req) ceph_dec_osd_stopping_blocker(fsc->mdsc); } +#ifdef CONFIG_CEPH_FS_INLINE_DATA static bool ceph_netfs_issue_op_inline(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; @@ -337,6 +338,12 @@ out: netfs_read_subreq_terminated(subreq); return true; } +#else +static bool ceph_netfs_issue_op_inline(struct netfs_io_subrequest *subreq) +{ + return false; +} +#endif /* CONFIG_CEPH_FS_INLINE_DATA */ static int ceph_netfs_prepare_read(struct netfs_io_subrequest *subreq) { @@ -2181,6 +2188,7 @@ out_free: return ret; } +#ifdef CONFIG_CEPH_FS_INLINE_DATA void ceph_fill_inline_data(struct inode *inode, struct page *locked_page, char *data, size_t len) { @@ -2242,7 +2250,7 @@ int ceph_uninline_data(struct file *file) u64 len; spin_lock(&ci->i_ceph_lock); - inline_version = ci->i_inline_version; + inline_version = ceph_inline_version(ci); spin_unlock(&ci->i_ceph_lock); doutc(cl, "%llx.%llx inline_version %llu\n", ceph_vinop(inode), @@ -2358,7 +2366,7 @@ out_uninline: /* Set to CAP_INLINE_NONE and dirty the caps */ down_read(&fsc->mdsc->snap_rwsem); spin_lock(&ci->i_ceph_lock); - ci->i_inline_version = CEPH_INLINE_NONE; + ceph_set_inline_version(ci, CEPH_INLINE_NONE); dirty = __ceph_mark_dirty_caps(ci, CEPH_CAP_FILE_WR, &prealloc_cf); spin_unlock(&ci->i_ceph_lock); up_read(&fsc->mdsc->snap_rwsem); @@ -2381,6 +2389,7 @@ out: ceph_vinop(inode), inline_version, err); return err; } +#endif /* CONFIG_CEPH_FS_INLINE_DATA */ static const struct vm_operations_struct ceph_vmops = { .fault = ceph_filemap_fault, diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 645d9096674d..1847badddd87 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1500,7 +1500,7 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci, arg->gid = inode->i_gid; arg->mode = inode->i_mode; - arg->inline_data = ci->i_inline_version != CEPH_INLINE_NONE; + arg->inline_data = ceph_inline_version(ci) != CEPH_INLINE_NONE; if (!(flags & CEPH_CLIENT_CAPS_PENDING_CAPSNAP) && !list_empty(&ci->i_cap_snaps)) { struct ceph_cap_snap *capsnap; @@ -3541,6 +3541,32 @@ struct cap_extra_info { u64 fscrypt_file_size; }; +/* + * Cap grant messages (msg_version >= 4) always carry inline_version + * followed by inline_data. With inline data support disabled we still + * have to consume the fields, we just don't keep the data around. + */ +static int parse_inline_data(void **p, void *end, + struct cap_extra_info *extra_info) +{ +#ifdef CONFIG_CEPH_FS_INLINE_DATA + ceph_decode_64_safe(p, end, extra_info->inline_version, bad); + ceph_decode_32_safe(p, end, extra_info->inline_len, bad); + ceph_decode_need(p, end, extra_info->inline_len, bad); + extra_info->inline_data = *p; + *p += extra_info->inline_len; +#else + u32 len; + + ceph_decode_skip_64(p, end, bad); + ceph_decode_32_safe(p, end, len, bad); + ceph_decode_skip_n(p, end, len, bad); +#endif + return 0; +bad: + return -EIO; +} + /* * Handle a cap GRANT message from the MDS. (Note that a GRANT may * actually be a revocation if it specifies a smaller cap set.) @@ -3833,9 +3859,9 @@ static void handle_cap_grant(struct inode *inode, } if (extra_info->inline_version > 0 && - extra_info->inline_version >= ci->i_inline_version) { - ci->i_inline_version = extra_info->inline_version; - if (ci->i_inline_version != CEPH_INLINE_NONE && + extra_info->inline_version >= ceph_inline_version(ci)) { + ceph_set_inline_version(ci, extra_info->inline_version); + if (extra_info->inline_version != CEPH_INLINE_NONE && (newcaps & (CEPH_CAP_FILE_CACHE|CEPH_CAP_FILE_LAZYIO))) fill_inline = true; } @@ -4427,6 +4453,7 @@ void ceph_handle_caps(struct ceph_mds_session *session, struct ceph_snap_realm *realm = NULL; int op; int msg_version = le16_to_cpu(msg->hdr.version); + int err; u32 seq, mseq, issue_seq; struct ceph_vino vino; void *snaptrace; @@ -4478,12 +4505,9 @@ void ceph_handle_caps(struct ceph_mds_session *session, } if (msg_version >= 4) { - ceph_decode_64_safe(&p, end, extra_info.inline_version, bad); - ceph_decode_32_safe(&p, end, extra_info.inline_len, bad); - if (p + extra_info.inline_len > end) + err = parse_inline_data(&p, end, &extra_info); + if (err < 0) goto bad; - extra_info.inline_data = p; - p += extra_info.inline_len; } if (msg_version >= 5) { diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index f913b8289976..b1282cd26526 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -636,7 +636,7 @@ struct inode *ceph_alloc_inode(struct super_block *sb) spin_lock_init(&ci->i_ceph_lock); ci->i_version = 0; - ci->i_inline_version = 0; + ceph_set_inline_version(ci, 0); ci->i_time_warp_seq = 0; ci->i_ceph_flags = 0; atomic64_set(&ci->i_ordered_count, 1); @@ -1372,9 +1372,9 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page, } if (iinfo->inline_version > 0 && - iinfo->inline_version >= ci->i_inline_version) { + iinfo->inline_version >= ceph_inline_version(ci)) { int cache_caps = CEPH_CAP_FILE_CACHE | CEPH_CAP_FILE_LAZYIO; - ci->i_inline_version = iinfo->inline_version; + ceph_set_inline_version(ci, iinfo->inline_version); if (ceph_has_inline_data(ci) && (locked_page || (info_caps & cache_caps))) fill_inline = true; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 9a07deae1421..e23bb16c43cb 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -114,6 +114,32 @@ bad: return -EIO; } +/* + * Decode the inline_version + inline_data fields the MDS puts into + * reply inodes. With inline data support disabled we still have to + * consume them, we just don't keep the data around. + */ +static int parse_reply_info_inline_data(void **p, void *end, + struct ceph_mds_reply_info_in *info) +{ +#ifdef CONFIG_CEPH_FS_INLINE_DATA + ceph_decode_64_safe(p, end, info->inline_version, bad); + ceph_decode_32_safe(p, end, info->inline_len, bad); + ceph_decode_need(p, end, info->inline_len, bad); + info->inline_data = *p; + *p += info->inline_len; +#else + u32 len; + + ceph_decode_skip_64(p, end, bad); + ceph_decode_32_safe(p, end, len, bad); + ceph_decode_skip_n(p, end, len, bad); +#endif + return 0; +bad: + return -EIO; +} + static int parse_reply_info_in(void **p, void *end, struct ceph_mds_reply_info_in *info, u64 features, @@ -158,11 +184,9 @@ static int parse_reply_info_in(void **p, void *end, if (features == (u64)-1) { /* inline data */ - ceph_decode_64_safe(p, end, info->inline_version, bad); - ceph_decode_32_safe(p, end, info->inline_len, bad); - ceph_decode_need(p, end, info->inline_len, bad); - info->inline_data = *p; - *p += info->inline_len; + err = parse_reply_info_inline_data(p, end, info); + if (err < 0) + goto out_bad; /* quota */ err = parse_reply_info_quota(p, end, info); if (err < 0) @@ -278,13 +302,12 @@ static int parse_reply_info_in(void **p, void *end, } else { /* legacy (unversioned) struct */ if (features & CEPH_FEATURE_MDS_INLINE_DATA) { - ceph_decode_64_safe(p, end, info->inline_version, bad); - ceph_decode_32_safe(p, end, info->inline_len, bad); - ceph_decode_need(p, end, info->inline_len, bad); - info->inline_data = *p; - *p += info->inline_len; - } else + err = parse_reply_info_inline_data(p, end, info); + if (err < 0) + goto out_bad; + } else { info->inline_version = CEPH_INLINE_NONE; + } if (features & CEPH_FEATURE_MDS_QUOTA) { err = parse_reply_info_quota(p, end, info); diff --git a/fs/ceph/snap.c b/fs/ceph/snap.c index f064b5ad3bf4..498ffc1a91cb 100644 --- a/fs/ceph/snap.c +++ b/fs/ceph/snap.c @@ -613,7 +613,7 @@ static void ceph_queue_cap_snap(struct ceph_inode_info *ci, capsnap->xattr_version = 0; } - capsnap->inline_data = ci->i_inline_version != CEPH_INLINE_NONE; + capsnap->inline_data = ceph_inline_version(ci) != CEPH_INLINE_NONE; /* dirty page count moved from _head to this cap_snap; all subsequent writes page dirties occur _after_ this diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 42287dba9e64..a1c0f28ed4b2 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -401,7 +401,9 @@ struct ceph_inode_info { spinlock_t i_ceph_lock; u64 i_version; +#ifdef CONFIG_CEPH_FS_INLINE_DATA u64 i_inline_version; +#endif u32 i_time_warp_seq; unsigned long i_ceph_flags; @@ -1404,15 +1406,64 @@ extern void __ceph_touch_fmode(struct ceph_inode_info *ci, extern const struct address_space_operations ceph_aops; extern const struct netfs_request_ops ceph_netfs_ops; int ceph_mmap_prepare(struct vm_area_desc *desc); -extern int ceph_uninline_data(struct file *file); extern int ceph_pool_perm_check(struct inode *inode, int need); extern void ceph_pool_perm_destroy(struct ceph_mds_client* mdsc); int ceph_purge_inode_cap(struct inode *inode, struct ceph_cap *cap, bool *invalidate); +#ifdef CONFIG_CEPH_FS_INLINE_DATA +extern int ceph_uninline_data(struct file *file); +extern void ceph_fill_inline_data(struct inode *inode, struct page *locked_page, + char *data, size_t len); + +static inline u64 ceph_inline_version(const struct ceph_inode_info *ci) +{ + return ci->i_inline_version; +} + +static inline void ceph_set_inline_version(struct ceph_inode_info *ci, + u64 version) +{ + ci->i_inline_version = version; +} +#else +/* + * Only ever called under ceph_has_inline_data(), which is a constant + * false here, so this is unreachable; 0 keeps the error paths above + * simple without implying anything was done. + */ +static inline int ceph_uninline_data(struct file *file) +{ + return 0; +} + +static inline void ceph_fill_inline_data(struct inode *inode, + struct page *locked_page, + char *data, size_t len) +{ +} + +/* + * Without inline data support an inode never has any, so report the + * version the MDS uses for "no inline data" and ignore any update. + * This constant-folds all the inline data handling away. + */ +static inline u64 ceph_inline_version(const struct ceph_inode_info *ci) +{ + return CEPH_INLINE_NONE; +} + +static inline void ceph_set_inline_version(struct ceph_inode_info *ci, + u64 version) +{ +} +#endif /* CONFIG_CEPH_FS_INLINE_DATA */ + static inline bool ceph_has_inline_data(const struct ceph_inode_info *ci) { - if (ci->i_inline_version == CEPH_INLINE_NONE || - ci->i_inline_version == 1) /* initial version, no data */ + u64 version = ceph_inline_version(ci); + + if (version == CEPH_INLINE_NONE || + version == 1) /* initial version, no data */ return false; return true; } @@ -1428,8 +1479,6 @@ extern ssize_t __ceph_sync_read(struct inode *inode, loff_t *ki_pos, struct iov_iter *to, int *retry_op, u64 *last_objver); extern int ceph_release(struct inode *inode, struct file *filp); -extern void ceph_fill_inline_data(struct inode *inode, struct page *locked_page, - char *data, size_t len); /* dir.c */ extern const struct file_operations ceph_dir_fops; diff --git a/include/linux/ceph/ceph_features.h b/include/linux/ceph/ceph_features.h index 3a47acd9cc14..1e4dce93a8a1 100644 --- a/include/linux/ceph/ceph_features.h +++ b/include/linux/ceph/ceph_features.h @@ -173,6 +173,18 @@ DEFINE_CEPH_FEATURE(62, 1, RESERVED) // do not use; used as a sentinal DEFINE_CEPH_FEATURE_DEPRECATED(63, 1, RESERVED_BROKEN, LUMINOUS) // client-facing +/* + * Only advertise CEPH_FEATURE_MDS_INLINE_DATA when inline data support is + * built in. Without it the MDS refuses to hand out files that still have + * inline data instead of letting the client read the data pool objects + * that do not hold it yet. + */ +#ifdef CONFIG_CEPH_FS_INLINE_DATA +#define CEPH_FEATURE_MDS_INLINE_DATA_SUPPORTED CEPH_FEATURE_MDS_INLINE_DATA +#else +#define CEPH_FEATURE_MDS_INLINE_DATA_SUPPORTED 0 +#endif + /* * Features supported. */ @@ -204,7 +216,7 @@ DEFINE_CEPH_FEATURE_DEPRECATED(63, 1, RESERVED_BROKEN, LUMINOUS) // client-facin CEPH_FEATURE_CRUSH_V2 | \ CEPH_FEATURE_EXPORT_PEER | \ CEPH_FEATURE_OSDMAP_ENC | \ - CEPH_FEATURE_MDS_INLINE_DATA | \ + CEPH_FEATURE_MDS_INLINE_DATA_SUPPORTED |\ CEPH_FEATURE_CRUSH_TUNABLES3 | \ CEPH_FEATURE_OSD_PRIMARY_AFFINITY | \ CEPH_FEATURE_MSGR_KEEPALIVE2 | \ diff --git a/include/linux/ceph/ceph_fs.h b/include/linux/ceph/ceph_fs.h index 69ac3e55a3fe..0adc526ec321 100644 --- a/include/linux/ceph/ceph_fs.h +++ b/include/linux/ceph/ceph_fs.h @@ -707,8 +707,13 @@ int ceph_flags_to_mode(int flags); CEPH_CAP_LINK_SHARED | \ CEPH_CAP_FILE_SHARED | \ CEPH_CAP_XATTR_SHARED) +#ifdef CONFIG_CEPH_FS_INLINE_DATA #define CEPH_STAT_CAP_INLINE_DATA (CEPH_CAP_FILE_SHARED | \ CEPH_CAP_FILE_RD) +#else +/* no inline data to fetch, so no extra caps are needed for it */ +#define CEPH_STAT_CAP_INLINE_DATA 0 +#endif #define CEPH_STAT_RSTAT CEPH_CAP_FILE_WREXTEND #define CEPH_CAP_ANY_SHARED (CEPH_CAP_AUTH_SHARED | \