mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 05:49:47 -04:00
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 ...
This commit is contained in:
@@ -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".
|
||||
|
||||
@@ -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 @@ int ceph_submit_write(struct address_space *mapping,
|
||||
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;
|
||||
|
||||
166
fs/ceph/caps.c
166
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 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
|
||||
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 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
|
||||
|
||||
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 @@ static int try_flush_caps(struct inode *inode, u64 *ptid)
|
||||
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 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
||||
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 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
||||
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 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
||||
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 @@ static void handle_cap_import(struct ceph_mds_client *mdsc,
|
||||
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;
|
||||
|
||||
|
||||
@@ -298,18 +298,26 @@ int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen)
|
||||
* 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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2388,7 +2388,8 @@ static ssize_t ceph_splice_read(struct file *in, loff_t *ppos,
|
||||
* 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 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
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 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
}
|
||||
|
||||
if (written >= 0) {
|
||||
if ((map_flags & CEPH_OSDMAP_NEARFULL) ||
|
||||
(pool_flags & CEPH_POOL_FLAG_NEARFULL))
|
||||
if (ceph_test_mount_opt(fsc, NEARFULL_SYNC) &&
|
||||
((map_flags & CEPH_OSDMAP_NEARFULL) ||
|
||||
(pool_flags & CEPH_POOL_FLAG_NEARFULL)))
|
||||
iocb->ki_flags |= IOCB_DSYNC;
|
||||
written = generic_write_sync(iocb, written);
|
||||
}
|
||||
|
||||
@@ -1814,7 +1814,8 @@ int ceph_fill_trace(struct super_block *sb, struct ceph_mds_request *req)
|
||||
ceph_dir_clear_ordered(dir);
|
||||
d_delete(dn);
|
||||
} else if (have_lease) {
|
||||
if (d_unhashed(dn))
|
||||
if (d_unhashed(dn) &&
|
||||
ceph_snap(dir) == CEPH_NOSNAP)
|
||||
d_add(dn, NULL);
|
||||
}
|
||||
|
||||
|
||||
@@ -491,11 +491,11 @@ static int parse_reply_info_readdir(void **p, void *end,
|
||||
struct inode *inode = d_inode(req->r_dentry);
|
||||
struct ceph_inode_info *ci = ceph_inode(inode);
|
||||
struct ceph_mds_reply_dir_entry *rde = info->dir_entries + i;
|
||||
struct fscrypt_str tname = FSTR_INIT(NULL, 0);
|
||||
struct fscrypt_str oname = FSTR_INIT(NULL, 0);
|
||||
struct ceph_fname fname;
|
||||
u32 altname_len, _name_len;
|
||||
u8 *altname, *_name;
|
||||
u8 *tname = NULL;
|
||||
|
||||
/* dentry */
|
||||
ceph_decode_32_safe(p, end, _name_len, bad);
|
||||
@@ -541,9 +541,13 @@ static int parse_reply_info_readdir(void **p, void *end,
|
||||
* to do the base64_decode in-place. It's
|
||||
* safe because the decoded string should
|
||||
* always be shorter, which is 3/4 of origin
|
||||
* string.
|
||||
* string. If this message was allocated with
|
||||
* vmalloc() (happens, but rarely), leave it
|
||||
* NULL and let ceph_fname_to_usr() allocate
|
||||
* suitable temporary working space instead.
|
||||
*/
|
||||
tname.name = _name;
|
||||
if (likely(!is_vmalloc_addr(_name)))
|
||||
tname = _name;
|
||||
|
||||
/*
|
||||
* Set oname to _name too, and this will be
|
||||
@@ -560,7 +564,7 @@ static int parse_reply_info_readdir(void **p, void *end,
|
||||
oname.len = altname_len;
|
||||
}
|
||||
rde->is_nokey = false;
|
||||
err = ceph_fname_to_usr(&fname, &tname, &oname, &rde->is_nokey);
|
||||
err = ceph_fname_to_usr(&fname, tname, &oname, &rde->is_nokey);
|
||||
if (err) {
|
||||
pr_err_client(cl, "unable to decode %.*s, got %d\n",
|
||||
_name_len, _name, err);
|
||||
@@ -615,10 +619,36 @@ static int parse_reply_info_filelock(void **p, void *end,
|
||||
|
||||
#define DELEGATED_INO_AVAILABLE xa_mk_value(1)
|
||||
|
||||
static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino)
|
||||
{
|
||||
struct ceph_client *cl = s->s_mdsc->fsc->client;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Cap how many delegated inodes a single session may hold. This is
|
||||
* the only place that grows the count, so atomic_add_unless() bounds
|
||||
* it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed
|
||||
* that.
|
||||
*/
|
||||
if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) {
|
||||
pr_warn_ratelimited_client(cl,
|
||||
"MDS session already holds %d delegated inodes\n",
|
||||
CEPH_MAX_DELEG_INOS);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
|
||||
GFP_KERNEL);
|
||||
if (err)
|
||||
atomic_dec(&s->s_num_deleg_inos);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ceph_parse_deleg_inos(void **p, void *end,
|
||||
struct ceph_mds_session *s)
|
||||
{
|
||||
struct ceph_client *cl = s->s_mdsc->fsc->client;
|
||||
u64 msg_deleg_inos = 0;
|
||||
u32 sets;
|
||||
|
||||
ceph_decode_32_safe(p, end, sets, bad);
|
||||
@@ -636,16 +666,34 @@ static int ceph_parse_deleg_inos(void **p, void *end,
|
||||
start, len);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bound the number of inodes one reply may delegate.
|
||||
* ceph_insert_deleg_ino() separately caps the per-session
|
||||
* population, so this only has to stop one reply from spinning
|
||||
* the insert loop under an attacker-controlled len.
|
||||
*/
|
||||
if (len > (u64)CEPH_MAX_DELEG_INOS ||
|
||||
msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) {
|
||||
pr_warn_ratelimited_client(cl,
|
||||
"MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n",
|
||||
msg_deleg_inos, len, CEPH_MAX_DELEG_INOS);
|
||||
return -EIO;
|
||||
}
|
||||
msg_deleg_inos += len;
|
||||
|
||||
while (len--) {
|
||||
int err = xa_insert(&s->s_delegated_inos, start++,
|
||||
DELEGATED_INO_AVAILABLE,
|
||||
GFP_KERNEL);
|
||||
int err = ceph_insert_deleg_ino(s, start++);
|
||||
|
||||
if (!err) {
|
||||
doutc(cl, "added delegated inode 0x%llx\n", start - 1);
|
||||
} else if (err == -EBUSY) {
|
||||
pr_warn_client(cl,
|
||||
"MDS delegated inode 0x%llx more than once.\n",
|
||||
start - 1);
|
||||
} else if (err == -EOVERFLOW) {
|
||||
/* ceph_insert_deleg_ino() already warned. */
|
||||
return -EIO;
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
@@ -663,16 +711,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s)
|
||||
|
||||
xa_for_each(&s->s_delegated_inos, ino, val) {
|
||||
val = xa_erase(&s->s_delegated_inos, ino);
|
||||
if (val == DELEGATED_INO_AVAILABLE)
|
||||
if (val == DELEGATED_INO_AVAILABLE) {
|
||||
atomic_dec(&s->s_num_deleg_inos);
|
||||
return ino;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino)
|
||||
{
|
||||
return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
|
||||
GFP_KERNEL);
|
||||
return ceph_insert_deleg_ino(s, ino);
|
||||
}
|
||||
#else /* BITS_PER_LONG == 64 */
|
||||
/*
|
||||
@@ -1059,6 +1108,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc,
|
||||
INIT_LIST_HEAD(&s->s_waiting);
|
||||
INIT_LIST_HEAD(&s->s_unsafe);
|
||||
xa_init(&s->s_delegated_inos);
|
||||
atomic_set(&s->s_num_deleg_inos, 0);
|
||||
INIT_LIST_HEAD(&s->s_cap_releases);
|
||||
INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work);
|
||||
|
||||
@@ -1800,16 +1850,19 @@ static void __open_export_target_sessions(struct ceph_mds_client *mdsc,
|
||||
* session caps
|
||||
*/
|
||||
|
||||
static void detach_cap_releases(struct ceph_mds_session *session,
|
||||
struct list_head *target)
|
||||
static int detach_cap_releases(struct ceph_mds_session *session,
|
||||
struct list_head *target)
|
||||
{
|
||||
struct ceph_client *cl = session->s_mdsc->fsc->client;
|
||||
const int num_cap_releases = session->s_num_cap_releases;
|
||||
|
||||
lockdep_assert_held(&session->s_cap_lock);
|
||||
|
||||
list_splice_init(&session->s_cap_releases, target);
|
||||
session->s_num_cap_releases = 0;
|
||||
doutc(cl, "mds%d\n", session->s_mds);
|
||||
|
||||
return num_cap_releases;
|
||||
}
|
||||
|
||||
static void dispose_cap_releases(struct ceph_mds_client *mdsc,
|
||||
@@ -1903,7 +1956,7 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session,
|
||||
|
||||
spin_lock(&session->s_cap_lock);
|
||||
p = p->next;
|
||||
if (!cap->ci) {
|
||||
if (ceph_cap_is_removed(cap)) {
|
||||
doutc(cl, "finishing cap %p removal\n", cap);
|
||||
BUG_ON(cap->session != session);
|
||||
cap->session = NULL;
|
||||
@@ -2259,7 +2312,7 @@ static int trim_caps_cb(struct inode *inode, int mds, void *arg)
|
||||
|
||||
if (oissued) {
|
||||
/* we aren't the only cap.. just remove us */
|
||||
ceph_remove_cap(mdsc, cap, true);
|
||||
ceph_remove_cap(mdsc, cap, ci, true);
|
||||
(*remaining)--;
|
||||
} else {
|
||||
struct dentry *dentry;
|
||||
@@ -2465,9 +2518,7 @@ static void ceph_send_cap_releases(struct ceph_mds_client *mdsc,
|
||||
|
||||
spin_lock(&session->s_cap_lock);
|
||||
again:
|
||||
list_splice_init(&session->s_cap_releases, &tmp_list);
|
||||
num_cap_releases = session->s_num_cap_releases;
|
||||
session->s_num_cap_releases = 0;
|
||||
num_cap_releases = detach_cap_releases(session, &tmp_list);
|
||||
spin_unlock(&session->s_cap_lock);
|
||||
|
||||
while (!list_empty(&tmp_list)) {
|
||||
@@ -4091,13 +4142,19 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
|
||||
list_add_tail(&req->r_unsafe_item, &req->r_session->s_unsafe);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that all mutex-protected state has been updated above
|
||||
* (the request has been unregistered or added to the
|
||||
* session's unsafe list), we can unlock it.
|
||||
*/
|
||||
mutex_unlock(&mdsc->mutex);
|
||||
|
||||
doutc(cl, "tid %lld result %d\n", tid, result);
|
||||
if (test_bit(CEPHFS_FEATURE_REPLY_ENCODING, &session->s_features))
|
||||
err = parse_reply_info(session, msg, req, (u64)-1);
|
||||
else
|
||||
err = parse_reply_info(session, msg, req,
|
||||
session->s_con.peer_features);
|
||||
mutex_unlock(&mdsc->mutex);
|
||||
|
||||
/* Must find target inode outside of mutexes to avoid deadlocks */
|
||||
rinfo = &req->r_reply_info;
|
||||
@@ -4441,7 +4498,9 @@ static void handle_session(struct ceph_mds_session *session,
|
||||
pr_err_client(cl, "No memory for path\n");
|
||||
goto fail;
|
||||
}
|
||||
ceph_decode_copy(&p, cap_auths[i].match.path, _len);
|
||||
ceph_decode_copy_safe(&p, end,
|
||||
cap_auths[i].match.path,
|
||||
_len, bad);
|
||||
|
||||
/* Remove the tailing '/' */
|
||||
while (_len && cap_auths[i].match.path[_len - 1] == '/') {
|
||||
@@ -4458,7 +4517,9 @@ static void handle_session(struct ceph_mds_session *session,
|
||||
pr_err_client(cl, "No memory for fs_name\n");
|
||||
goto fail;
|
||||
}
|
||||
ceph_decode_copy(&p, cap_auths[i].match.fs_name, _len);
|
||||
ceph_decode_copy_safe(&p, end,
|
||||
cap_auths[i].match.fs_name,
|
||||
_len, bad);
|
||||
}
|
||||
|
||||
ceph_decode_8_safe(&p, end, cap_auths[i].match.root_squash, bad);
|
||||
@@ -5106,6 +5167,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc,
|
||||
|
||||
/* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */
|
||||
xa_destroy(&session->s_delegated_inos);
|
||||
atomic_set(&session->s_num_deleg_inos, 0);
|
||||
if (session->s_state == CEPH_MDS_SESSION_CLOSED ||
|
||||
session->s_state == CEPH_MDS_SESSION_REJECTED) {
|
||||
pr_info_client(cl, "mds%d skipping reconnect, session %s\n",
|
||||
@@ -5753,7 +5815,7 @@ int ceph_mdsc_schedule_reset(struct ceph_mds_client *mdsc,
|
||||
strscpy(st->last_reason, msg, sizeof(st->last_reason));
|
||||
spin_unlock(&st->lock);
|
||||
|
||||
if (WARN_ON_ONCE(!queue_work(system_unbound_wq, &mdsc->reset_work))) {
|
||||
if (WARN_ON_ONCE(!queue_work(system_dfl_wq, &mdsc->reset_work))) {
|
||||
spin_lock(&st->lock);
|
||||
st->phase = CEPH_CLIENT_RESET_IDLE;
|
||||
st->last_errno = -EALREADY;
|
||||
@@ -5834,9 +5896,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
|
||||
ceph_mdsmap_get_addr(newmap, i),
|
||||
sizeof(struct ceph_entity_addr))) {
|
||||
/* just close it */
|
||||
ceph_get_mds_session(s);
|
||||
mutex_unlock(&mdsc->mutex);
|
||||
mutex_lock(&s->s_mutex);
|
||||
mutex_lock(&mdsc->mutex);
|
||||
ceph_put_mds_session(s);
|
||||
ceph_con_close(&s->s_con);
|
||||
mutex_unlock(&s->s_mutex);
|
||||
s->s_state = CEPH_MDS_SESSION_RESTARTING;
|
||||
@@ -5851,6 +5915,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
|
||||
newstate >= CEPH_MDS_STATE_RECONNECT) {
|
||||
int rc;
|
||||
|
||||
ceph_get_mds_session(s);
|
||||
mutex_unlock(&mdsc->mutex);
|
||||
clear_bit(i, targets);
|
||||
rc = send_mds_reconnect(mdsc, s);
|
||||
@@ -5859,6 +5924,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
|
||||
"mds%d reconnect failed: %d\n",
|
||||
i, rc);
|
||||
mutex_lock(&mdsc->mutex);
|
||||
ceph_put_mds_session(s);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -5871,9 +5937,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
|
||||
pr_info_client(cl, "mds%d recovery completed\n",
|
||||
s->s_mds);
|
||||
kick_requests(mdsc, i);
|
||||
ceph_get_mds_session(s);
|
||||
mutex_unlock(&mdsc->mutex);
|
||||
mutex_lock(&s->s_mutex);
|
||||
mutex_lock(&mdsc->mutex);
|
||||
ceph_put_mds_session(s);
|
||||
ceph_kick_flushing_caps(mdsc, s);
|
||||
mutex_unlock(&s->s_mutex);
|
||||
wake_up_session_caps(s, RECONNECT);
|
||||
|
||||
@@ -300,6 +300,7 @@ struct ceph_mds_session {
|
||||
struct list_head s_waiting; /* waiting requests */
|
||||
struct list_head s_unsafe; /* unsafe requests */
|
||||
struct xarray s_delegated_inos;
|
||||
atomic_t s_num_deleg_inos;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
@@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
|
||||
u8 mdsmap_v;
|
||||
u16 mdsmap_ev;
|
||||
u32 target;
|
||||
size_t export_targets_len;
|
||||
|
||||
m = kzalloc_obj(*m, GFP_NOFS);
|
||||
if (!m)
|
||||
@@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
|
||||
*p += namelen;
|
||||
if (info_v >= 2) {
|
||||
ceph_decode_32_safe(p, end, num_export_targets, bad);
|
||||
export_targets_len = size_mul(num_export_targets,
|
||||
sizeof(u32));
|
||||
ceph_decode_need(p, end, export_targets_len, bad);
|
||||
pexport_targets = *p;
|
||||
*p += num_export_targets * sizeof(u32);
|
||||
*p += export_targets_len;
|
||||
} else {
|
||||
num_export_targets = 0;
|
||||
}
|
||||
@@ -264,6 +269,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
|
||||
goto nomem;
|
||||
for (j = 0; j < num_export_targets; j++) {
|
||||
target = ceph_decode_32(&pexport_targets);
|
||||
if (target >= CEPH_MAX_MDS) {
|
||||
err = -EIO;
|
||||
goto corrupt;
|
||||
}
|
||||
info->export_targets[j] = target;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -177,6 +177,7 @@ enum {
|
||||
Opt_wsync,
|
||||
Opt_pagecache,
|
||||
Opt_sparseread,
|
||||
Opt_nearfull_sync,
|
||||
};
|
||||
|
||||
enum ceph_recover_session_mode {
|
||||
@@ -205,6 +206,7 @@ static const struct fs_parameter_spec ceph_mount_parameters[] = {
|
||||
fsparam_flag_no ("ino32", Opt_ino32),
|
||||
fsparam_string ("mds_namespace", Opt_mds_namespace),
|
||||
fsparam_string ("mon_addr", Opt_mon_addr),
|
||||
fsparam_flag_no ("nearfull_sync", Opt_nearfull_sync),
|
||||
fsparam_flag_no ("poolperm", Opt_poolperm),
|
||||
fsparam_flag_no ("quotadf", Opt_quotadf),
|
||||
fsparam_u32 ("rasize", Opt_rasize),
|
||||
@@ -593,6 +595,12 @@ static int ceph_parse_mount_param(struct fs_context *fc,
|
||||
else
|
||||
fsopt->flags |= CEPH_MOUNT_OPT_SPARSEREAD;
|
||||
break;
|
||||
case Opt_nearfull_sync:
|
||||
if (result.negated)
|
||||
fsopt->flags &= ~CEPH_MOUNT_OPT_NEARFULL_SYNC;
|
||||
else
|
||||
fsopt->flags |= CEPH_MOUNT_OPT_NEARFULL_SYNC;
|
||||
break;
|
||||
case Opt_test_dummy_encryption:
|
||||
#ifdef CONFIG_FS_ENCRYPTION
|
||||
fscrypt_free_dummy_policy(&fsopt->dummy_enc_policy);
|
||||
@@ -749,6 +757,8 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root)
|
||||
seq_puts(m, ",nopagecache");
|
||||
if (fsopt->flags & CEPH_MOUNT_OPT_SPARSEREAD)
|
||||
seq_puts(m, ",sparseread");
|
||||
if (fsopt->flags & CEPH_MOUNT_OPT_NEARFULL_SYNC)
|
||||
seq_puts(m, ",nearfull_sync");
|
||||
|
||||
fscrypt_show_test_dummy_encryption(m, ',', root->d_sb);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#define CEPH_MOUNT_OPT_ASYNC_DIROPS (1<<15) /* allow async directory ops */
|
||||
#define CEPH_MOUNT_OPT_NOPAGECACHE (1<<16) /* bypass pagecache altogether */
|
||||
#define CEPH_MOUNT_OPT_SPARSEREAD (1<<17) /* always do sparse reads */
|
||||
#define CEPH_MOUNT_OPT_NEARFULL_SYNC (1<<18) /* sync writes when nearfull */
|
||||
|
||||
#define CEPH_MOUNT_OPT_DEFAULT \
|
||||
(CEPH_MOUNT_OPT_DCACHE | \
|
||||
@@ -203,7 +204,19 @@ struct ceph_fs_client {
|
||||
*/
|
||||
struct ceph_cap {
|
||||
struct ceph_inode_info *ci;
|
||||
struct rb_node ci_node; /* per-ci cap tree */
|
||||
|
||||
/**
|
||||
* Per-ci cap tree. Protected with
|
||||
* `ceph_inode_info.i_ceph_lock`.
|
||||
*
|
||||
* Clearing this field with RB_CLEAR_NODE() requires holding
|
||||
* both `ceph_inode_info.i_ceph_lock` and
|
||||
* `ceph_mds_session->s_cap_lock`. Calling RB_EMPTY_NODE()
|
||||
* (via ceph_cap_is_removed()) requires holding at least one
|
||||
* of these.
|
||||
*/
|
||||
struct rb_node ci_node;
|
||||
|
||||
struct ceph_mds_session *session;
|
||||
struct list_head session_caps; /* per-session caplist */
|
||||
u64 cap_id; /* unique cap id (mds provided) */
|
||||
@@ -641,6 +654,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data)
|
||||
#define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS)
|
||||
#define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY))
|
||||
|
||||
/*
|
||||
* Upper bound on the number of delegated inodes a single MDS session may
|
||||
* hold. The MDS normally hands out a small preallocation window (the
|
||||
* userspace mds_client_prealloc_inos option defaults to 1000) and refills
|
||||
* it as the client consumes entries. This leaves generous headroom while
|
||||
* bounding the CPU and memory a malformed delegation interval can consume.
|
||||
*/
|
||||
#define CEPH_MAX_DELEG_INOS 8192
|
||||
|
||||
static inline bool ceph_vino_is_reserved(const struct ceph_vino vino)
|
||||
{
|
||||
if (vino.ino >= CEPH_INO_SYSTEM_BASE ||
|
||||
@@ -687,6 +709,10 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
|
||||
#define CEPH_I_ASYNC_CREATE_BIT (12) /* async create in flight for this */
|
||||
#define CEPH_I_SHUTDOWN_BIT (13) /* inode is no longer usable */
|
||||
#define CEPH_I_ASYNC_CHECK_CAPS_BIT (14) /* check caps after async creating finishes */
|
||||
#define CEPH_I_FLUSH_FORCE_BIT (15) /* a revoke's response was deferred;
|
||||
* force a cap message to the MDS once
|
||||
* the deferred work completes
|
||||
*/
|
||||
|
||||
#define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT)
|
||||
#define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT)
|
||||
@@ -699,6 +725,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
|
||||
#define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT)
|
||||
#define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT)
|
||||
#define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT)
|
||||
#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT)
|
||||
|
||||
/*
|
||||
* Masks of ceph inode work.
|
||||
@@ -1269,8 +1296,22 @@ extern void ceph_add_cap(struct inode *inode,
|
||||
unsigned issued, unsigned wanted,
|
||||
unsigned cap, unsigned seq, u64 realmino, int flags,
|
||||
struct ceph_cap **new_cap);
|
||||
extern void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release);
|
||||
|
||||
/**
|
||||
* Determine whether __ceph_remove_cap() has been called on this #cap
|
||||
* (but the object has not yet been freed because it is protected by
|
||||
* `ceph_mds_session.s_cap_iterator`).
|
||||
*
|
||||
* Caller must lock either `ceph_inode_info.i_ceph_lock` or
|
||||
* `ceph_mds_session.s_cap_lock`.
|
||||
*/
|
||||
static inline bool ceph_cap_is_removed(const struct ceph_cap *cap)
|
||||
{
|
||||
return RB_EMPTY_NODE(&cap->ci_node);
|
||||
}
|
||||
|
||||
extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap,
|
||||
struct ceph_inode_info *ci,
|
||||
bool queue_release);
|
||||
extern void __ceph_remove_caps(struct ceph_inode_info *ci);
|
||||
extern void ceph_put_cap(struct ceph_mds_client *mdsc,
|
||||
|
||||
@@ -848,6 +848,7 @@ static int __build_xattrs(struct inode *inode)
|
||||
name = p;
|
||||
p += len;
|
||||
ceph_decode_32_safe(&p, end, len, bad);
|
||||
ceph_decode_need(&p, end, len, bad);
|
||||
val = p;
|
||||
p += len;
|
||||
|
||||
|
||||
@@ -313,8 +313,6 @@ int ceph_wait_for_latest_osdmap(struct ceph_client *client,
|
||||
|
||||
/* pagevec.c */
|
||||
extern void ceph_release_page_vector(struct page **pages, int num_pages);
|
||||
extern void ceph_put_page_vector(struct page **pages, int num_pages,
|
||||
bool dirty);
|
||||
extern struct page **ceph_alloc_page_vector(int num_pages, gfp_t flags);
|
||||
extern void ceph_copy_from_page_vector(struct page **pages,
|
||||
void *data,
|
||||
|
||||
@@ -2142,6 +2142,11 @@ static int process_banner_prefix(struct ceph_connection *con)
|
||||
payload_len = ceph_decode_16(&p);
|
||||
dout("%s con %p payload_len %d\n", __func__, con, payload_len);
|
||||
|
||||
if (payload_len < sizeof(u64) + sizeof(u64)) {
|
||||
con->error_msg = "protocol error, bad banner payload len";
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return prepare_read_banner_payload(con, payload_len);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
@@ -5802,6 +5803,31 @@ static inline void convert_extent_map(struct ceph_sparse_read *sr)
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sparse_extent_map_valid(struct ceph_sparse_read *sr)
|
||||
{
|
||||
u64 req_end, pos;
|
||||
int i;
|
||||
|
||||
if (check_add_overflow(sr->sr_req_off, sr->sr_req_len, &req_end))
|
||||
return false;
|
||||
|
||||
pos = sr->sr_req_off;
|
||||
for (i = 0; i < sr->sr_count; i++) {
|
||||
struct ceph_sparse_extent *ext = &sr->sr_extent[i];
|
||||
u64 end;
|
||||
|
||||
if (ext->off < pos)
|
||||
return false;
|
||||
if (check_add_overflow(ext->off, ext->len, &end))
|
||||
return false;
|
||||
if (end > req_end)
|
||||
return false;
|
||||
pos = end;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int osd_sparse_read(struct ceph_connection *con,
|
||||
struct ceph_msg_data_cursor *cursor,
|
||||
char **pbuf)
|
||||
@@ -5852,6 +5878,10 @@ static int osd_sparse_read(struct ceph_connection *con,
|
||||
fallthrough;
|
||||
case CEPH_SPARSE_READ_DATA_LEN:
|
||||
convert_extent_map(sr);
|
||||
if (!sparse_extent_map_valid(sr)) {
|
||||
pr_warn_ratelimited("invalid sparse extent map\n");
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
ret = sizeof(sr->sr_datalen);
|
||||
*pbuf = (char *)&sr->sr_datalen;
|
||||
sr->sr_state = CEPH_SPARSE_READ_DATA_PRE;
|
||||
|
||||
@@ -517,6 +517,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end)
|
||||
|
||||
ceph_decode_need(p, end, 4*sizeof(u32), bad);
|
||||
b->id = ceph_decode_32(p);
|
||||
if (b->id != -1 - i)
|
||||
goto bad;
|
||||
b->type = ceph_decode_16(p);
|
||||
if (b->type == 0)
|
||||
goto bad;
|
||||
|
||||
@@ -10,19 +10,6 @@
|
||||
|
||||
#include <linux/ceph/libceph.h>
|
||||
|
||||
void ceph_put_page_vector(struct page **pages, int num_pages, bool dirty)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_pages; i++) {
|
||||
if (dirty)
|
||||
set_page_dirty_lock(pages[i]);
|
||||
put_page(pages[i]);
|
||||
}
|
||||
kvfree(pages);
|
||||
}
|
||||
EXPORT_SYMBOL(ceph_put_page_vector);
|
||||
|
||||
void ceph_release_page_vector(struct page **pages, int num_pages)
|
||||
{
|
||||
int i;
|
||||
|
||||
Reference in New Issue
Block a user