aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-28 11:51:05 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-28 11:51:05 -0700
commit548e7bcd0c5460ddcbca9600cea603ebeebf4da7 (patch)
treef6c9495617263ee073c84c9735afa173a3686a3b
parentce727a090be04dc7c51edd5c0da2a41d2fb6e106 (diff)
parent8fdf946445732c2bcd685abc8bd0e509d2ebc158 (diff)
Merge tag 'ceph-for-7.3-rc1' of https://github.com/ceph/ceph-client
Pull ceph updates from Ilya Dryomov: "A wide variety of mostly CephFS fixes and cleanups, split between changes that address edge cases (Sam, Xiubo, Matthew), efficiency improvements (Max) and AI-assisted hardening (Michael, Jeremy). One thing that stands out is Alex's change to how CephFS behaves in NEARFULL scenarios: the long-standing "make all writes synchronous" behavior has become opt-in. It was always somewhat controversial and doesn't make much sense for modern deployments; the new default is to continue normal operation (i.e. buffer writes as MDS allows, etc). The behavior in case the cluster reaches any FULL state remains the same as before" * tag 'ceph-for-7.3-rc1' of https://github.com/ceph/ceph-client: (32 commits) ceph: force a cap message when a deferred revoke can't be acked immediately libceph: reject buckets with mismatched CRUSH ids ceph: reject export_targets ranks >= CEPH_MAX_MDS in mdsmap decode ceph: fix leaked inode reference on writeback abort at umount libceph: remove ceph_put_page_vector() libceph: validate banner payload length ceph: make nearfull sync writes opt-in ceph: do not repeat ceph_trim_dentries() if no progress possible ceph: drop mdsc->mutex before decoding the MDS reply ceph: fix UAF in check_new_map() on session freed during unlock ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock ceph: pass inode pointer around instead of reloading it ceph: mark cap remove with RB_CLEAR_NODE() instead of setting ci=NULL ceph: add helper function ceph_cap_is_removed() ceph: make __ceph_remove_cap() static ceph: cap delegated inode count in ceph_parse_deleg_inos() ceph: bound num_export_targets array for mds info v2/v3 ceph: bound MDSCapAuth path and fs_name decode in handle_session() ceph: bound xattr value length in __build_xattrs() ceph: bound copied dentry name length in NFS export get_name ...
-rw-r--r--Documentation/filesystems/ceph.rst6
-rw-r--r--fs/ceph/addr.c30
-rw-r--r--fs/ceph/caps.c166
-rw-r--r--fs/ceph/crypto.c50
-rw-r--r--fs/ceph/crypto.h4
-rw-r--r--fs/ceph/dir.c22
-rw-r--r--fs/ceph/export.c26
-rw-r--r--fs/ceph/file.c56
-rw-r--r--fs/ceph/inode.c3
-rw-r--r--fs/ceph/mds_client.c110
-rw-r--r--fs/ceph/mds_client.h1
-rw-r--r--fs/ceph/mdsmap.c11
-rw-r--r--fs/ceph/super.c10
-rw-r--r--fs/ceph/super.h45
-rw-r--r--fs/ceph/xattr.c1
-rw-r--r--include/linux/ceph/libceph.h2
-rw-r--r--net/ceph/messenger_v2.c5
-rw-r--r--net/ceph/osd_client.c30
-rw-r--r--net/ceph/osdmap.c2
-rw-r--r--net/ceph/pagevec.c13
20 files changed, 462 insertions, 131 deletions
diff --git a/Documentation/filesystems/ceph.rst b/Documentation/filesystems/ceph.rst
index 6d2276a87a5a..ee2ca0c0c654 100644
--- a/Documentation/filesystems/ceph.rst
+++ b/Documentation/filesystems/ceph.rst
@@ -194,6 +194,12 @@ Mount Options
copies. Currently, it's only used in copy_file_range, which will revert
to the default VFS implementation if this option is used.
+ nearfull_sync
+ Force written data to stable storage when the cluster or file data pool is
+ marked NEARFULL. This restores the legacy client-side backpressure
+ behavior. By default, CephFS writes are not forced synchronous solely
+ because of NEARFULL.
+
recover_session=<no|clean>
Set auto reconnect mode in the case where the client is blocklisted. The
available modes are "no" and "clean". The default is "no".
diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
index ecf33b66610c..657c2cb0f881 100644
--- a/fs/ceph/addr.c
+++ b/fs/ceph/addr.c
@@ -255,9 +255,12 @@ static void finish_netfs_read(struct ceph_osd_request *req)
}
if (osd_data->type == CEPH_OSD_DATA_TYPE_PAGES) {
- ceph_put_page_vector(osd_data->pages,
- calc_pages_for(osd_data->alignment,
- osd_data->length), false);
+ int num_pages = calc_pages_for(osd_data->alignment,
+ osd_data->length);
+
+ for (int i = 0; i < num_pages; i++)
+ put_page(osd_data->pages[i]);
+ kvfree(osd_data->pages);
}
if (err > 0) {
ceph_subvolume_metrics_record_io(fsc->mdsc, ceph_inode(inode),
@@ -1426,6 +1429,16 @@ void ceph_shift_unused_folios_left(struct folio_batch *fbatch)
fbatch->nr = n;
}
+static void ceph_undo_wrbuffer_claim(struct inode *inode, struct folio *folio)
+{
+ struct ceph_snap_context *snapc = folio_detach_private(folio);
+
+ if (!snapc)
+ return;
+ ceph_put_wrbuffer_cap_refs(ceph_inode(inode), 1, snapc);
+ ceph_put_snap_context(snapc);
+}
+
static
int ceph_submit_write(struct address_space *mapping,
struct writeback_control *wbc,
@@ -1489,6 +1502,7 @@ new_request:
if (!page)
continue;
+ ceph_undo_wrbuffer_claim(inode, page_folio(page));
redirty_page_for_writepage(wbc, page);
unlock_page(page);
}
@@ -2464,7 +2478,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci,
}
rd_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL,
- 1, false, GFP_NOFS);
+ 1, false, GFP_KERNEL);
if (!rd_req) {
err = -ENOMEM;
goto out_unlock;
@@ -2477,12 +2491,12 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci,
rd_req->r_base_oloc.pool_ns = ceph_get_string(pool_ns);
ceph_oid_printf(&rd_req->r_base_oid, "%llx.00000000", ci->i_vino.ino);
- err = ceph_osdc_alloc_messages(rd_req, GFP_NOFS);
+ err = ceph_osdc_alloc_messages(rd_req, GFP_KERNEL);
if (err)
goto out_unlock;
wr_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL,
- 1, false, GFP_NOFS);
+ 1, false, GFP_KERNEL);
if (!wr_req) {
err = -ENOMEM;
goto out_unlock;
@@ -2493,7 +2507,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci,
ceph_oloc_copy(&wr_req->r_base_oloc, &rd_req->r_base_oloc);
ceph_oid_copy(&wr_req->r_base_oid, &rd_req->r_base_oid);
- err = ceph_osdc_alloc_messages(wr_req, GFP_NOFS);
+ err = ceph_osdc_alloc_messages(wr_req, GFP_KERNEL);
if (err)
goto out_unlock;
@@ -2532,7 +2546,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci,
}
pool_ns_len = pool_ns ? pool_ns->len : 0;
- perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_NOFS);
+ perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_KERNEL);
if (!perm) {
err = -ENOMEM;
goto out_unlock;
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
index d7283fb54cec..bcb04c6cb92c 100644
--- a/fs/ceph/caps.c
+++ b/fs/ceph/caps.c
@@ -785,9 +785,9 @@ void ceph_add_cap(struct inode *inode,
* generation of the MDS session (i.e. has not gone 'stale' due to
* us losing touch with the mds).
*/
-static int __cap_is_valid(struct ceph_cap *cap)
+static int __cap_is_valid(struct ceph_inode_info *ci, struct ceph_cap *cap)
{
- struct inode *inode = &cap->ci->netfs.inode;
+ struct inode *inode = &ci->netfs.inode;
struct ceph_client *cl = cap->session->s_mdsc->fsc->client;
unsigned long ttl;
u32 gen;
@@ -822,7 +822,7 @@ int __ceph_caps_issued(struct ceph_inode_info *ci, int *implemented)
*implemented = 0;
for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
cap = rb_entry(p, struct ceph_cap, ci_node);
- if (!__cap_is_valid(cap))
+ if (!__cap_is_valid(ci, cap))
continue;
doutc(cl, "%p %llx.%llx cap %p issued %s\n", inode,
ceph_vinop(inode), cap, ceph_cap_string(cap->issued));
@@ -855,7 +855,7 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap)
cap = rb_entry(p, struct ceph_cap, ci_node);
if (cap == ocap)
continue;
- if (!__cap_is_valid(cap))
+ if (!__cap_is_valid(ci, cap))
continue;
have |= cap->issued;
}
@@ -866,11 +866,19 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap)
* Move a cap to the end of the LRU (oldest caps at list head, newest
* at list tail).
*/
-static void __touch_cap(struct ceph_cap *cap)
+static void __touch_cap(struct ceph_inode_info *ci, struct ceph_cap *cap)
{
- struct inode *inode = &cap->ci->netfs.inode;
+ struct inode *inode = &ci->netfs.inode;
struct ceph_mds_session *s = cap->session;
struct ceph_client *cl = s->s_mdsc->fsc->client;
+ static u8 skip_counter;
+
+ if (data_race(++skip_counter))
+ /* skip this call most of the time to reduce lock
+ * contention; the LRU list is still accurate enough
+ * for ceph_trim_caps()
+ */
+ return;
spin_lock(&s->s_cap_lock);
if (!s->s_cap_iterator) {
@@ -906,7 +914,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch)
for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
cap = rb_entry(p, struct ceph_cap, ci_node);
- if (!__cap_is_valid(cap))
+ if (!__cap_is_valid(ci, cap))
continue;
if ((cap->issued & mask) == mask) {
doutc(cl, "mask %p %llx.%llx cap %p issued %s (mask %s)\n",
@@ -914,7 +922,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch)
ceph_cap_string(cap->issued),
ceph_cap_string(mask));
if (touch)
- __touch_cap(cap);
+ __touch_cap(ci, cap);
return 1;
}
@@ -929,15 +937,15 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch)
struct rb_node *q;
/* touch this + preceding caps */
- __touch_cap(cap);
+ __touch_cap(ci, cap);
for (q = rb_first(&ci->i_caps); q != p;
q = rb_next(q)) {
cap = rb_entry(q, struct ceph_cap,
ci_node);
- if (!__cap_is_valid(cap))
+ if (!__cap_is_valid(ci, cap))
continue;
if (cap->issued & mask)
- __touch_cap(cap);
+ __touch_cap(ci, cap);
}
}
return 1;
@@ -979,6 +987,27 @@ int __ceph_caps_revoking_other(struct ceph_inode_info *ci,
return 0;
}
+/*
+ * Return true if any cap of this inode holds caps which the MDS has
+ * revoked, but which we have not released yet.
+ */
+static bool __ceph_is_any_revoking(const struct ceph_inode_info *ci)
+{
+ const struct rb_node *p;
+
+ lockdep_assert_held(&ci->i_ceph_lock);
+
+ for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
+ const struct ceph_cap *cap =
+ rb_entry(p, struct ceph_cap, ci_node);
+
+ if (cap->implemented & ~cap->issued)
+ return true;
+ }
+
+ return false;
+}
+
int __ceph_caps_used(struct ceph_inode_info *ci)
{
int used = 0;
@@ -1091,7 +1120,7 @@ int __ceph_caps_mds_wanted(struct ceph_inode_info *ci, bool check)
for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
cap = rb_entry(p, struct ceph_cap, ci_node);
- if (check && !__cap_is_valid(cap))
+ if (check && !__cap_is_valid(ci, cap))
continue;
if (cap == ci->i_auth_cap)
mds_wanted |= cap->mds_wanted;
@@ -1119,20 +1148,20 @@ int ceph_is_any_caps(struct inode *inode)
* caller should hold i_ceph_lock.
* caller will not hold session s_mutex if called from destroy_inode.
*/
-void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
+static void __ceph_remove_cap(struct ceph_inode_info *ci, struct ceph_cap *cap, bool queue_release)
{
- struct ceph_mds_session *session = cap->session;
- struct ceph_client *cl = session->s_mdsc->fsc->client;
- struct ceph_inode_info *ci = cap->ci;
- struct inode *inode = &ci->netfs.inode;
+ struct ceph_mds_session *session;
+ struct ceph_client *cl;
+ struct inode *inode;
struct ceph_mds_client *mdsc;
int removed = 0;
- /* 'ci' being NULL means the remove have already occurred */
- if (!ci) {
- doutc(cl, "inode is NULL\n");
+ if (ceph_cap_is_removed(cap))
return;
- }
+
+ session = cap->session;
+ cl = session->s_mdsc->fsc->client;
+ inode = &ci->netfs.inode;
lockdep_assert_held(&ci->i_ceph_lock);
@@ -1158,8 +1187,11 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
cap->session = NULL;
removed = 1;
}
- /* protect backpointer with s_cap_lock: see iterate_session_caps */
- cap->ci = NULL;
+
+ /* protect removal marker with both i_ceph_lock and
+ s_cap_lock, so either one can be used to check for
+ removal */
+ RB_CLEAR_NODE(&cap->ci_node);
/*
* s_cap_reconnect is protected by s_cap_lock. no one changes
@@ -1196,13 +1228,12 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
}
void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap,
+ struct ceph_inode_info *ci,
bool queue_release)
{
- struct ceph_inode_info *ci = cap->ci;
struct ceph_fs_client *fsc;
- /* 'ci' being NULL means the remove have already occurred */
- if (!ci) {
+ if (ceph_cap_is_removed(cap)) {
doutc(mdsc->fsc->client, "inode is NULL\n");
return;
}
@@ -1215,7 +1246,7 @@ void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap,
!fsc->blocklisted &&
!ceph_inode_is_shutdown(&ci->netfs.inode));
- __ceph_remove_cap(cap, queue_release);
+ __ceph_remove_cap(ci, cap, queue_release);
}
struct cap_msg_args {
@@ -1375,7 +1406,7 @@ void __ceph_remove_caps(struct ceph_inode_info *ci)
while (p) {
struct ceph_cap *cap = rb_entry(p, struct ceph_cap, ci_node);
p = rb_next(p);
- ceph_remove_cap(mdsc, cap, true);
+ ceph_remove_cap(mdsc, cap, ci, true);
}
spin_unlock(&ci->i_ceph_lock);
}
@@ -1388,11 +1419,11 @@ void __ceph_remove_caps(struct ceph_inode_info *ci)
* Make note of max_size reported/requested from mds, revoked caps
* that have now been implemented.
*/
-static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap,
+static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci,
+ struct ceph_cap *cap,
int op, int flags, int used, int want, int retain,
int flushing, u64 flush_tid, u64 oldest_flush_tid)
{
- struct ceph_inode_info *ci = cap->ci;
struct inode *inode = &ci->netfs.inode;
struct ceph_client *cl = ceph_inode_to_client(inode);
int held, revoking;
@@ -1421,6 +1452,9 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap,
cap->implemented &= cap->issued | used;
cap->mds_wanted = want;
+ if ((ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) != 0 && !__ceph_is_any_revoking(ci))
+ clear_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+
arg->session = cap->session;
arg->ino = ceph_vino(inode).ino;
arg->cid = cap->cap_id;
@@ -1842,7 +1876,7 @@ struct ceph_cap_flush *ceph_alloc_cap_flush(void)
{
struct ceph_cap_flush *cf;
- cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_KERNEL);
+ cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_NOFS);
if (!cf)
return NULL;
@@ -2038,6 +2072,14 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
if (ci->i_ceph_flags & CEPH_I_FLUSH)
flags |= CHECK_CAPS_FLUSH;
+ /*
+ * A revoke whose response was deferred (see handle_cap_grant()) must
+ * still be acknowledged. Replay the forced flush here so that even a
+ * check triggered by writeback/invalidation completion sends a cap
+ * message to the MDS.
+ */
+ if (ci->i_ceph_flags & CEPH_I_FLUSH_FORCE)
+ flags |= CHECK_CAPS_FLUSH_FORCE;
retry:
/* Caps wanted by virtue of active open files. */
file_wanted = __ceph_caps_file_wanted(ci);
@@ -2210,7 +2252,7 @@ retry:
if (want & ~cap->mds_wanted) {
if (want & ~(cap->mds_wanted | cap->issued))
goto ack;
- if (!__cap_is_valid(cap))
+ if (!__cap_is_valid(ci, cap))
goto ack;
}
@@ -2252,7 +2294,7 @@ ack:
mds = cap->mds; /* remember mds, so we don't repeat */
- __prep_cap(&arg, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used,
+ __prep_cap(&arg, ci, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used,
want, retain, flushing, flush_tid, oldest_flush_tid);
spin_unlock(&ci->i_ceph_lock);
@@ -2314,7 +2356,7 @@ retry_locked:
flush_tid = __mark_caps_flushing(inode, session, true,
&oldest_flush_tid);
- __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC,
+ __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC,
__ceph_caps_used(ci), __ceph_caps_wanted(ci),
(cap->issued | cap->implemented),
flushing, flush_tid, oldest_flush_tid);
@@ -2589,9 +2631,14 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
}
- list_for_each_entry(cf, &ci->i_cap_flush_list, i_list) {
- if (cf->tid < first_tid)
+ cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list);
+ while (&cf->i_list != &ci->i_cap_flush_list) {
+ struct ceph_cap_flush *next;
+
+ if (cf->tid < first_tid) {
+ cf = list_next_entry(cf, i_list);
continue;
+ }
cap = ci->i_auth_cap;
if (!(cap && cap->session == session)) {
@@ -2601,6 +2648,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
first_tid = cf->tid + 1;
+ next = list_next_entry(cf, i_list);
if (!cf->is_capsnap) {
struct cap_msg_args arg;
@@ -2608,7 +2656,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
doutc(cl, "%p %llx.%llx cap %p tid %llu %s\n",
inode, ceph_vinop(inode), cap, cf->tid,
ceph_cap_string(cf->caps));
- __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH,
+ __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH,
(cf->tid < last_snap_flush ?
CEPH_CLIENT_CAPS_PENDING_CAPSNAP : 0),
__ceph_caps_used(ci),
@@ -2641,6 +2689,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
spin_lock(&ci->i_ceph_lock);
+ cf = next;
}
}
@@ -3757,13 +3806,30 @@ static void handle_cap_grant(struct inode *inode,
BUG_ON(cap->issued & ~cap->implemented);
/* don't let check_caps skip sending a response to MDS for revoke msgs */
- if (!revoke_wait && le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
- cap->mds_wanted = 0;
- flags |= CHECK_CAPS_FLUSH_FORCE;
- if (cap == ci->i_auth_cap)
- check_caps = 1; /* check auth cap only */
- else
- check_caps = 2; /* check all caps */
+ if (le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
+ if (revoke_wait) {
+ /*
+ * We can't ack the revoke yet: the response is deferred
+ * until the writeback or cache invalidation queued above
+ * completes. Set the CEPH_I_FLUSH_FORCE flag to remember
+ * that a forced cap message is owed so that deferred
+ * completion (ceph_put_wrbuffer_cap_refs() or the
+ * invalidate worker, both of which call ceph_check_caps())
+ * actually sends one, even if by then the revoked caps look
+ * unused, the inode is retaining caps, or the MDS has
+ * re-granted them. Without this, the cap message is never
+ * sent and the MDS hangs ("isn't responding to
+ * mclientcaps(revoke)").
+ */
+ set_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+ } else {
+ cap->mds_wanted = 0;
+ flags |= CHECK_CAPS_FLUSH_FORCE;
+ if (cap == ci->i_auth_cap)
+ check_caps = 1; /* check auth cap only */
+ else
+ check_caps = 2; /* check all caps */
+ }
}
if (extra_info->inline_version > 0 &&
@@ -4119,7 +4185,7 @@ retry:
goto out_unlock;
if (target < 0) {
- ceph_remove_cap(mdsc, cap, false);
+ ceph_remove_cap(mdsc, cap, ci, false);
goto out_unlock;
}
@@ -4156,7 +4222,7 @@ retry:
change_auth_cap_ses(ci, tcap->session);
}
}
- ceph_remove_cap(mdsc, cap, false);
+ ceph_remove_cap(mdsc, cap, ci, false);
goto out_unlock;
} else if (tsession) {
/* add placeholder for the export target */
@@ -4173,7 +4239,7 @@ retry:
spin_unlock(&mdsc->cap_dirty_lock);
}
- ceph_remove_cap(mdsc, cap, false);
+ ceph_remove_cap(mdsc, cap, ci, false);
goto out_unlock;
}
@@ -4289,7 +4355,7 @@ retry:
inode, ceph_vinop(inode), peer,
ocap->seq, ocap->mseq, mds, piseq, pmseq);
}
- ceph_remove_cap(mdsc, ocap, (ph->flags & CEPH_CAP_FLAG_RELEASE));
+ ceph_remove_cap(mdsc, ocap, ci, (ph->flags & CEPH_CAP_FLAG_RELEASE));
}
*old_issued = issued;
@@ -4887,7 +4953,7 @@ int ceph_encode_inode_release(void **p, struct inode *inode,
drop &= ~(used | dirty);
cap = __get_cap_for_mds(ci, mds);
- if (cap && __cap_is_valid(cap)) {
+ if (cap && __cap_is_valid(ci, cap)) {
unless &= cap->issued;
if (unless) {
if (unless & CEPH_CAP_AUTH_EXCL)
@@ -5046,7 +5112,7 @@ int ceph_purge_inode_cap(struct inode *inode, struct ceph_cap *cap, bool *invali
cap, ci, inode, ceph_vinop(inode));
is_auth = (cap == ci->i_auth_cap);
- __ceph_remove_cap(cap, false);
+ __ceph_remove_cap(ci, cap, false);
if (is_auth) {
struct ceph_cap_flush *cf;
diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
index 64d240759277..bc0a097a4cea 100644
--- a/fs/ceph/crypto.c
+++ b/fs/ceph/crypto.c
@@ -298,18 +298,26 @@ out:
* Otherwise, base64 decode the string, and then ask fscrypt to format it
* for userland presentation.
*
+ * Though the fscrypt/crypto subsystems broadly expect all buffers to be in the
+ * linear-mapped region, this function slightly relaxes those requirements:
+ * fname->ctext, fname->name, and oname->name may be vmalloc(), but not tname.
+ *
* Returns 0 on success or negative error code on error.
*/
-int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
+int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname,
struct fscrypt_str *oname, bool *is_nokey)
{
struct inode *dir = fname->dir;
struct fscrypt_str _tname = FSTR_INIT(NULL, 0);
+ struct fscrypt_str _oname;
struct fscrypt_str iname;
char *name = fname->name;
int name_len = fname->name_len;
int ret;
+ if (WARN_ON_ONCE(tname && is_vmalloc_addr(tname)))
+ return -EIO;
+
/* Sanity check that the resulting name will fit in the buffer */
if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX)
return -EIO;
@@ -350,31 +358,47 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
goto out_inode;
}
+ if (!tname && (fname->ctext_len == 0 ||
+ unlikely(is_vmalloc_addr(fname->ctext)) ||
+ unlikely(is_vmalloc_addr(oname->name)))) {
+ ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
+ if (ret)
+ goto out_inode;
+ tname = _tname.name;
+ }
+
if (fname->ctext_len == 0) {
int declen;
- if (!tname) {
- ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
- if (ret)
- goto out_inode;
- tname = &_tname;
- }
-
- declen = base64_decode(name, name_len,
- tname->name, false, BASE64_IMAP);
+ declen = base64_decode(name, name_len, tname, false, BASE64_IMAP);
if (declen <= 0) {
ret = -EIO;
goto out;
}
- iname.name = tname->name;
+ iname.name = tname;
iname.len = declen;
+ } else if (unlikely(is_vmalloc_addr(fname->ctext))) {
+ memcpy(tname, fname->ctext, fname->ctext_len);
+
+ iname.name = tname;
+ iname.len = fname->ctext_len;
} else {
iname.name = fname->ctext;
iname.len = fname->ctext_len;
}
- ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname);
- if (!ret && (dir != fname->dir)) {
+ _oname.name = unlikely(is_vmalloc_addr(oname->name)) ? tname : oname->name;
+ _oname.len = oname->len;
+
+ ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, &_oname);
+ if (ret)
+ goto out;
+
+ if (unlikely(is_vmalloc_addr(oname->name)))
+ memcpy(oname->name, _oname.name, _oname.len);
+ oname->len = _oname.len;
+
+ if (dir != fname->dir) {
char tmp_buf[BASE64_CHARS(NAME_MAX)];
name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%llu",
diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h
index b748e2060bc9..79cb563fd887 100644
--- a/fs/ceph/crypto.h
+++ b/fs/ceph/crypto.h
@@ -115,7 +115,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent,
fscrypt_fname_free_buffer(fname);
}
-int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
+int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname,
struct fscrypt_str *oname, bool *is_nokey);
int ceph_fscrypt_prepare_readdir(struct inode *dir);
@@ -204,7 +204,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent,
}
static inline int ceph_fname_to_usr(const struct ceph_fname *fname,
- struct fscrypt_str *tname,
+ unsigned char *tname,
struct fscrypt_str *oname, bool *is_nokey)
{
oname->name = fname->name;
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 4a5e0290f2e3..2e5c0ccb1b34 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -774,8 +774,13 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
d_drop(dentry);
err = -ENOENT;
} else {
- if (d_unhashed(dentry))
- d_add(dentry, NULL);
+ if (d_unhashed(dentry)) {
+ struct inode *parent =
+ d_inode(dentry->d_parent);
+ if (!parent ||
+ ceph_snap(parent) == CEPH_NOSNAP)
+ d_add(dentry, NULL);
+ }
}
}
}
@@ -840,6 +845,7 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
dentry->d_name.len) &&
!is_root_ceph_dentry(dir, dentry) &&
ceph_test_mount_opt(fsc, DCACHE) &&
+ ceph_snap(dir) == CEPH_NOSNAP &&
__ceph_dir_is_complete(ci) &&
__ceph_caps_issued_mask_metric(ci, CEPH_CAP_FILE_SHARED, 1)) {
__ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD);
@@ -1173,7 +1179,7 @@ static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir,
!req->r_reply_info.head->is_target &&
!req->r_reply_info.head->is_dentry)
err = ceph_handle_notrace_create(dir, dentry);
- ret = ERR_PTR(err);
+ ret = err ? ERR_PTR(err) : NULL;
out_req:
if (!IS_ERR(ret) && req->r_dentry != dentry)
/* Some other dentry was spliced in */
@@ -1763,11 +1769,11 @@ static int __dir_lease_check(const struct dentry *dentry,
if (ret > 0) {
if (time_before(jiffies, di->time + lwc->dir_lease_ttl))
return STOP;
+ if (!lwc->expire_dir_lease)
+ return KEEP;
/* Move dentry to tail of dir lease list if we don't want
* to delete it. So dentries in the list are checked in a
* round robin manner */
- if (!lwc->expire_dir_lease)
- return TOUCH;
if (dentry->d_lockref.count > 0 ||
(di->flags & CEPH_DENTRY_REFERENCED))
return TOUCH;
@@ -1794,7 +1800,7 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.dir_lease = false;
lwc.nr_to_scan = CEPH_CAPS_PER_RELEASE * 2;
freed = __dentry_leases_walk(mdsc, &lwc);
- if (!lwc.nr_to_scan) /* more invalid leases */
+ if (freed > 0 && !lwc.nr_to_scan) /* more invalid leases */
return -EAGAIN;
if (lwc.nr_to_scan < CEPH_CAPS_PER_RELEASE)
@@ -1804,6 +1810,10 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.expire_dir_lease = freed < count;
lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max * HZ;
freed +=__dentry_leases_walk(mdsc, &lwc);
+ if (freed == 0 && count == 0)
+ /* no progress possible currently, retry futile */
+ return 0;
+
if (!lwc.nr_to_scan) /* more to check */
return -EAGAIN;
diff --git a/fs/ceph/export.c b/fs/ceph/export.c
index b2f2af104679..debb9634b9e3 100644
--- a/fs/ceph/export.c
+++ b/fs/ceph/export.c
@@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb,
return dentry;
}
+static int ceph_export_copy_name(char *name, const char *src, u32 len)
+{
+ if (len > NAME_MAX)
+ return -ENAMETOOLONG;
+
+ memcpy(name, src, len);
+ name[len] = '\0';
+ return 0;
+}
+
static int __get_snap_name(struct dentry *parent, char *name,
struct dentry *child)
{
@@ -513,9 +523,8 @@ static int __get_snap_name(struct dentry *parent, char *name,
BUG_ON(!rde->inode.in);
if (ceph_snap(inode) ==
le64_to_cpu(rde->inode.in->snapid)) {
- memcpy(name, rde->name, rde->name_len);
- name[rde->name_len] = '\0';
- err = 0;
+ err = ceph_export_copy_name(name, rde->name,
+ rde->name_len);
goto out;
}
}
@@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry *parent, char *name,
rinfo = &req->r_reply_info;
if (!IS_ENCRYPTED(dir)) {
- memcpy(name, rinfo->dname, rinfo->dname_len);
- name[rinfo->dname_len] = 0;
+ err = ceph_export_copy_name(name, rinfo->dname,
+ rinfo->dname_len);
} else {
struct fscrypt_str oname = FSTR_INIT(NULL, 0);
struct ceph_fname fname = { .dir = dir,
@@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry *parent, char *name,
goto out;
err = ceph_fname_to_usr(&fname, NULL, &oname, NULL);
- if (!err) {
- memcpy(name, oname.name, oname.len);
- name[oname.len] = 0;
- }
+ if (!err)
+ err = ceph_export_copy_name(name, oname.name,
+ oname.len);
ceph_fname_free_buffer(dir, &oname);
}
out:
diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index a4a2a4b6a027..bd3e3f5c269e 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -2388,7 +2388,8 @@ out_end:
* dropping our cap refs and allowing the pending snap to logically
* complete _before_ this write occurs.
*
- * If we are near ENOSPC, write synchronously.
+ * If requested, nearfull writes are synced to preserve the legacy
+ * client-side backpressure behavior.
*/
static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
@@ -2477,6 +2478,54 @@ retry_snap:
if (err < 0)
goto out;
+ /*
+ * For O_APPEND writes we may have waited for Fwx exclusive caps
+ * while the previous Fwx holder (another client) extended the
+ * file. i_size has been updated via the cap grant message from
+ * the MDS, but ki_pos is still the old EOF. Re-read i_size here
+ * (no extra MDS round-trip needed) and adjust ki_pos to the true
+ * EOF. Since we hold Fwx, no other client can change the file.
+ */
+ if (iocb->ki_flags & IOCB_APPEND) {
+ loff_t cur_eof = i_size_read(inode);
+
+ if (cur_eof != pos) {
+ doutc(cl,
+ "%p %llx.%llx O_APPEND: pos adjusted %lld -> %lld\n",
+ inode, ceph_vinop(inode), pos, cur_eof);
+ iocb->ki_pos = cur_eof;
+ pos = cur_eof;
+ if (pos >= limit) {
+ err = -EFBIG;
+ goto out_caps;
+ }
+ iov_iter_truncate(from, limit - pos);
+ count = iov_iter_count(from);
+
+ /*
+ * ceph_get_caps() validated the old endoff
+ * against i_max_size; adjusting ki_pos forward
+ * may have shifted the write range beyond the
+ * granted max_size. Re-check and truncate if
+ * necessary.
+ */
+ spin_lock(&ci->i_ceph_lock);
+ if (pos + count > (loff_t)ci->i_max_size) {
+ loff_t max_size = ci->i_max_size;
+
+ spin_unlock(&ci->i_ceph_lock);
+ if (pos >= max_size) {
+ err = -EFBIG;
+ goto out_caps;
+ }
+ iov_iter_truncate(from, max_size - pos);
+ count = iov_iter_count(from);
+ } else {
+ spin_unlock(&ci->i_ceph_lock);
+ }
+ }
+ }
+
err = file_update_time(file);
if (err)
goto out_caps;
@@ -2556,8 +2605,9 @@ retry_snap:
}
if (written >= 0) {
- if ((map_flags & CEPH_OSDMAP_NEARFULL) ||
- (pool_flags & CEPH_POOL_FLAG_NEARFULL))
+ if (ceph_test_mount_opt(fsc, NEARFULL_SYNC) &&
+ ((map_