summaryrefslogtreecommitdiffstats
path: root/src/mds/Server.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/mds/Server.cc
parentInitial commit. (diff)
downloadceph-upstream/18.2.2.tar.xz
ceph-upstream/18.2.2.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/mds/Server.cc')
-rw-r--r--src/mds/Server.cc11740
1 files changed, 11740 insertions, 0 deletions
diff --git a/src/mds/Server.cc b/src/mds/Server.cc
new file mode 100644
index 000000000..ced4ecffa
--- /dev/null
+++ b/src/mds/Server.cc
@@ -0,0 +1,11740 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include <boost/lexical_cast.hpp>
+#include "include/ceph_assert.h" // lexical_cast includes system assert.h
+
+#include <boost/config/warning_disable.hpp>
+#include <boost/fusion/include/std_pair.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+
+#include "MDSRank.h"
+#include "Server.h"
+#include "Locker.h"
+#include "MDCache.h"
+#include "MDLog.h"
+#include "Migrator.h"
+#include "MDBalancer.h"
+#include "InoTable.h"
+#include "SnapClient.h"
+#include "Mutation.h"
+#include "MetricsHandler.h"
+#include "cephfs_features.h"
+
+#include "msg/Messenger.h"
+
+#include "osdc/Objecter.h"
+
+#include "events/EUpdate.h"
+#include "events/EPeerUpdate.h"
+#include "events/ESession.h"
+#include "events/EOpen.h"
+#include "events/ECommitted.h"
+#include "events/EPurged.h"
+
+#include "include/stringify.h"
+#include "include/filepath.h"
+#include "common/errno.h"
+#include "common/Timer.h"
+#include "common/perf_counters.h"
+#include "include/compat.h"
+#include "osd/OSDMap.h"
+#include "fscrypt.h"
+
+#include <errno.h>
+
+#include <list>
+#include <regex>
+#include <string_view>
+#include <functional>
+
+#include "common/config.h"
+
+#include "msg/Message.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_mds
+#undef dout_prefix
+#define dout_prefix *_dout << "mds." << mds->get_nodeid() << ".server "
+
+using namespace std;
+
+class ServerContext : public MDSContext {
+ protected:
+ Server *server;
+ MDSRank *get_mds() override
+ {
+ return server->mds;
+ }
+
+ public:
+ explicit ServerContext(Server *s) : server(s) {
+ ceph_assert(server != NULL);
+ }
+};
+
+class Batch_Getattr_Lookup : public BatchOp {
+protected:
+ Server* server;
+ ceph::ref_t<MDRequestImpl> mdr;
+ std::vector<ceph::ref_t<MDRequestImpl>> batch_reqs;
+ int res = 0;
+public:
+ Batch_Getattr_Lookup(Server* s, const ceph::ref_t<MDRequestImpl>& r)
+ : server(s), mdr(r) {
+ if (mdr->client_request->get_op() == CEPH_MDS_OP_LOOKUP)
+ mdr->batch_op_map = &mdr->dn[0].back()->batch_ops;
+ else
+ mdr->batch_op_map = &mdr->in[0]->batch_ops;
+ }
+ void add_request(const ceph::ref_t<MDRequestImpl>& r) override {
+ batch_reqs.push_back(r);
+ }
+ ceph::ref_t<MDRequestImpl> find_new_head() override {
+ while (!batch_reqs.empty()) {
+ auto r = std::move(batch_reqs.back());
+ batch_reqs.pop_back();
+ if (r->killed)
+ continue;
+
+ r->batch_op_map = mdr->batch_op_map;
+ mdr->batch_op_map = nullptr;
+ mdr = r;
+ return mdr;
+ }
+ return nullptr;
+ }
+ void _forward(mds_rank_t t) override {
+ MDCache* mdcache = server->mdcache;
+ mdcache->mds->forward_message_mds(mdr, t);
+ mdr->set_mds_stamp(ceph_clock_now());
+ for (auto& m : batch_reqs) {
+ if (!m->killed)
+ mdcache->request_forward(m, t);
+ }
+ batch_reqs.clear();
+ }
+ void _respond(int r) override {
+ mdr->set_mds_stamp(ceph_clock_now());
+ for (auto& m : batch_reqs) {
+ if (!m->killed) {
+ m->tracei = mdr->tracei;
+ m->tracedn = mdr->tracedn;
+ server->respond_to_request(m, r);
+ }
+ }
+ batch_reqs.clear();
+ server->reply_client_request(mdr, make_message<MClientReply>(*mdr->client_request, r));
+ }
+ void print(std::ostream& o) const override {
+ o << "[batch front=" << *mdr << "]";
+ }
+};
+
+class ServerLogContext : public MDSLogContextBase {
+protected:
+ Server *server;
+ MDSRank *get_mds() override
+ {
+ return server->mds;
+ }
+
+ MDRequestRef mdr;
+ void pre_finish(int r) override {
+ if (mdr)
+ mdr->mark_event("journal_committed: ");
+ }
+public:
+ explicit ServerLogContext(Server *s) : server(s) {
+ ceph_assert(server != NULL);
+ }
+ explicit ServerLogContext(Server *s, MDRequestRef& r) : server(s), mdr(r) {
+ ceph_assert(server != NULL);
+ }
+};
+
+void Server::create_logger()
+{
+ PerfCountersBuilder plb(g_ceph_context, "mds_server", l_mdss_first, l_mdss_last);
+
+ plb.add_u64_counter(l_mdss_handle_client_request, "handle_client_request",
+ "Client requests", "hcr", PerfCountersBuilder::PRIO_INTERESTING);
+ plb.add_u64_counter(l_mdss_handle_peer_request, "handle_peer_request",
+ "Peer requests", "hsr", PerfCountersBuilder::PRIO_INTERESTING);
+ plb.add_u64_counter(l_mdss_handle_client_session,
+ "handle_client_session", "Client session messages", "hcs",
+ PerfCountersBuilder::PRIO_INTERESTING);
+ plb.add_u64_counter(l_mdss_cap_revoke_eviction, "cap_revoke_eviction",
+ "Cap Revoke Client Eviction", "cre", PerfCountersBuilder::PRIO_INTERESTING);
+ plb.add_u64_counter(l_mdss_cap_acquisition_throttle,
+ "cap_acquisition_throttle", "Cap acquisition throttle counter", "cat",
+ PerfCountersBuilder::PRIO_INTERESTING);
+
+ // fop latencies are useful
+ plb.set_prio_default(PerfCountersBuilder::PRIO_USEFUL);
+ plb.add_time_avg(l_mdss_req_lookuphash_latency, "req_lookuphash_latency",
+ "Request type lookup hash of inode latency");
+ plb.add_time_avg(l_mdss_req_lookupino_latency, "req_lookupino_latency",
+ "Request type lookup inode latency");
+ plb.add_time_avg(l_mdss_req_lookupparent_latency, "req_lookupparent_latency",
+ "Request type lookup parent latency");
+ plb.add_time_avg(l_mdss_req_lookupname_latency, "req_lookupname_latency",
+ "Request type lookup name latency");
+ plb.add_time_avg(l_mdss_req_lookup_latency, "req_lookup_latency",
+ "Request type lookup latency");
+ plb.add_time_avg(l_mdss_req_lookupsnap_latency, "req_lookupsnap_latency",
+ "Request type lookup snapshot latency");
+ plb.add_time_avg(l_mdss_req_getattr_latency, "req_getattr_latency",
+ "Request type get attribute latency");
+ plb.add_time_avg(l_mdss_req_setattr_latency, "req_setattr_latency",
+ "Request type set attribute latency");
+ plb.add_time_avg(l_mdss_req_setlayout_latency, "req_setlayout_latency",
+ "Request type set file layout latency");
+ plb.add_time_avg(l_mdss_req_setdirlayout_latency, "req_setdirlayout_latency",
+ "Request type set directory layout latency");
+ plb.add_time_avg(l_mdss_req_getvxattr_latency, "req_getvxattr_latency",
+ "Request type get virtual extended attribute latency");
+ plb.add_time_avg(l_mdss_req_setxattr_latency, "req_setxattr_latency",
+ "Request type set extended attribute latency");
+ plb.add_time_avg(l_mdss_req_rmxattr_latency, "req_rmxattr_latency",
+ "Request type remove extended attribute latency");
+ plb.add_time_avg(l_mdss_req_readdir_latency, "req_readdir_latency",
+ "Request type read directory latency");
+ plb.add_time_avg(l_mdss_req_setfilelock_latency, "req_setfilelock_latency",
+ "Request type set file lock latency");
+ plb.add_time_avg(l_mdss_req_getfilelock_latency, "req_getfilelock_latency",
+ "Request type get file lock latency");
+ plb.add_time_avg(l_mdss_req_create_latency, "req_create_latency",
+ "Request type create latency");
+ plb.add_time_avg(l_mdss_req_open_latency, "req_open_latency",
+ "Request type open latency");
+ plb.add_time_avg(l_mdss_req_mknod_latency, "req_mknod_latency",
+ "Request type make node latency");
+ plb.add_time_avg(l_mdss_req_link_latency, "req_link_latency",
+ "Request type link latency");
+ plb.add_time_avg(l_mdss_req_unlink_latency, "req_unlink_latency",
+ "Request type unlink latency");
+ plb.add_time_avg(l_mdss_req_rmdir_latency, "req_rmdir_latency",
+ "Request type remove directory latency");
+ plb.add_time_avg(l_mdss_req_rename_latency, "req_rename_latency",
+ "Request type rename latency");
+ plb.add_time_avg(l_mdss_req_mkdir_latency, "req_mkdir_latency",
+ "Request type make directory latency");
+ plb.add_time_avg(l_mdss_req_symlink_latency, "req_symlink_latency",
+ "Request type symbolic link latency");
+ plb.add_time_avg(l_mdss_req_lssnap_latency, "req_lssnap_latency",
+ "Request type list snapshot latency");
+ plb.add_time_avg(l_mdss_req_mksnap_latency, "req_mksnap_latency",
+ "Request type make snapshot latency");
+ plb.add_time_avg(l_mdss_req_rmsnap_latency, "req_rmsnap_latency",
+ "Request type remove snapshot latency");
+ plb.add_time_avg(l_mdss_req_renamesnap_latency, "req_renamesnap_latency",
+ "Request type rename snapshot latency");
+ plb.add_time_avg(l_mdss_req_snapdiff_latency, "req_snapdiff_latency",
+ "Request type snapshot difference latency");
+
+ plb.set_prio_default(PerfCountersBuilder::PRIO_DEBUGONLY);
+ plb.add_u64_counter(l_mdss_dispatch_client_request, "dispatch_client_request",
+ "Client requests dispatched");
+ plb.add_u64_counter(l_mdss_dispatch_peer_request, "dispatch_server_request",
+ "Server requests dispatched");
+
+ logger = plb.create_perf_counters();
+ g_ceph_context->get_perfcounters_collection()->add(logger);
+}
+
+Server::Server(MDSRank *m, MetricsHandler *metrics_handler) :
+ mds(m),
+ mdcache(mds->mdcache), mdlog(mds->mdlog),
+ inject_rename_corrupt_dentry_first(g_conf().get_val<double>("mds_inject_rename_corrupt_dentry_first")),
+ recall_throttle(g_conf().get_val<double>("mds_recall_max_decay_rate")),
+ metrics_handler(metrics_handler)
+{
+ forward_all_requests_to_auth = g_conf().get_val<bool>("mds_forward_all_requests_to_auth");
+ replay_unsafe_with_closed_session = g_conf().get_val<bool>("mds_replay_unsafe_with_closed_session");
+ cap_revoke_eviction_timeout = g_conf().get_val<double>("mds_cap_revoke_eviction_timeout");
+ max_snaps_per_dir = g_conf().get_val<uint64_t>("mds_max_snaps_per_dir");
+ delegate_inos_pct = g_conf().get_val<uint64_t>("mds_client_delegate_inos_pct");
+ max_caps_per_client = g_conf().get_val<uint64_t>("mds_max_caps_per_client");
+ cap_acquisition_throttle = g_conf().get_val<uint64_t>("mds_session_cap_acquisition_throttle");
+ max_caps_throttle_ratio = g_conf().get_val<double>("mds_session_max_caps_throttle_ratio");
+ caps_throttle_retry_request_timeout = g_conf().get_val<double>("mds_cap_acquisition_throttle_retry_request_timeout");
+ dir_max_entries = g_conf().get_val<uint64_t>("mds_dir_max_entries");
+ bal_fragment_size_max = g_conf().get_val<int64_t>("mds_bal_fragment_size_max");
+ supported_features = feature_bitset_t(CEPHFS_FEATURES_MDS_SUPPORTED);
+ supported_metric_spec = feature_bitset_t(CEPHFS_METRIC_FEATURES_ALL);
+}
+
+void Server::dispatch(const cref_t<Message> &m)
+{
+ switch (m->get_type()) {
+ case CEPH_MSG_CLIENT_RECONNECT:
+ handle_client_reconnect(ref_cast<MClientReconnect>(m));
+ return;
+ }
+
+/*
+ *In reconnect phase, client sent unsafe requests to mds before reconnect msg. Seting sessionclosed_isok will handle scenario like this:
+
+1. In reconnect phase, client sent unsafe requests to mds.
+2. It reached reconnect timeout. All sessions without sending reconnect msg in time, some of which may had sent unsafe requests, are marked as closed.
+(Another situation is #31668, which will deny all client reconnect msg to speed up reboot).
+3.So these unsafe request from session without sending reconnect msg in time or being denied could be handled in clientreplay phase.
+
+*/
+ bool sessionclosed_isok = replay_unsafe_with_closed_session;
+ // active?
+ // handle_peer_request()/handle_client_session() will wait if necessary
+ if (m->get_type() == CEPH_MSG_CLIENT_REQUEST && !mds->is_active()) {
+ const auto &req = ref_cast<MClientRequest>(m);
+ if (mds->is_reconnect() || mds->get_want_state() == CEPH_MDS_STATE_RECONNECT) {
+ Session *session = mds->get_session(req);
+ if (!session || (!session->is_open() && !sessionclosed_isok)) {
+ dout(5) << "session is closed, dropping " << req->get_reqid() << dendl;
+ return;
+ }
+ bool queue_replay = false;
+ if (req->is_replay() || req->is_async()) {
+ dout(3) << "queuing replayed op" << dendl;
+ queue_replay = true;
+ if (req->head.ino &&
+ !session->have_completed_request(req->get_reqid().tid, nullptr)) {
+ inodeno_t ino(req->head.ino);
+ mdcache->add_replay_ino_alloc(ino);
+ if (replay_unsafe_with_closed_session &&
+ session->free_prealloc_inos.contains(ino)) {
+ // don't purge inodes that will be created by later replay
+ session->free_prealloc_inos.erase(ino);
+ session->delegated_inos.insert(ino);
+ }
+ }
+ } else if (req->get_retry_attempt()) {
+ // process completed request in clientreplay stage. The completed request
+ // might have created new file/directorie. This guarantees MDS sends a reply
+ // to client before other request modifies the new file/directorie.
+ if (session->have_completed_request(req->get_reqid().tid, NULL)) {
+ dout(3) << "queuing completed op" << dendl;
+ queue_replay = true;
+ }
+ // this request was created before the cap reconnect message, drop any embedded
+ // cap releases.
+ req->releases.clear();
+ }
+ if (queue_replay) {
+ req->mark_queued_for_replay();
+ mds->enqueue_replay(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+ }
+
+ bool wait_for_active = true;
+ if (mds->is_stopping()) {
+ wait_for_active = false;
+ } else if (mds->is_clientreplay()) {
+ if (req->is_queued_for_replay()) {
+ wait_for_active = false;
+ }
+ }
+ if (wait_for_active) {
+ dout(3) << "not active yet, waiting" << dendl;
+ mds->wait_for_active(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+ }
+
+ switch (m->get_type()) {
+ case CEPH_MSG_CLIENT_SESSION:
+ handle_client_session(ref_cast<MClientSession>(m));
+ return;
+ case CEPH_MSG_CLIENT_REQUEST:
+ handle_client_request(ref_cast<MClientRequest>(m));
+ return;
+ case CEPH_MSG_CLIENT_RECLAIM:
+ handle_client_reclaim(ref_cast<MClientReclaim>(m));
+ return;
+ case MSG_MDS_PEER_REQUEST:
+ handle_peer_request(ref_cast<MMDSPeerRequest>(m));
+ return;
+ default:
+ derr << "Server unknown message " << m->get_type() << " from peer type " << m->get_connection()->get_peer_type() << dendl;
+ ceph_abort_msg("server unknown message " + to_string(m->get_type()) + " from peer type " + to_string(m->get_connection()->get_peer_type()));
+ }
+}
+
+
+
+// ----------------------------------------------------------
+// SESSION management
+
+class C_MDS_session_finish : public ServerLogContext {
+ Session *session;
+ uint64_t state_seq;
+ bool open;
+ version_t cmapv;
+ interval_set<inodeno_t> inos_to_free;
+ version_t inotablev;
+ interval_set<inodeno_t> inos_to_purge;
+ LogSegment *ls = nullptr;
+ Context *fin;
+public:
+ C_MDS_session_finish(Server *srv, Session *se, uint64_t sseq, bool s, version_t mv, Context *fin_ = nullptr) :
+ ServerLogContext(srv), session(se), state_seq(sseq), open(s), cmapv(mv), inotablev(0), fin(fin_) { }
+ C_MDS_session_finish(Server *srv, Session *se, uint64_t sseq, bool s, version_t mv,
+ const interval_set<inodeno_t>& to_free, version_t iv,
+ const interval_set<inodeno_t>& to_purge, LogSegment *_ls, Context *fin_ = nullptr) :
+ ServerLogContext(srv), session(se), state_seq(sseq), open(s), cmapv(mv),
+ inos_to_free(to_free), inotablev(iv), inos_to_purge(to_purge), ls(_ls), fin(fin_) {}
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_session_logged(session, state_seq, open, cmapv, inos_to_free, inotablev, inos_to_purge, ls);
+ if (fin) {
+ fin->complete(r);
+ }
+ }
+};
+
+Session* Server::find_session_by_uuid(std::string_view uuid)
+{
+ Session* session = nullptr;
+ for (auto& it : mds->sessionmap.get_sessions()) {
+ auto& metadata = it.second->info.client_metadata;
+
+ auto p = metadata.find("uuid");
+ if (p == metadata.end() || p->second != uuid)
+ continue;
+
+ if (!session) {
+ session = it.second;
+ } else if (!session->reclaiming_from) {
+ ceph_assert(it.second->reclaiming_from == session);
+ session = it.second;
+ } else {
+ ceph_assert(session->reclaiming_from == it.second);
+ }
+ }
+ return session;
+}
+
+void Server::reclaim_session(Session *session, const cref_t<MClientReclaim> &m)
+{
+ if (!session->is_open() && !session->is_stale()) {
+ dout(10) << "session not open, dropping this req" << dendl;
+ return;
+ }
+
+ auto reply = make_message<MClientReclaimReply>(0);
+ if (m->get_uuid().empty()) {
+ dout(10) << __func__ << " invalid message (no uuid)" << dendl;
+ reply->set_result(-CEPHFS_EINVAL);
+ mds->send_message_client(reply, session);
+ return;
+ }
+
+ unsigned flags = m->get_flags();
+ if (flags != CEPH_RECLAIM_RESET) { // currently only support reset
+ dout(10) << __func__ << " unsupported flags" << dendl;
+ reply->set_result(-CEPHFS_EINVAL);
+ mds->send_message_client(reply, session);
+ return;
+ }
+
+ Session* target = find_session_by_uuid(m->get_uuid());
+ if (target) {
+ if (session->info.auth_name != target->info.auth_name) {
+ dout(10) << __func__ << " session auth_name " << session->info.auth_name
+ << " != target auth_name " << target->info.auth_name << dendl;
+ reply->set_result(-CEPHFS_EPERM);
+ mds->send_message_client(reply, session);
+ }
+
+ ceph_assert(!target->reclaiming_from);
+ ceph_assert(!session->reclaiming_from);
+ session->reclaiming_from = target;
+ reply->set_addrs(entity_addrvec_t(target->info.inst.addr));
+ }
+
+ if (flags & CEPH_RECLAIM_RESET) {
+ finish_reclaim_session(session, reply);
+ } else ceph_assert(0); /* no other flags are handled at this time */
+}
+
+void Server::finish_reclaim_session(Session *session, const ref_t<MClientReclaimReply> &reply)
+{
+ Session *target = session->reclaiming_from;
+ if (target) {
+ session->reclaiming_from = nullptr;
+
+ Context *send_reply;
+ if (reply) {
+ int64_t session_id = session->get_client().v;
+ send_reply = new LambdaContext([this, session_id, reply](int r) {
+ ceph_assert(ceph_mutex_is_locked_by_me(mds->mds_lock));
+ Session *session = mds->sessionmap.get_session(entity_name_t::CLIENT(session_id));
+ if (!session) {
+ return;
+ }
+ auto epoch = mds->objecter->with_osdmap([](const OSDMap &map){ return map.get_epoch(); });
+ reply->set_epoch(epoch);
+ mds->send_message_client(reply, session);
+ });
+ } else {
+ send_reply = nullptr;
+ }
+
+ bool blocklisted = mds->objecter->with_osdmap([target](const OSDMap &map) {
+ return map.is_blocklisted(target->info.inst.addr);
+ });
+
+ if (blocklisted || !g_conf()->mds_session_blocklist_on_evict) {
+ kill_session(target, send_reply);
+ } else {
+ CachedStackStringStream css;
+ mds->evict_client(target->get_client().v, false, true, *css, send_reply);
+ }
+ } else if (reply) {
+ mds->send_message_client(reply, session);
+ }
+}
+
+void Server::handle_client_reclaim(const cref_t<MClientReclaim> &m)
+{
+ Session *session = mds->get_session(m);
+ uint32_t flags = m->get_flags();
+ dout(3) << __func__ << " " << *m << " from " << m->get_source() << dendl;
+ ceph_assert(m->is_a_client()); // should _not_ come from an mds!
+
+ if (!session) {
+ dout(0) << " ignoring sessionless msg " << *m << dendl;
+ return;
+ }
+
+ std::string_view fs_name = mds->mdsmap->get_fs_name();
+ if (!fs_name.empty() && !session->fs_name_capable(fs_name, MAY_READ)) {
+ dout(0) << " dropping message not allowed for this fs_name: " << *m << dendl;
+ return;
+ }
+
+ if (mds->get_state() < MDSMap::STATE_CLIENTREPLAY) {
+ mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+
+ if (flags & MClientReclaim::FLAG_FINISH) {
+ if (flags ^ MClientReclaim::FLAG_FINISH) {
+ dout(0) << __func__ << " client specified FLAG_FINISH with other flags."
+ " Other flags:" << flags << dendl;
+ auto reply = make_message<MClientReclaimReply>(0);
+ reply->set_result(-CEPHFS_EINVAL);
+ mds->send_message_client(reply, session);
+ return;
+ }
+ finish_reclaim_session(session);
+ } else {
+ reclaim_session(session, m);
+ }
+}
+
+void Server::handle_client_session(const cref_t<MClientSession> &m)
+{
+ version_t pv;
+ Session *session = mds->get_session(m);
+
+ dout(3) << "handle_client_session " << *m << " from " << m->get_source() << dendl;
+ ceph_assert(m->is_a_client()); // should _not_ come from an mds!
+
+ if (!session) {
+ dout(0) << " ignoring sessionless msg " << *m << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_REJECT);
+ reply->metadata["error_string"] = "sessionless";
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+
+ std::string_view fs_name = mds->mdsmap->get_fs_name();
+ if (!fs_name.empty() && !session->fs_name_capable(fs_name, MAY_READ)) {
+ dout(0) << " dropping message not allowed for this fs_name: " << *m << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_REJECT);
+ reply->metadata["error_string"] = "client doesn't have caps for FS \"" +
+ std::string(fs_name) + "\"";
+ mds->send_message(std::move(reply), m->get_connection());
+ return;
+ }
+
+ if (m->get_op() == CEPH_SESSION_REQUEST_RENEWCAPS) {
+ // always handle renewcaps (state >= MDSMap::STATE_RECONNECT)
+ } else if (m->get_op() == CEPH_SESSION_REQUEST_CLOSE) {
+ // close requests need to be handled when mds is active
+ if (mds->get_state() < MDSMap::STATE_ACTIVE) {
+ mds->wait_for_active(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+ } else {
+ if (mds->get_state() < MDSMap::STATE_CLIENTREPLAY) {
+ mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+ }
+
+ if (logger)
+ logger->inc(l_mdss_handle_client_session);
+
+ uint64_t sseq = 0;
+ switch (m->get_op()) {
+ case CEPH_SESSION_REQUEST_OPEN:
+ if(mds->mdsmap->test_flag(CEPH_MDSMAP_REFUSE_CLIENT_SESSION)) {
+ dout(0) << "new sessions are not permitted, enable again via"
+ "`ceph fs set <fs_name> refuse_client_session false`" << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_REJECT);
+ reply->metadata["error_string"] = "new sessions are not permitted,"
+ " enable again via `ceph fs set"
+ " <fs_name> refuse_client_session false`";
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+ if (session->is_opening() ||
+ session->is_open() ||
+ session->is_stale() ||
+ session->is_killing() ||
+ terminating_sessions) {
+ if (m->supported_features.test(CEPHFS_FEATURE_NOTIFY_SESSION_STATE)) {
+ if (session->is_open() && !mds->is_stopping()) {
+ dout(10) << "currently already opened" << dendl;
+
+ auto reply = make_message<MClientSession>(CEPH_SESSION_OPEN,
+ session->get_push_seq());
+ if (session->info.has_feature(CEPHFS_FEATURE_MIMIC))
+ reply->supported_features = supported_features;
+ mds->send_message_client(reply, session);
+ if (mdcache->is_readonly()) {
+ auto m = make_message<MClientSession>(CEPH_SESSION_FORCE_RO);
+ mds->send_message_client(m, session);
+ }
+ }
+ }
+ dout(10) << "currently " << session->get_state_name()
+ << ", dropping this req" << dendl;
+ return;
+ }
+ ceph_assert(session->is_closed() || session->is_closing());
+
+ if (mds->is_stopping()) {
+ dout(10) << "mds is stopping, dropping open req" << dendl;
+ return;
+ }
+
+ {
+ auto& addr = session->info.inst.addr;
+ session->set_client_metadata(client_metadata_t(m->metadata, m->supported_features, m->metric_spec));
+ auto& client_metadata = session->info.client_metadata;
+
+ auto log_session_status = [this, m, session](std::string_view status, std::string_view err) {
+ auto now = ceph_clock_now();
+ auto throttle_elapsed = m->get_recv_complete_stamp() - m->get_throttle_stamp();
+ auto elapsed = now - m->get_recv_stamp();
+ CachedStackStringStream css;
+ *css << "New client session:"
+ << " addr=\"" << session->info.inst.addr << "\""
+ << ",elapsed=" << elapsed
+ << ",throttled=" << throttle_elapsed
+ << ",status=\"" << status << "\"";
+ if (!err.empty()) {
+ *css << ",error=\"" << err << "\"";
+ }
+ const auto& metadata = session->info.client_metadata;
+ if (auto it = metadata.find("root"); it != metadata.end()) {
+ *css << ",root=\"" << it->second << "\"";
+ }
+ dout(2) << css->strv() << dendl;
+ };
+
+ auto send_reject_message = [this, &session, &log_session_status](std::string_view err_str, unsigned flags=0) {
+ auto m = make_message<MClientSession>(CEPH_SESSION_REJECT, 0, flags);
+ if (session->info.has_feature(CEPHFS_FEATURE_MIMIC))
+ m->metadata["error_string"] = err_str;
+ mds->send_message_client(m, session);
+ log_session_status("REJECTED", err_str);
+ };
+
+ bool blocklisted = mds->objecter->with_osdmap(
+ [&addr](const OSDMap &osd_map) -> bool {
+ return osd_map.is_blocklisted(addr);
+ });
+
+ if (blocklisted) {
+ dout(10) << "rejecting blocklisted client " << addr << dendl;
+ // This goes on the wire and the "blacklisted" substring is
+ // depended upon by the kernel client for detecting whether it
+ // has been blocklisted. If mounted with recover_session=clean
+ // (since 5.4), it tries to automatically recover itself from
+ // blocklisting.
+ unsigned flags = 0;
+ flags |= MClientSession::SESSION_BLOCKLISTED;
+ send_reject_message("blocklisted (blacklisted)", flags);
+ session->clear();
+ break;
+ }
+
+ if (client_metadata.features.empty())
+ infer_supported_features(session, client_metadata);
+
+ dout(20) << __func__ << " CEPH_SESSION_REQUEST_OPEN metadata entries:" << dendl;
+ dout(20) << " features: '" << client_metadata.features << "'" << dendl;
+ dout(20) << " metric specification: [" << client_metadata.metric_spec << "]" << dendl;
+ for (const auto& p : client_metadata) {
+ dout(20) << " " << p.first << ": " << p.second << dendl;
+ }
+
+ feature_bitset_t missing_features = required_client_features;
+ missing_features -= client_metadata.features;
+ if (!missing_features.empty()) {
+ CachedStackStringStream css;
+ *css << "missing required features '" << missing_features << "'";
+ send_reject_message(css->strv());
+ mds->clog->warn() << "client session (" << session->info.inst
+ << ") lacks required features " << missing_features
+ << "; client supports " << client_metadata.features;
+ session->clear();
+ break;
+ }
+
+ // Special case for the 'root' metadata path; validate that the claimed
+ // root is actually within the caps of the session
+ if (auto it = client_metadata.find("root"); it != client_metadata.end()) {
+ auto claimed_root = it->second;
+ CachedStackStringStream css;
+ bool denied = false;
+ // claimed_root has a leading "/" which we strip before passing
+ // into caps check
+ if (claimed_root.empty() || claimed_root[0] != '/') {
+ denied = true;
+ *css << "invalue root '" << claimed_root << "'";
+ } else if (!session->auth_caps.path_capable(claimed_root.substr(1))) {
+ denied = true;
+ *css << "non-allowable root '" << claimed_root << "'";
+ }
+
+ if (denied) {
+ // Tell the client we're rejecting their open
+ send_reject_message(css->strv());
+ mds->clog->warn() << "client session with " << css->strv()
+ << " denied (" << session->info.inst << ")";
+ session->clear();
+ break;
+ }
+ }
+
+ if (auto it = client_metadata.find("uuid"); it != client_metadata.end()) {
+ if (find_session_by_uuid(it->second)) {
+ send_reject_message("duplicated session uuid");
+ mds->clog->warn() << "client session with duplicated session uuid '"
+ << it->second << "' denied (" << session->info.inst << ")";
+ session->clear();
+ break;
+ }
+ }
+
+ if (session->is_closed()) {
+ mds->sessionmap.add_session(session);
+ }
+
+ pv = mds->sessionmap.mark_projected(session);
+ sseq = mds->sessionmap.set_state(session, Session::STATE_OPENING);
+ mds->sessionmap.touch_session(session);
+ auto fin = new LambdaContext([log_session_status = std::move(log_session_status)](int r){
+ ceph_assert(r == 0);
+ log_session_status("ACCEPTED", "");
+ });
+ mdlog->start_submit_entry(new ESession(m->get_source_inst(), true, pv, client_metadata),
+ new C_MDS_session_finish(this, session, sseq, true, pv, fin));
+ mdlog->flush();
+ }
+ break;
+
+ case CEPH_SESSION_REQUEST_RENEWCAPS:
+ if (session->is_open() || session->is_stale()) {
+ mds->sessionmap.touch_session(session);
+ if (session->is_stale()) {
+ mds->sessionmap.set_state(session, Session::STATE_OPEN);
+ mds->locker->resume_stale_caps(session);
+ mds->sessionmap.touch_session(session);
+ }
+ auto reply = make_message<MClientSession>(CEPH_SESSION_RENEWCAPS, m->get_seq());
+ mds->send_message_client(reply, session);
+ } else {
+ dout(10) << "ignoring renewcaps on non open|stale session (" << session->get_state_name() << ")" << dendl;
+ }
+ break;
+
+ case CEPH_SESSION_REQUEST_CLOSE:
+ {
+ if (session->is_closed() ||
+ session->is_closing() ||
+ session->is_killing()) {
+ dout(10) << "already closed|closing|killing, dropping this req" << dendl;
+ return;
+ }
+ if (session->is_importing()) {
+ dout(10) << "ignoring close req on importing session" << dendl;
+ return;
+ }
+ ceph_assert(session->is_open() ||
+ session->is_stale() ||
+ session->is_opening());
+ if (m->get_seq() < session->get_push_seq()) {
+ dout(10) << "old push seq " << m->get_seq() << " < " << session->get_push_seq()
+ << ", dropping" << dendl;
+ return;
+ }
+ // We are getting a seq that is higher than expected.
+ // Handle the same as any other seqn error.
+ //
+ if (m->get_seq() != session->get_push_seq()) {
+ dout(0) << "old push seq " << m->get_seq() << " != " << session->get_push_seq()
+ << ", BUGGY!" << dendl;
+ mds->clog->warn() << "incorrect push seq " << m->get_seq() << " != "
+ << session->get_push_seq() << ", dropping" << " from client : " << session->get_human_name();
+ return;
+ }
+ journal_close_session(session, Session::STATE_CLOSING, NULL);
+ }
+ break;
+
+ case CEPH_SESSION_FLUSHMSG_ACK:
+ finish_flush_session(session, m->get_seq());
+ break;
+
+ case CEPH_SESSION_REQUEST_FLUSH_MDLOG:
+ if (mds->is_active())
+ mdlog->flush();
+ break;
+
+ default:
+ auto m = make_message<MClientSession>(CEPH_SESSION_REJECT);
+ mds->send_message_client(m, session);
+ derr << "Server received unknown message " << m->get_type() << ", closing session and blocklisting the client " << session->get_client() << dendl;
+ CachedStackStringStream css;
+ mds->evict_client(session->get_client().v, false, true, *css, nullptr);
+ }
+}
+
+void Server::flush_session(Session *session, MDSGatherBuilder& gather) {
+ if (!session->is_open() ||
+ !session->get_connection() ||
+ !session->get_connection()->has_feature(CEPH_FEATURE_EXPORT_PEER)) {
+ return;
+ }
+
+ version_t seq = session->wait_for_flush(gather.new_sub());
+ mds->send_message_client(
+ make_message<MClientSession>(CEPH_SESSION_FLUSHMSG, seq), session);
+}
+
+void Server::flush_client_sessions(set<client_t>& client_set, MDSGatherBuilder& gather)
+{
+ for (const auto& client : client_set) {
+ Session *session = mds->sessionmap.get_session(entity_name_t::CLIENT(client.v));
+ ceph_assert(session);
+ flush_session(session, gather);
+ }
+}
+
+void Server::finish_flush_session(Session *session, version_t seq)
+{
+ MDSContext::vec finished;
+ session->finish_flush(seq, finished);
+ mds->queue_waiters(finished);
+}
+
+void Server::_session_logged(Session *session, uint64_t state_seq, bool open, version_t pv,
+ const interval_set<inodeno_t>& inos_to_free, version_t piv,
+ const interval_set<inodeno_t>& inos_to_purge, LogSegment *ls)
+{
+ dout(10) << "_session_logged " << session->info.inst
+ << " state_seq " << state_seq
+ << " " << (open ? "open":"close") << " " << pv
+ << " inos_to_free " << inos_to_free << " inotablev " << piv
+ << " inos_to_purge " << inos_to_purge << dendl;
+
+ if (!open) {
+ if (inos_to_purge.size()){
+ ceph_assert(ls);
+ session->info.prealloc_inos.subtract(inos_to_purge);
+ ls->purging_inodes.insert(inos_to_purge);
+ if (mds->is_clientreplay() || mds->is_active() || mds->is_stopping())
+ mdcache->purge_inodes(inos_to_purge, ls);
+ }
+
+ if (inos_to_free.size()) {
+ ceph_assert(piv);
+ ceph_assert(session->is_closing() || session->is_killing() ||
+ session->is_opening()); // re-open closing session
+ session->info.prealloc_inos.subtract(inos_to_free);
+ mds->inotable->apply_release_ids(inos_to_free);
+ ceph_assert(mds->inotable->get_version() == piv);
+ }
+ session->free_prealloc_inos = session->info.prealloc_inos;
+ session->delegated_inos.clear();
+ }
+
+ mds->sessionmap.mark_dirty(session);
+
+ // apply
+ if (session->get_state_seq() != state_seq) {
+ dout(10) << " journaled state_seq " << state_seq << " != current " << session->get_state_seq()
+ << ", noop" << dendl;
+ // close must have been canceled (by an import?), or any number of other things..
+ } else if (open) {
+ ceph_assert(session->is_opening());
+ mds->sessionmap.set_state(session, Session::STATE_OPEN);
+ mds->sessionmap.touch_session(session);
+ metrics_handler->add_session(session);
+ ceph_assert(session->get_connection());
+ auto reply = make_message<MClientSession>(CEPH_SESSION_OPEN);
+ if (session->info.has_feature(CEPHFS_FEATURE_MIMIC)) {
+ reply->supported_features = supported_features;
+ reply->metric_spec = supported_metric_spec;
+ }
+ mds->send_message_client(reply, session);
+ if (mdcache->is_readonly()) {
+ auto m = make_message<MClientSession>(CEPH_SESSION_FORCE_RO);
+ mds->send_message_client(m, session);
+ }
+ } else if (session->is_closing() ||
+ session->is_killing()) {
+ // kill any lingering capabilities, leases, requests
+ bool killing = session->is_killing();
+ while (!session->caps.empty()) {
+ Capability *cap = session->caps.front();
+ CInode *in = cap->get_inode();
+ dout(20) << " killing capability " << ccap_string(cap->issued()) << " on " << *in << dendl;
+ mds->locker->remove_client_cap(in, cap, killing);
+ }
+ while (!session->leases.empty()) {
+ ClientLease *r = session->leases.front();
+ CDentry *dn = static_cast<CDentry*>(r->parent);
+ dout(20) << " killing client lease of " << *dn << dendl;
+ dn->remove_client_lease(r, mds->locker);
+ }
+ if (client_reconnect_gather.erase(session->info.get_client())) {
+ dout(20) << " removing client from reconnect set" << dendl;
+ if (client_reconnect_gather.empty()) {
+ dout(7) << " client " << session->info.inst << " was last reconnect, finishing" << dendl;
+ reconnect_gather_finish();
+ }
+ }
+ if (client_reclaim_gather.erase(session->info.get_client())) {
+ dout(20) << " removing client from reclaim set" << dendl;
+ if (client_reclaim_gather.empty()) {
+ dout(7) << " client " << session->info.inst << " was last reclaimed, finishing" << dendl;
+ mds->maybe_clientreplay_done();
+ }
+ }
+
+ if (session->is_closing()) {
+ // mark con disposable. if there is a fault, we will get a
+ // reset and clean it up. if the client hasn't received the
+ // CLOSE message yet, they will reconnect and get an
+ // ms_handle_remote_reset() and realize they had in fact closed.
+ // do this *before* sending the message to avoid a possible
+ // race.
+ if (session->get_connection()) {
+ // Conditional because terminate_sessions will indiscrimately
+ // put sessions in CLOSING whether they ever had a conn or not.
+ session->get_connection()->mark_disposable();
+ }
+
+ // reset session
+ mds->send_message_client(make_message<MClientSession>(CEPH_SESSION_CLOSE), session);
+ mds->sessionmap.set_state(session, Session::STATE_CLOSED);
+ session->clear();
+ metrics_handler->remove_session(session);
+ mds->sessionmap.remove_session(session);
+ } else if (session->is_killing()) {
+ // destroy session, close connection
+ if (session->get_connection()) {
+ session->get_connection()->mark_down();
+ mds->sessionmap.set_state(session, Session::STATE_CLOSED);
+ session->set_connection(nullptr);
+ }
+ metrics_handler->remove_session(session);
+ mds->sessionmap.remove_session(session);
+ } else {
+ ceph_abort();
+ }
+ } else {
+ ceph_abort();
+ }
+}
+
+/**
+ * Inject sessions from some source other than actual connections.
+ *
+ * For example:
+ * - sessions inferred from journal replay
+ * - sessions learned from other MDSs during rejoin
+ * - sessions learned from other MDSs during dir/caps migration
+ * - sessions learned from other MDSs during a cross-MDS rename
+ */
+version_t Server::prepare_force_open_sessions(map<client_t,entity_inst_t>& cm,
+ map<client_t,client_metadata_t>& cmm,
+ map<client_t, pair<Session*,uint64_t> >& smap)
+{
+ version_t pv = mds->sessionmap.get_projected();
+
+ dout(10) << "prepare_force_open_sessions " << pv
+ << " on " << cm.size() << " clients"
+ << dendl;
+
+ mds->objecter->with_osdmap(
+ [this, &cm, &cmm](const OSDMap &osd_map) {
+ for (auto p = cm.begin(); p != cm.end(); ) {
+ if (osd_map.is_blocklisted(p->second.addr)) {
+ dout(10) << " ignoring blocklisted client." << p->first
+ << " (" << p->second.addr << ")" << dendl;
+ cmm.erase(p->first);
+ cm.erase(p++);
+ } else {
+ ++p;
+ }
+ }
+ });
+
+ for (map<client_t,entity_inst_t>::iterator p = cm.begin(); p != cm.end(); ++p) {
+ Session *session = mds->sessionmap.get_or_add_session(p->second);
+ pv = mds->sessionmap.mark_projected(session);
+ uint64_t sseq;
+ if (session->is_closed() ||
+ session->is_closing() ||
+ session->is_killing()) {
+ sseq = mds->sessionmap.set_state(session, Session::STATE_OPENING);
+ auto q = cmm.find(p->first);
+ if (q != cmm.end())
+ session->info.client_metadata.merge(q->second);
+ } else {
+ ceph_assert(session->is_open() ||
+ session->is_opening() ||
+ session->is_stale());
+ sseq = 0;
+ }
+ smap[p->first] = make_pair(session, sseq);
+ session->inc_importing();
+ }
+ return pv;
+}
+
+void Server::finish_force_open_sessions(const map<client_t,pair<Session*,uint64_t> >& smap,
+ bool dec_import)
+{
+ /*
+ * FIXME: need to carefully consider the race conditions between a
+ * client trying to close a session and an MDS doing an import
+ * trying to force open a session...
+ */
+ dout(10) << "finish_force_open_sessions on " << smap.size() << " clients,"
+ << " initial v " << mds->sessionmap.get_version() << dendl;
+
+ for (auto &it : smap) {
+ Session *session = it.second.first;
+ uint64_t sseq = it.second.second;
+ if (sseq > 0) {
+ if (session->get_state_seq() != sseq) {
+ dout(10) << "force_open_sessions skipping changed " << session->info.inst << dendl;
+ } else {
+ dout(10) << "force_open_sessions opened " << session->info.inst << dendl;
+ mds->sessionmap.set_state(session, Session::STATE_OPEN);
+ mds->sessionmap.touch_session(session);
+ metrics_handler->add_session(session);
+
+ auto reply = make_message<MClientSession>(CEPH_SESSION_OPEN);
+ if (session->info.has_feature(CEPHFS_FEATURE_MIMIC)) {
+ reply->supported_features = supported_features;
+ reply->metric_spec = supported_metric_spec;
+ }
+ mds->send_message_client(reply, session);
+
+ if (mdcache->is_readonly())
+ mds->send_message_client(make_message<MClientSession>(CEPH_SESSION_FORCE_RO), session);
+ }
+ } else {
+ dout(10) << "force_open_sessions skipping already-open " << session->info.inst << dendl;
+ ceph_assert(session->is_open() || session->is_stale());
+ }
+
+ if (dec_import) {
+ session->dec_importing();
+ }
+
+ mds->sessionmap.mark_dirty(session);
+ }
+
+ dout(10) << __func__ << ": final v " << mds->sessionmap.get_version() << dendl;
+}
+
+class C_MDS_TerminatedSessions : public ServerContext {
+ void finish(int r) override {
+ server->terminating_sessions = false;
+ }
+ public:
+ explicit C_MDS_TerminatedSessions(Server *s) : ServerContext(s) {}
+};
+
+void Server::terminate_sessions()
+{
+ dout(5) << "terminating all sessions..." << dendl;
+
+ terminating_sessions = true;
+
+ // kill them off. clients will retry etc.
+ set<Session*> sessions;
+ mds->sessionmap.get_client_session_set(sessions);
+ for (set<Session*>::const_iterator p = sessions.begin();
+ p != sessions.end();
+ ++p) {
+ Session *session = *p;
+ if (session->is_closing() ||
+ session->is_killing() ||
+ session->is_closed())
+ continue;
+ journal_close_session(session, Session::STATE_CLOSING, NULL);
+ }
+
+ mdlog->wait_for_safe(new C_MDS_TerminatedSessions(this));
+}
+
+
+void Server::find_idle_sessions()
+{
+ auto now = clock::now();
+ auto last_cleared_laggy = mds->last_cleared_laggy();
+
+ dout(10) << "find_idle_sessions. last cleared laggy state " << last_cleared_laggy << "s ago" << dendl;
+
+ // timeout/stale
+ // (caps go stale, lease die)
+ double queue_max_age = mds->get_dispatch_queue_max_age(ceph_clock_now());
+ double cutoff = queue_max_age + mds->mdsmap->get_session_timeout();
+
+ // don't kick clients if we've been laggy
+ if (last_cleared_laggy < cutoff) {
+ dout(10) << " last cleared laggy " << last_cleared_laggy << "s ago (< cutoff " << cutoff
+ << "), not marking any client stale" << dendl;
+ return;
+ }
+
+ std::vector<Session*> to_evict;
+
+ bool defer_session_stale = g_conf().get_val<bool>("mds_defer_session_stale");
+ const auto sessions_p1 = mds->sessionmap.by_state.find(Session::STATE_OPEN);
+ if (sessions_p1 != mds->sessionmap.by_state.end() && !sessions_p1->second->empty()) {
+ std::vector<Session*> new_stale;
+
+ for (auto session : *(sessions_p1->second)) {
+ auto last_cap_renew_span = std::chrono::duration<double>(now - session->last_cap_renew).count();
+ if (last_cap_renew_span < cutoff) {
+ dout(20) << "laggiest active session is " << session->info.inst
+ << " and renewed caps recently (" << last_cap_renew_span << "s ago)" << dendl;
+ break;
+ }
+
+ if (session->last_seen > session->last_cap_renew) {
+ last_cap_renew_span = std::chrono::duration<double>(now - session->last_seen).count();
+ if (last_cap_renew_span < cutoff) {
+ dout(20) << "laggiest active session is " << session->info.inst
+ << " and renewed caps recently (" << last_cap_renew_span << "s ago)" << dendl;
+ continue;
+ }
+ }
+
+ if (last_cap_renew_span >= mds->mdsmap->get_session_autoclose()) {
+ dout(20) << "evicting session " << session->info.inst << " since autoclose "
+ "has arrived" << dendl;
+ // evict session without marking it stale
+ to_evict.push_back(session);
+ continue;
+ }
+
+ if (defer_session_stale &&
+ !session->is_any_flush_waiter() &&
+ !mds->locker->is_revoking_any_caps_from(session->get_client())) {
+ dout(20) << "deferring marking session " << session->info.inst << " stale "
+ "since it holds no caps" << dendl;
+ continue;
+ }
+
+ auto it = session->info.client_metadata.find("timeout");
+ if (it != session->info.client_metadata.end()) {
+ unsigned timeout = strtoul(it->second.c_str(), nullptr, 0);
+ if (timeout == 0) {
+ dout(10) << "skipping session " << session->info.inst
+ << ", infinite timeout specified" << dendl;
+ continue;
+ }
+ double cutoff = queue_max_age + timeout;
+ if (last_cap_renew_span < cutoff) {
+ dout(10) << "skipping session " << session->info.inst
+ << ", timeout (" << timeout << ") specified"
+ << " and renewed caps recently (" << last_cap_renew_span << "s ago)" << dendl;
+ continue;
+ }
+
+ // do not go through stale, evict it directly.
+ to_evict.push_back(session);
+ } else {
+ dout(10) << "new stale session " << session->info.inst
+ << " last renewed caps " << last_cap_renew_span << "s ago" << dendl;
+ new_stale.push_back(session);
+ }
+ }
+
+ for (auto session : new_stale) {
+ mds->sessionmap.set_state(session, Session::STATE_STALE);
+ if (mds->locker->revoke_stale_caps(session)) {
+ mds->locker->remove_stale_leases(session);
+ finish_flush_session(session, session->get_push_seq());
+ auto m = make_message<MClientSession>(CEPH_SESSION_STALE);
+ mds->send_message_client(m, session);
+ } else {
+ to_evict.push_back(session);
+ }
+ }
+ }
+
+ // autoclose
+ cutoff = queue_max_age + mds->mdsmap->get_session_autoclose();
+
+ // Collect a list of sessions exceeding the autoclose threshold
+ const auto sessions_p2 = mds->sessionmap.by_state.find(Session::STATE_STALE);
+ if (sessions_p2 != mds->sessionmap.by_state.end() && !sessions_p2->second->empty()) {
+ for (auto session : *(sessions_p2->second)) {
+ ceph_assert(session->is_stale());
+ auto last_cap_renew_span = std::chrono::duration<double>(now - session->last_cap_renew).count();
+ if (last_cap_renew_span < cutoff) {
+ dout(20) << "oldest stale session is " << session->info.inst
+ << " and recently renewed caps " << last_cap_renew_span << "s ago" << dendl;
+ break;
+ }
+ to_evict.push_back(session);
+ }
+ }
+
+ for (auto session: to_evict) {
+ if (session->is_importing()) {
+ dout(10) << "skipping session " << session->info.inst << ", it's being imported" << dendl;
+ continue;
+ }
+
+ auto last_cap_renew_span = std::chrono::duration<double>(now - session->last_cap_renew).count();
+ mds->clog->warn() << "evicting unresponsive client " << *session
+ << ", after " << last_cap_renew_span << " seconds";
+ dout(10) << "autoclosing stale session " << session->info.inst
+ << " last renewed caps " << last_cap_renew_span << "s ago" << dendl;
+
+ if (g_conf()->mds_session_blocklist_on_timeout) {
+ CachedStackStringStream css;
+ mds->evict_client(session->get_client().v, false, true, *css, nullptr);
+ } else {
+ kill_session(session, NULL);
+ }
+ }
+}
+
+void Server::evict_cap_revoke_non_responders() {
+ if (!cap_revoke_eviction_timeout) {
+ return;
+ }
+
+ auto&& to_evict = mds->locker->get_late_revoking_clients(cap_revoke_eviction_timeout);
+
+ for (auto const &client: to_evict) {
+ mds->clog->warn() << "client id " << client << " has not responded to"
+ << " cap revoke by MDS for over " << cap_revoke_eviction_timeout
+ << " seconds, evicting";
+ dout(1) << __func__ << ": evicting cap revoke non-responder client id "
+ << client << dendl;
+
+ CachedStackStringStream css;
+ bool evicted = mds->evict_client(client.v, false,
+ g_conf()->mds_session_blocklist_on_evict,
+ *css, nullptr);
+ if (evicted && logger) {
+ logger->inc(l_mdss_cap_revoke_eviction);
+ }
+ }
+}
+
+void Server::handle_conf_change(const std::set<std::string>& changed) {
+ if (changed.count("mds_forward_all_requests_to_auth")){
+ forward_all_requests_to_auth = g_conf().get_val<bool>("mds_forward_all_requests_to_auth");
+ }
+ if (changed.count("mds_cap_revoke_eviction_timeout")) {
+ cap_revoke_eviction_timeout = g_conf().get_val<double>("mds_cap_revoke_eviction_timeout");
+ dout(20) << __func__ << " cap revoke eviction timeout changed to "
+ << cap_revoke_eviction_timeout << dendl;
+ }
+ if (changed.count("mds_recall_max_decay_rate")) {
+ recall_throttle = DecayCounter(g_conf().get_val<double>("mds_recall_max_decay_rate"));
+ }
+ if (changed.count("mds_max_snaps_per_dir")) {
+ max_snaps_per_dir = g_conf().get_val<uint64_t>("mds_max_snaps_per_dir");
+ dout(20) << __func__ << " max snapshots per directory changed to "
+ << max_snaps_per_dir << dendl;
+ }
+ if (changed.count("mds_client_delegate_inos_pct")) {
+ delegate_inos_pct = g_conf().get_val<uint64_t>("mds_client_delegate_inos_pct");
+ }
+ if (changed.count("mds_max_caps_per_client")) {
+ max_caps_per_client = g_conf().get_val<uint64_t>("mds_max_caps_per_client");
+ }
+ if (changed.count("mds_session_cap_acquisition_throttle")) {
+ cap_acquisition_throttle = g_conf().get_val<uint64_t>("mds_session_cap_acquisition_throttle");
+ }
+ if (changed.count("mds_session_max_caps_throttle_ratio")) {
+ max_caps_throttle_ratio = g_conf().get_val<double>("mds_session_max_caps_throttle_ratio");
+ }
+ if (changed.count("mds_cap_acquisition_throttle_retry_request_timeout")) {
+ caps_throttle_retry_request_timeout = g_conf().get_val<double>("mds_cap_acquisition_throttle_retry_request_timeout");
+ }
+ if (changed.count("mds_alternate_name_max")) {
+ alternate_name_max = g_conf().get_val<Option::size_t>("mds_alternate_name_max");
+ }
+ if (changed.count("mds_fscrypt_last_block_max_size")) {
+ fscrypt_last_block_max_size = g_conf().get_val<Option::size_t>("mds_fscrypt_last_block_max_size");
+ }
+ if (changed.count("mds_dir_max_entries")) {
+ dir_max_entries = g_conf().get_val<uint64_t>("mds_dir_max_entries");
+ dout(20) << __func__ << " max entries per directory changed to "
+ << dir_max_entries << dendl;
+ }
+ if (changed.count("mds_bal_fragment_size_max")) {
+ bal_fragment_size_max = g_conf().get_val<int64_t>("mds_bal_fragment_size_max");
+ dout(20) << __func__ << " max fragment size changed to "
+ << bal_fragment_size_max << dendl;
+ }
+ if (changed.count("mds_inject_rename_corrupt_dentry_first")) {
+ inject_rename_corrupt_dentry_first = g_conf().get_val<double>("mds_inject_rename_corrupt_dentry_first");
+ }
+}
+
+/*
+ * XXX bump in the interface here, not using an MDSContext here
+ * because all the callers right now happen to use a SaferCond
+ */
+void Server::kill_session(Session *session, Context *on_safe)
+{
+ ceph_assert(ceph_mutex_is_locked_by_me(mds->mds_lock));
+
+ if ((session->is_opening() ||
+ session->is_open() ||
+ session->is_stale()) &&
+ !session->is_importing()) {
+ dout(10) << "kill_session " << session << dendl;
+ journal_close_session(session, Session::STATE_KILLING, on_safe);
+ } else {
+ dout(10) << "kill_session importing or already closing/killing " << session << dendl;
+ if (session->is_closing() ||
+ session->is_killing()) {
+ if (on_safe)
+ mdlog->wait_for_safe(new MDSInternalContextWrapper(mds, on_safe));
+ } else {
+ ceph_assert(session->is_closed() ||
+ session->is_importing());
+ if (on_safe)
+ on_safe->complete(0);
+ }
+ }
+}
+
+size_t Server::apply_blocklist()
+{
+ std::vector<Session*> victims;
+ const auto& sessions = mds->sessionmap.get_sessions();
+ mds->objecter->with_osdmap(
+ [&](const OSDMap& o) {
+ for (const auto& p : sessions) {
+ if (!p.first.is_client()) {
+ // Do not apply OSDMap blocklist to MDS daemons, we find out
+ // about their death via MDSMap.
+ continue;
+ }
+ if (o.is_blocklisted(p.second->info.inst.addr)) {
+ victims.push_back(p.second);
+ }
+ }
+ });
+
+ for (const auto& s : victims) {
+ kill_session(s, nullptr);
+ }
+
+ dout(10) << "apply_blocklist: killed " << victims.size() << dendl;
+
+ return victims.size();
+}
+
+void Server::journal_close_session(Session *session, int state, Context *on_safe)
+{
+ dout(10) << __func__ << " : "
+ << session->info.inst
+ << " pending_prealloc_inos " << session->pending_prealloc_inos
+ << " free_prealloc_inos " << session->free_prealloc_inos
+ << " delegated_inos " << session->delegated_inos << dendl;
+
+ uint64_t sseq = mds->sessionmap.set_state(session, state);
+ version_t pv = mds->sessionmap.mark_projected(session);
+ version_t piv = 0;
+
+ // release alloc and pending-alloc inos for this session
+ // and wipe out session state, in case the session close aborts for some reason
+ interval_set<inodeno_t> inos_to_free;
+ inos_to_free.insert(session->pending_prealloc_inos);
+ inos_to_free.insert(session->free_prealloc_inos);
+ if (inos_to_free.size()) {
+ mds->inotable->project_release_ids(inos_to_free);
+ piv = mds->inotable->get_projected_version();
+ } else
+ piv = 0;
+
+ auto le = new ESession(session->info.inst, false, pv, inos_to_free, piv, session->delegated_inos);
+ auto fin = new C_MDS_session_finish(this, session, sseq, false, pv, inos_to_free, piv,
+ session->delegated_inos, mdlog->get_current_segment(), on_safe);
+ mdlog->start_submit_entry(le, fin);
+ mdlog->flush();
+
+ // clean up requests, too
+ while(!session->requests.empty()) {
+ auto mdr = MDRequestRef(*session->requests.begin());
+ mdcache->request_kill(mdr);
+ }
+
+ finish_flush_session(session, session->get_push_seq());
+}
+
+void Server::reconnect_clients(MDSContext *reconnect_done_)
+{
+ reconnect_done = reconnect_done_;
+
+ auto now = clock::now();
+ set<Session*> sessions;
+ mds->sessionmap.get_client_session_set(sessions);
+ for (auto session : sessions) {
+ if (session->is_open()) {
+ client_reconnect_gather.insert(session->get_client());
+ session->set_reconnecting(true);
+ session->last_cap_renew = now;
+ }
+ }
+
+ if (client_reconnect_gather.empty()) {
+ dout(7) << "reconnect_clients -- no sessions, doing nothing." << dendl;
+ reconnect_gather_finish();
+ return;
+ }
+
+ // clients will get the mdsmap and discover we're reconnecting via the monitor.
+
+ reconnect_start = now;
+ dout(1) << "reconnect_clients -- " << client_reconnect_gather.size() << " sessions" << dendl;
+ mds->sessionmap.dump();
+}
+
+void Server::handle_client_reconnect(const cref_t<MClientReconnect> &m)
+{
+ dout(7) << "handle_client_reconnect " << m->get_source()
+ << (m->has_more() ? " (more)" : "") << dendl;
+ client_t from = m->get_source().num();
+ Session *session = mds->get_session(m);
+ if (!session) {
+ dout(0) << " ignoring sessionless msg " << *m << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_REJECT);
+ reply->metadata["error_string"] = "sessionless";
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+
+ if(mds->mdsmap->test_flag(CEPH_MDSMAP_REFUSE_CLIENT_SESSION)) {
+ mds->clog->warn() << "client could not reconnect as"
+ " file system flag refuse_client_session is set";
+ dout(0) << "client cannot reconnect when file system flag"
+ " refuse_client_session is set" << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_CLOSE);
+ reply->metadata["error_string"] = "client cannot reconnect when file system flag"
+ " refuse_client_session is set";
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+
+ if (!session->is_open()) {
+ dout(0) << " ignoring msg from not-open session" << *m << dendl;
+ auto reply = make_message<MClientSession>(CEPH_SESSION_CLOSE);
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+
+ bool reconnect_all_deny = g_conf().get_val<bool>("mds_deny_all_reconnect");
+
+ if (!mds->is_reconnect() && mds->get_want_state() == CEPH_MDS_STATE_RECONNECT) {
+ dout(10) << " we're almost in reconnect state (mdsmap delivery race?); waiting" << dendl;
+ mds->wait_for_reconnect(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+
+ auto delay = std::chrono::duration<double>(clock::now() - reconnect_start).count();
+ dout(10) << " reconnect_start " << reconnect_start << " delay " << delay << dendl;
+
+ bool deny = false;
+ if (reconnect_all_deny || !mds->is_reconnect() || mds->get_want_state() != CEPH_MDS_STATE_RECONNECT || reconnect_evicting) {
+ // XXX maybe in the future we can do better than this?
+ if (reconnect_all_deny) {
+ dout(1) << "mds_deny_all_reconnect was set to speed up reboot phase, ignoring reconnect, sending close" << dendl;
+ } else {
+ dout(1) << "no longer in reconnect state, ignoring reconnect, sending close" << dendl;
+ }
+ mds->clog->info() << "denied reconnect attempt (mds is "
+ << ceph_mds_state_name(mds->get_state())
+ << ") from " << m->get_source_inst()
+ << " after " << delay << " (allowed interval " << g_conf()->mds_reconnect_timeout << ")";
+ deny = true;
+ } else {
+ std::string error_str;
+ if (!session->is_open()) {
+ error_str = "session is closed";
+ } else if (mdcache->is_readonly()) {
+ error_str = "mds is readonly";
+ } else {
+ if (session->info.client_metadata.features.empty())
+ infer_supported_features(session, session->info.client_metadata);
+
+ feature_bitset_t missing_features = required_client_features;
+ missing_features -= session->info.client_metadata.features;
+ if (!missing_features.empty()) {
+ CachedStackStringStream css;
+ *css << "missing required features '" << missing_features << "'";
+ error_str = css->strv();
+ }
+ }
+
+ if (!error_str.empty()) {
+ deny = true;
+ dout(1) << " " << error_str << ", ignoring reconnect, sending close" << dendl;
+ mds->clog->info() << "denied reconnect attempt from "
+ << m->get_source_inst() << " (" << error_str << ")";
+ }
+ }
+
+ if (deny) {
+ auto r = make_message<MClientSession>(CEPH_SESSION_CLOSE);
+ mds->send_message_client(r, session);
+ if (session->is_open()) {
+ client_reconnect_denied.insert(session->get_client());
+ }
+ return;
+ }
+
+ if (!m->has_more()) {
+ metrics_handler->add_session(session);
+ // notify client of success with an OPEN
+ auto reply = make_message<MClientSession>(CEPH_SESSION_OPEN);
+ if (session->info.has_feature(CEPHFS_FEATURE_MIMIC)) {
+ reply->supported_features = supported_features;
+ reply->metric_spec = supported_metric_spec;
+ }
+ mds->send_message_client(reply, session);
+ mds->clog->debug() << "reconnect by " << session->info.inst << " after " << delay;
+ }
+
+ session->last_cap_renew = clock::now();
+
+ // snaprealms
+ for (const auto &r : m->realms) {
+ CInode *in = mdcache->get_inode(inodeno_t(r.realm.ino));
+ if (in && in->state_test(CInode::STATE_PURGING))
+ continue;
+ if (in) {
+ if (in->snaprealm) {
+ dout(15) << "open snaprealm (w inode) on " << *in << dendl;
+ } else {
+ // this can happen if we are non-auth or we rollback snaprealm
+ dout(15) << "open snaprealm (null snaprealm) on " << *in << dendl;
+ }
+ mdcache->add_reconnected_snaprealm(from, inodeno_t(r.realm.ino), snapid_t(r.realm.seq));
+ } else {
+ dout(15) << "open snaprealm (w/o inode) on " << inodeno_t(r.realm.ino)
+ << " seq " << r.realm.seq << dendl;
+ mdcache->add_reconnected_snaprealm(from, inodeno_t(r.realm.ino), snapid_t(r.realm.seq));
+ }
+ }
+
+ // caps
+ for (const auto &p : m->caps) {
+ // make sure our last_cap_id is MAX over all issued caps
+ if (p.second.capinfo.cap_id > mdcache->last_cap_id)
+ mdcache->last_cap_id = p.second.capinfo.cap_id;
+
+ CInode *in = mdcache->get_inode(p.first);
+ if (in && in->state_test(CInode::STATE_PURGING))
+ continue;
+ if (in && in->is_auth()) {
+ // we recovered it, and it's ours. take note.
+ dout(15) << "open cap realm " << inodeno_t(p.second.capinfo.snaprealm)
+ << " on " << *in << dendl;
+ in->reconnect_cap(from, p.second, session);
+ mdcache->add_reconnected_cap(from, p.first, p.second);
+ recover_filelocks(in, p.second.flockbl, m->get_orig_source().num());
+ continue;
+ }
+
+ if (in && !in->is_auth()) {
+ // not mine.
+ dout(10) << "non-auth " << *in << ", will pass off to authority" << dendl;
+ // add to cap export list.
+ mdcache->rejoin_export_caps(p.first, from, p.second,
+ in->authority().first, true);
+ } else {
+ // don't know if the inode is mine
+ dout(10) << "missing ino " << p.first << ", will load later" << dendl;
+ mdcache->rejoin_recovered_caps(p.first, from, p.second, MDS_RANK_NONE);
+ }
+ }
+
+ reconnect_last_seen = clock::now();
+
+ if (!m->has_more()) {
+ mdcache->rejoin_recovered_client(session->get_client(), session->info.inst);
+
+ // remove from gather set
+ client_reconnect_gather.erase(from);
+ session->set_reconnecting(false);
+ if (client_reconnect_gather.empty())
+ reconnect_gather_finish();
+ }
+}
+
+void Server::infer_supported_features(Session *session, client_metadata_t& client_metadata)
+{
+ int supported = -1;
+ auto it = client_metadata.find("ceph_version");
+ if (it != client_metadata.end()) {
+ // user space client
+ if (it->second.compare(0, 16, "ceph version 12.") == 0)
+ supported = CEPHFS_FEATURE_LUMINOUS;
+ else if (session->get_connection()->has_feature(CEPH_FEATURE_FS_CHANGE_ATTR))
+ supported = CEPHFS_FEATURE_KRAKEN;
+ } else {
+ it = client_metadata.find("kernel_version");
+ if (it != client_metadata.end()) {
+ // kernel client
+ if (session->get_connection()->has_feature(CEPH_FEATURE_NEW_OSDOP_ENCODING))
+ supported = CEPHFS_FEATURE_LUMINOUS;
+ }
+ }
+ if (supported == -1 &&
+ session->get_connection()->has_feature(CEPH_FEATURE_FS_FILE_LAYOUT_V2))
+ supported = CEPHFS_FEATURE_JEWEL;
+
+ if (supported >= 0) {
+ unsigned long value = (1UL << (supported + 1)) - 1;
+ client_metadata.features = feature_bitset_t(value);
+ dout(10) << __func__ << " got '" << client_metadata.features << "'" << dendl;
+ }
+}
+
+void Server::update_required_client_features()
+{
+ required_client_features = mds->mdsmap->get_required_client_features();
+ dout(7) << "required_client_features: " << required_client_features << dendl;
+
+ if (mds->get_state() >= MDSMap::STATE_RECONNECT) {
+ set<Session*> sessions;
+ mds->sessionmap.get_client_session_set(sessions);
+ for (auto session : sessions) {
+ feature_bitset_t missing_features = required_client_features;
+ missing_features -= session->info.client_metadata.features;
+ if (!missing_features.empty()) {
+ bool blocklisted = mds->objecter->with_osdmap(
+ [session](const OSDMap &osd_map) -> bool {
+ return osd_map.is_blocklisted(session->info.inst.addr);
+ });
+ if (blocklisted)
+ continue;
+
+ mds->clog->warn() << "evicting session " << *session << ", missing required features '"
+ << missing_features << "'";
+ CachedStackStringStream css;
+ mds->evict_client(session->get_client().v, false,
+ g_conf()->mds_session_blocklist_on_evict, *css);
+ }
+ }
+ }
+}
+
+void Server::reconnect_gather_finish()
+{
+ dout(7) << "reconnect_gather_finish. failed on " << failed_reconnects << " clients" << dendl;
+ ceph_assert(reconnect_done);
+
+ if (!mds->snapclient->is_synced()) {
+ // make sure snaptable cache is populated. snaprealms will be
+ // extensively used in rejoin stage.
+ dout(7) << " snaptable cache isn't synced, delaying state transition" << dendl;
+ mds->snapclient->wait_for_sync(reconnect_done);
+ } else {
+ reconnect_done->complete(0);
+ }
+ reconnect_done = NULL;
+}
+
+void Server::reconnect_tick()
+{
+ bool reject_all_reconnect = false;
+ if (reconnect_evicting) {
+ dout(7) << "reconnect_tick: waiting for evictions" << dendl;
+ return;
+ }
+
+ /*
+ * Set mds_deny_all_reconnect to reject all the reconnect req ,
+ * then load less meta information in rejoin phase. This will shorten reboot time.
+ * Moreover, loading less meta increases the chance standby with less memory can failover.
+
+ * Why not shorten reconnect period?
+ * Clients may send unsafe or retry requests, which haven't been
+ * completed before old mds stop, to new mds. These requests may
+ * need to be processed during new mds's clientreplay phase,
+ * see: #https://github.com/ceph/ceph/pull/29059.
+ */
+ bool reconnect_all_deny = g_conf().get_val<bool>("mds_deny_all_reconnect");
+ if (client_reconnect_gather.empty())
+ return;
+
+ if (reconnect_all_deny && (client_reconnect_gather == client_reconnect_denied))
+ reject_all_reconnect = true;
+
+ auto now = clock::now();
+ auto elapse1 = std::chrono::duration<double>(now - reconnect_start).count();
+ if (elapse1 < g_conf()->mds_reconnect_timeout && !reject_all_reconnect)
+ return;
+
+ vector<Session*> remaining_sessions;
+ remaining_sessions.reserve(client_reconnect_gather.size());
+ for (auto c : client_reconnect_gather) {
+ Session *session = mds->sessionmap.get_session(entity_name_t::CLIENT(c.v));
+ ceph_assert(session);
+ remaining_sessions.push_back(session);
+ // client re-sends cap flush messages before the reconnect message
+ if (session->last_seen > reconnect_last_seen)
+ reconnect_last_seen = session->last_seen;
+ }
+
+ auto elapse2 = std::chrono::duration<double>(now - reconnect_last_seen).count();
+ if (elapse2 < g_conf()->mds_reconnect_timeout / 2 && !reject_all_reconnect) {
+ dout(7) << "reconnect_tick: last seen " << elapse2
+ << " seconds ago, extending reconnect interval" << dendl;
+ return;
+ }
+
+ dout(7) << "reconnect timed out, " << remaining_sessions.size()
+ << " clients have not reconnected in time" << dendl;
+
+ // If we're doing blocklist evictions, use this to wait for them before
+ // proceeding to reconnect_gather_finish
+ MDSGatherBuilder gather(g_ceph_context);
+
+ for (auto session : remaining_sessions) {
+ // Keep sessions that have specified timeout. These sessions will prevent
+ // mds from going to active. MDS goes to active after they all have been
+ // killed or reclaimed.
+ if (session->info.client_metadata.find("timeout") !=
+ session->info.client_metadata.end()) {
+ dout(1) << "reconnect keeps " << session->info.inst
+ << ", need to be reclaimed" << dendl;
+ client_reclaim_gather.insert(session->get_client());
+ continue;
+ }
+
+ dout(1) << "reconnect gives up on " << session->info.inst << dendl;
+
+ mds->clog->warn() << "evicting unresponsive client " << *session
+ << ", after waiting " << elapse1
+ << " seconds during MDS startup";
+
+ // make _session_logged() purge orphan objects of lost async/unsafe requests
+ session->delegated_inos.swap(session->free_prealloc_inos);
+
+ if (g_conf()->mds_session_blocklist_on_timeout) {
+ CachedStackStringStream css;
+ mds->evict_client(session->get_client().v, false, true, *css,
+ gather.new_sub());
+ } else {
+ kill_session(session, NULL);
+ }
+
+ failed_reconnects++;
+ }
+ client_reconnect_gather.clear();
+ client_reconnect_denied.clear();
+
+ if (gather.has_subs()) {
+ dout(1) << "reconnect will complete once clients are evicted" << dendl;
+ gather.set_finisher(new MDSInternalContextWrapper(mds, new LambdaContext(
+ [this](int r){reconnect_gather_finish();})));
+ gather.activate();
+ reconnect_evicting = true;
+ } else {
+ reconnect_gather_finish();
+ }
+}
+
+void Server::recover_filelocks(CInode *in, bufferlist locks, int64_t client)
+{
+ if (!locks.length()) return;
+ int numlocks;
+ ceph_filelock lock;
+ auto p = locks.cbegin();
+ decode(numlocks, p);
+ for (int i = 0; i < numlocks; ++i) {
+ decode(lock, p);
+ lock.client = client;
+ in->get_fcntl_lock_state()->held_locks.insert(pair<uint64_t, ceph_filelock>(lock.start, lock));
+ ++in->get_fcntl_lock_state()->client_held_lock_counts[client];
+ }
+ decode(numlocks, p);
+ for (int i = 0; i < numlocks; ++i) {
+ decode(lock, p);
+ lock.client = client;
+ in->get_flock_lock_state()->held_locks.insert(pair<uint64_t, ceph_filelock> (lock.start, lock));
+ ++in->get_flock_lock_state()->client_held_lock_counts[client];
+ }
+}
+
+/**
+ * Call this when the MDCache is oversized, to send requests to the clients
+ * to trim some caps, and consequently unpin some inodes in the MDCache so
+ * that it can trim too.
+ */
+std::pair<bool, uint64_t> Server::recall_client_state(MDSGatherBuilder* gather, RecallFlags flags)
+{
+ const auto now = clock::now();
+ const bool steady = !!(flags&RecallFlags::STEADY);
+ const bool enforce_max = !!(flags&RecallFlags::ENFORCE_MAX);
+ const bool enforce_liveness = !!(flags&RecallFlags::ENFORCE_LIVENESS);
+ const bool trim = !!(flags&RecallFlags::TRIM);
+
+ const auto max_caps_per_client = g_conf().get_val<uint64_t>("mds_max_caps_per_client");
+ const auto min_caps_per_client = g_conf().get_val<uint64_t>("mds_min_caps_per_client");
+ const auto recall_global_max_decay_threshold = g_conf().get_val<Option::size_t>("mds_recall_global_max_decay_threshold");
+ const auto recall_max_caps = g_conf().get_val<Option::size_t>("mds_recall_max_caps");
+ const auto recall_max_decay_threshold = g_conf().get_val<Option::size_t>("mds_recall_max_decay_threshold");
+ const auto cache_liveness_magnitude = g_conf().get_val<Option::size_t>("mds_session_cache_liveness_magnitude");
+
+ dout(7) << __func__ << ":"
+ << " min=" << min_caps_per_client
+ << " max=" << max_caps_per_client
+ << " total=" << Capability::count()
+ << " flags=" << flags
+ << dendl;
+
+ /* trim caps of sessions with the most caps first */
+ std::multimap<uint64_t, Session*> caps_session;
+ auto f = [&caps_session, enforce_max, enforce_liveness, trim, max_caps_per_client, cache_liveness_magnitude](auto& s) {
+ auto num_caps = s->caps.size();
+ auto cache_liveness = s->get_session_cache_liveness();
+ if (trim || (enforce_max && num_caps > max_caps_per_client) || (enforce_liveness && cache_liveness < (num_caps>>cache_liveness_magnitude))) {
+ caps_session.emplace(std::piecewise_construct, std::forward_as_tuple(num_caps), std::forward_as_tuple(s));
+ }
+ };
+ mds->sessionmap.get_client_sessions(std::move(f));
+
+ std::pair<bool, uint64_t> result = {false, 0};
+ auto& [throttled, caps_recalled] = result;
+ last_recall_state = now;
+ for (const auto& [num_caps, session] : boost::adaptors::reverse(caps_session)) {
+ if (!session->is_open() ||
+ !session->get_connection() ||
+ !session->info.inst.name.is_client())
+ continue;
+
+ dout(10) << __func__ << ":"
+ << " session " << session->info.inst
+ << " caps " << num_caps
+ << ", leases " << session->leases.size()
+ << dendl;
+
+ uint64_t newlim;
+ if (num_caps < recall_max_caps || (num_caps-recall_max_caps) < min_caps_per_client) {
+ newlim = min_caps_per_client;
+ } else {
+ newlim = num_caps-recall_max_caps;
+ }
+ if (num_caps > newlim) {
+ /* now limit the number of caps we recall at a time to prevent overloading ourselves */
+ uint64_t recall = std::min<uint64_t>(recall_max_caps, num_caps-newlim);
+ newlim = num_caps-recall;
+ const uint64_t session_recall_throttle = session->get_recall_caps_throttle();
+ const uint64_t session_recall_throttle2o = session->get_recall_caps_throttle2o();
+ const uint64_t global_recall_throttle = recall_throttle.get();
+ if (session_recall_throttle+recall > recall_max_decay_threshold) {
+ dout(15) << " session recall threshold (" << recall_max_decay_threshold << ") hit at " << session_recall_throttle << "; skipping!" << dendl;
+ throttled = true;
+ continue;
+ } else if (session_recall_throttle2o+recall > recall_max_caps*2) {
+ dout(15) << " session recall 2nd-order threshold (" << 2*recall_max_caps << ") hit at " << session_recall_throttle2o << "; skipping!" << dendl;
+ throttled = true;
+ continue;
+ } else if (global_recall_throttle+recall > recall_global_max_decay_threshold) {
+ dout(15) << " global recall threshold (" << recall_global_max_decay_threshold << ") hit at " << global_recall_throttle << "; skipping!" << dendl;
+ throttled = true;
+ break;
+ }
+
+ // now check if we've recalled caps recently and the client is unlikely to satisfy a new recall
+ if (steady) {
+ const auto session_recall = session->get_recall_caps();
+ const auto session_release = session->get_release_caps();
+ if (2*session_release < session_recall && 2*session_recall > recall_max_decay_threshold) {
+ /* The session has been unable to keep up with the number of caps
+ * recalled (by half); additionally, to prevent marking sessions
+ * we've just begun to recall from, the session_recall counter
+ * (decayed count of caps recently recalled) is **greater** than the
+ * session threshold for the session's cap recall throttle.
+ */
+ dout(15) << " 2*session_release < session_recall"
+ " (2*" << session_release << " < " << session_recall << ") &&"
+ " 2*session_recall < recall_max_decay_threshold"
+ " (2*" << session_recall << " > " << recall_max_decay_threshold << ")"
+ " Skipping because we are unlikely to get more released." << dendl;
+ continue;
+ } else if (recall < recall_max_caps && 2*recall < session_recall) {
+ /* The number of caps recalled is less than the number we *could*
+ * recall (so there isn't much left to recall?) and the number of
+ * caps is less than the current recall_caps counter (decayed count
+ * of caps recently recalled).
+ */
+ dout(15) << " 2*recall < session_recall "
+ " (2*" << recall << " < " << session_recall << ") &&"
+ " recall < recall_max_caps (" << recall << " < " << recall_max_caps << ");"
+ " Skipping because we are unlikely to get more released." << dendl;
+ continue;
+ }
+ }
+
+ dout(7) << " recalling " << recall << " caps; session_recall_throttle = " << session_recall_throttle << "; global_recall_throttle = " << global_recall_throttle << dendl;
+
+ auto m = make_message<MClientSession>(CEPH_SESSION_RECALL_STATE);
+ m->head.max_caps = newlim;
+ mds->send_message_client(m, session);
+ if (gather) {
+ flush_session(session, *gather);
+ }
+ caps_recalled += session->notify_recall_sent(newlim);
+ recall_throttle.hit(recall);
+ }
+ }
+
+ dout(7) << "recalled" << (throttled ? " (throttled)" : "") << " " << caps_recalled << " client caps." << dendl;
+
+ return result;
+}
+
+void Server::force_clients_readonly()
+{
+ dout(10) << "force_clients_readonly" << dendl;
+ set<Session*> sessions;
+ mds->sessionmap.get_client_session_set(sessions);
+ for (set<Session*>::const_iterator p = sessions.begin();
+ p != sessions.end();
+ ++p) {
+ Session *session = *p;
+ if (!session->info.inst.name.is_client() ||
+ !(session->is_open() || session->is_stale()))
+ continue;
+ mds->send_message_client(make_message<MClientSession>(CEPH_SESSION_FORCE_RO), session);
+ }
+}
+
+/*******
+ * some generic stuff for finishing off requests
+ */
+void Server::journal_and_reply(MDRequestRef& mdr, CInode *in, CDentry *dn, LogEvent *le, MDSLogContextBase *fin)
+{
+ dout(10) << "journal_and_reply tracei " << in << " tracedn " << dn << dendl;
+ ceph_assert(!mdr->has_completed);
+
+ // note trace items for eventual reply.
+ mdr->tracei = in;
+ if (in)
+ mdr->pin(in);
+
+ mdr->tracedn = dn;
+ if (dn)
+ mdr->pin(dn);
+
+ early_reply(mdr, in, dn);
+
+ mdr->committing = true;
+ submit_mdlog_entry(le, fin, mdr, __func__);
+
+ if (mdr->client_request && mdr->client_request->is_queued_for_replay()) {
+ if (mds->queue_one_replay()) {
+ dout(10) << " queued next replay op" << dendl;
+ } else {
+ dout(10) << " journaled last replay op" << dendl;
+ }
+ } else if (mdr->did_early_reply)
+ mds->locker->drop_rdlocks_for_early_reply(mdr.get());
+ else
+ mdlog->flush();
+}
+
+void Server::submit_mdlog_entry(LogEvent *le, MDSLogContextBase *fin, MDRequestRef& mdr,
+ std::string_view event)
+{
+ if (mdr) {
+ string event_str("submit entry: ");
+ event_str += event;
+ mdr->mark_event(event_str);
+ }
+ mdlog->submit_entry(le, fin);
+}
+
+/*
+ * send response built from mdr contents and error code; clean up mdr
+ */
+void Server::respond_to_request(MDRequestRef& mdr, int r)
+{
+ if (mdr->client_request) {
+ if (mdr->is_batch_head()) {
+ dout(20) << __func__ << " batch head " << *mdr << dendl;
+ mdr->release_batch_op()->respond(r);
+ } else {
+ reply_client_request(mdr, make_message<MClientReply>(*mdr->client_request, r));
+ }
+ } else if (mdr->internal_op > -1) {
+ dout(10) << "respond_to_request on internal request " << mdr << dendl;
+ if (!mdr->internal_op_finish)
+ ceph_abort_msg("trying to respond to internal op without finisher");
+ mdr->internal_op_finish->complete(r);
+ mdcache->request_finish(mdr);
+ }
+}
+
+// statistics mds req op number and latency
+void Server::perf_gather_op_latency(const cref_t<MClientRequest> &req, utime_t lat)
+{
+ int code = l_mdss_first;
+ switch(req->get_op()) {
+ case CEPH_MDS_OP_LOOKUPHASH:
+ code = l_mdss_req_lookuphash_latency;
+ break;
+ case CEPH_MDS_OP_LOOKUPINO:
+ code = l_mdss_req_lookupino_latency;
+ break;
+ case CEPH_MDS_OP_LOOKUPPARENT:
+ code = l_mdss_req_lookupparent_latency;
+ break;
+ case CEPH_MDS_OP_LOOKUPNAME:
+ code = l_mdss_req_lookupname_latency;
+ break;
+ case CEPH_MDS_OP_LOOKUP:
+ code = l_mdss_req_lookup_latency;
+ break;
+ case CEPH_MDS_OP_LOOKUPSNAP:
+ code = l_mdss_req_lookupsnap_latency;
+ break;
+ case CEPH_MDS_OP_GETATTR:
+ code = l_mdss_req_getattr_latency;
+ break;
+ case CEPH_MDS_OP_SETATTR:
+ code = l_mdss_req_setattr_latency;
+ break;
+ case CEPH_MDS_OP_SETLAYOUT:
+ code = l_mdss_req_setlayout_latency;
+ break;
+ case CEPH_MDS_OP_SETDIRLAYOUT:
+ code = l_mdss_req_setdirlayout_latency;
+ break;
+ case CEPH_MDS_OP_GETVXATTR:
+ code = l_mdss_req_getvxattr_latency;
+ break;
+ case CEPH_MDS_OP_SETXATTR:
+ code = l_mdss_req_setxattr_latency;
+ break;
+ case CEPH_MDS_OP_RMXATTR:
+ code = l_mdss_req_rmxattr_latency;
+ break;
+ case CEPH_MDS_OP_READDIR:
+ code = l_mdss_req_readdir_latency;
+ break;
+ case CEPH_MDS_OP_SETFILELOCK:
+ code = l_mdss_req_setfilelock_latency;
+ break;
+ case CEPH_MDS_OP_GETFILELOCK:
+ code = l_mdss_req_getfilelock_latency;
+ break;
+ case CEPH_MDS_OP_CREATE:
+ code = l_mdss_req_create_latency;
+ break;
+ case CEPH_MDS_OP_OPEN:
+ code = l_mdss_req_open_latency;
+ break;
+ case CEPH_MDS_OP_MKNOD:
+ code = l_mdss_req_mknod_latency;
+ break;
+ case CEPH_MDS_OP_LINK:
+ code = l_mdss_req_link_latency;
+ break;
+ case CEPH_MDS_OP_UNLINK:
+ code = l_mdss_req_unlink_latency;
+ break;
+ case CEPH_MDS_OP_RMDIR:
+ code = l_mdss_req_rmdir_latency;
+ break;
+ case CEPH_MDS_OP_RENAME:
+ code = l_mdss_req_rename_latency;
+ break;
+ case CEPH_MDS_OP_MKDIR:
+ code = l_mdss_req_mkdir_latency;
+ break;
+ case CEPH_MDS_OP_SYMLINK:
+ code = l_mdss_req_symlink_latency;
+ break;
+ case CEPH_MDS_OP_LSSNAP:
+ code = l_mdss_req_lssnap_latency;
+ break;
+ case CEPH_MDS_OP_MKSNAP:
+ code = l_mdss_req_mksnap_latency;
+ break;
+ case CEPH_MDS_OP_RMSNAP:
+ code = l_mdss_req_rmsnap_latency;
+ break;
+ case CEPH_MDS_OP_RENAMESNAP:
+ code = l_mdss_req_renamesnap_latency;
+ break;
+ case CEPH_MDS_OP_READDIR_SNAPDIFF:
+ code = l_mdss_req_snapdiff_latency;
+ break;
+ default:
+ dout(1) << ": unknown client op" << dendl;
+ return;
+ }
+ logger->tinc(code, lat);
+}
+
+void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
+{
+ if (!g_conf()->mds_early_reply)
+ return;
+
+ if (mdr->no_early_reply) {
+ dout(10) << "early_reply - flag no_early_reply is set, not allowed." << dendl;
+ return;
+ }
+
+ if (mdr->has_more() && mdr->more()->has_journaled_peers) {
+ dout(10) << "early_reply - there are journaled peers, not allowed." << dendl;
+ return;
+ }
+
+ if (mdr->alloc_ino) {
+ dout(10) << "early_reply - allocated ino, not allowed" << dendl;
+ return;
+ }
+
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ entity_inst_t client_inst = req->get_source_inst();
+ if (client_inst.name.is_mds())
+ return;
+
+ if (req->is_replay()) {
+ dout(10) << " no early reply on replay op" << dendl;
+ return;
+ }
+
+
+ auto reply = make_message<MClientReply>(*req, 0);
+ reply->set_unsafe();
+
+ // mark xlocks "done", indicating that we are exposing uncommitted changes.
+ //
+ //_rename_finish() does not send dentry link/unlink message to replicas.
+ // so do not set xlocks on dentries "done", the xlocks prevent dentries
+ // that have projected linkages from getting new replica.
+ mds->locker->set_xlocks_done(mdr.get(), req->get_op() == CEPH_MDS_OP_RENAME);
+
+ dout(10) << "early_reply " << reply->get_result()
+ << " (" << cpp_strerror(reply->get_result())
+ << ") " << *req << dendl;
+
+ if (tracei || tracedn) {
+ if (tracei)
+ mdr->cap_releases.erase(tracei->vino());
+ if (tracedn)
+ mdr->cap_releases.erase(tracedn->get_dir()->get_inode()->vino());
+
+ set_trace_dist(reply, tracei, tracedn, mdr);
+ }
+
+ reply->set_extra_bl(mdr->reply_extra_bl);
+ mds->send_message_client(reply, mdr->session);
+
+ mdr->did_early_reply = true;
+
+ mds->logger->inc(l_mds_reply);
+ utime_t lat = ceph_clock_now() - req->get_recv_stamp();
+ mds->logger->tinc(l_mds_reply_latency, lat);
+ if (lat >= g_conf()->mds_op_complaint_time) {
+ mds->logger->inc(l_mds_slow_reply);
+ }
+ if (client_inst.name.is_client()) {
+ mds->sessionmap.hit_session(mdr->session);
+ }
+ perf_gather_op_latency(req, lat);
+ dout(20) << "lat " << lat << dendl;
+
+ mdr->mark_event("early_replied");
+}
+
+/*
+ * send given reply
+ * include a trace to tracei
+ * Clean up mdr
+ */
+void Server::reply_client_request(MDRequestRef& mdr, const ref_t<MClientReply> &reply)
+{
+ ceph_assert(mdr.get());
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ dout(7) << "reply_client_request " << reply->get_result()
+ << " (" << cpp_strerror(reply->get_result())
+ << ") " << *req << dendl;
+
+ mdr->mark_event("replying");
+
+ Session *session = mdr->session;
+
+ // note successful request in session map?
+ //
+ // setfilelock requests are special, they only modify states in MDS memory.
+ // The states get lost when MDS fails. If Client re-send a completed
+ // setfilelock request, it means that client did not receive corresponding
+ // setfilelock reply. So MDS should re-execute the setfilelock request.
+ if (req->may_write() && req->get_op() != CEPH_MDS_OP_SETFILELOCK &&
+ reply->get_result() == 0 && session) {
+ inodeno_t created = mdr->alloc_ino ? mdr->alloc_ino : mdr->used_prealloc_ino;
+ session->add_completed_request(mdr->reqid.tid, created);
+ if (mdr->ls) {
+ mdr->ls->touched_sessions.insert(session->info.inst.name);
+ }
+ }
+
+ // give any preallocated inos to the session
+ apply_allocated_inos(mdr, session);
+
+ // get tracei/tracedn from mdr?
+ CInode *tracei = mdr->tracei;
+ CDentry *tracedn = mdr->tracedn;
+
+ bool is_replay = mdr->client_request->is_replay();
+ bool did_early_reply = mdr->did_early_reply;
+ entity_inst_t client_inst = req->get_source_inst();
+
+ if (!did_early_reply && !is_replay) {
+
+ mds->logger->inc(l_mds_reply);
+ utime_t lat = ceph_clock_now() - mdr->client_request->get_recv_stamp();
+ mds->logger->tinc(l_mds_reply_latency, lat);
+ if (lat >= g_conf()->mds_op_complaint_time) {
+ mds->logger->inc(l_mds_slow_reply);
+ }
+ if (session && client_inst.name.is_client()) {
+ mds->sessionmap.hit_session(session);
+ }
+ perf_gather_op_latency(req, lat);
+ dout(20) << "lat " << lat << dendl;
+
+ if (tracei)
+ mdr->cap_releases.erase(tracei->vino());
+ if (tracedn)
+ mdr->cap_releases.erase(tracedn->get_dir()->get_inode()->vino());
+ }
+
+ // drop non-rdlocks before replying, so that we can issue leases
+ mdcache->request_drop_non_rdlocks(mdr);
+
+ // reply at all?
+ if (session && !client_inst.name.is_mds()) {
+ // send reply.
+ if (!did_early_reply && // don't issue leases if we sent an earlier reply already
+ (tracei || tracedn)) {
+ if (is_replay) {
+ if (tracei)
+ mdcache->try_reconnect_cap(tracei, session);
+ } else {
+ // include metadata in reply
+ set_trace_dist(reply, tracei, tracedn, mdr);
+ }
+ }
+
+ // We can set the extra bl unconditionally: if it's already been sent in the
+ // early_reply, set_extra_bl will have claimed it and reply_extra_bl is empty
+ reply->set_extra_bl(mdr->reply_extra_bl);
+
+ reply->set_mdsmap_epoch(mds->mdsmap->get_epoch());
+ mds->send_message_client(reply, session);
+ }
+
+ if (req->is_queued_for_replay() &&
+ (mdr->has_completed || reply->get_result() < 0)) {
+ if (reply->get_result() < 0) {
+ int r = reply->get_result();
+ derr << "reply_client_request: failed to replay " << *req
+ << " error " << r << " (" << cpp_strerror(r) << ")" << dendl;
+ mds->clog->warn() << "failed to replay " << req->get_reqid() << " error " << r;
+ }
+ mds->queue_one_replay();
+ }
+
+ // clean up request
+ mdcache->request_finish(mdr);
+
+ // take a closer look at tracei, if it happens to be a remote link
+ if (tracei &&
+ tracedn &&
+ tracedn->get_projected_linkage()->is_remote()) {
+ mdcache->eval_remote(tracedn);
+ }
+}
+
+/*
+ * pass inode OR dentry (not both, or we may get confused)
+ *
+ * trace is in reverse order (i.e. root inode comes last)
+ */
+void Server::set_trace_dist(const ref_t<MClientReply> &reply,
+ CInode *in, CDentry *dn,
+ MDRequestRef& mdr)
+{
+ // skip doing this for debugging purposes?
+ if (g_conf()->mds_inject_traceless_reply_probability &&
+ mdr->ls && !mdr->o_trunc &&
+ (rand() % 10000 < g_conf()->mds_inject_traceless_reply_probability * 10000.0)) {
+ dout(5) << "deliberately skipping trace for " << *reply << dendl;
+ return;
+ }
+
+ // inode, dentry, dir, ..., inode
+ bufferlist bl;
+ mds_rank_t whoami = mds->get_nodeid();
+ Session *session = mdr->session;
+ snapid_t snapid = mdr->snapid;
+ utime_t now = ceph_clock_now();
+
+ dout(20) << "set_trace_dist snapid " << snapid << dendl;
+
+ // realm
+ if (snapid == CEPH_NOSNAP) {
+ SnapRealm *realm;
+ if (in)
+ realm = in->find_snaprealm();
+ else
+ realm = dn->get_dir()->get_inode()->find_snaprealm();
+ reply->snapbl = get_snap_trace(session, realm);
+ dout(10) << "set_trace_dist snaprealm " << *realm << " len=" << reply->snapbl.length() << dendl;
+ }
+
+ // dir + dentry?
+ if (dn) {
+ reply->head.is_dentry = 1;
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+
+ diri->encode_inodestat(bl, session, NULL, snapid);
+ dout(20) << "set_trace_dist added diri " << *diri << dendl;
+
+#ifdef MDS_VERIFY_FRAGSTAT
+ if (dir->is_complete())
+ dir->verify_fragstat();
+#endif
+ DirStat ds;
+ ds.frag = dir->get_frag();
+ ds.auth = dir->get_dir_auth().first;
+ if (dir->is_auth() && !forward_all_requests_to_auth)
+ dir->get_dist_spec(ds.dist, whoami);
+
+ dir->encode_dirstat(bl, session->info, ds);
+ dout(20) << "set_trace_dist added dir " << *dir << dendl;
+
+ encode(dn->get_name(), bl);
+ mds->locker->issue_client_lease(dn, in, mdr, now, bl);
+ } else
+ reply->head.is_dentry = 0;
+
+ // inode
+ if (in) {
+ in->encode_inodestat(bl, session, NULL, snapid, 0, mdr->getattr_caps);
+ dout(20) << "set_trace_dist added snap " << snapid << " in " << *in
+ << dendl;
+ reply->head.is_target = 1;
+ } else
+ reply->head.is_target = 0;
+
+ reply->set_trace(bl);
+}
+
+void Server::handle_client_request(const cref_t<MClientRequest> &req)
+{
+ dout(4) << "handle_client_request " << *req << dendl;
+
+ if (mds->logger)
+ mds->logger->inc(l_mds_request);
+ if (logger)
+ logger->inc(l_mdss_handle_client_request);
+
+ if (!mdcache->is_open()) {
+ dout(5) << "waiting for root" << dendl;
+ mdcache->wait_for_open(new C_MDS_RetryMessage(mds, req));
+ return;
+ }
+
+ bool sessionclosed_isok = replay_unsafe_with_closed_session;
+ // active session?
+ Session *session = 0;
+ if (req->is_a_client()) {
+ session = mds->get_session(req);
+ if (!session) {
+ dout(5) << "no session for " << req->get_source() << ", dropping" << dendl;
+ } else if ((session->is_closed() && (!mds->is_clientreplay() || !sessionclosed_isok)) ||
+ session->is_closing() ||
+ session->is_killing()) {
+ dout(5) << "session closed|closing|killing, dropping" << dendl;
+ session = NULL;
+ }
+ if (!session) {
+ if (req->is_queued_for_replay())
+ mds->queue_one_replay();
+ return;
+ }
+ }
+
+ // old mdsmap?
+ if (req->get_mdsmap_epoch() < mds->mdsmap->get_epoch()) {
+ // send it? hrm, this isn't ideal; they may get a lot of copies if
+ // they have a high request rate.
+ }
+
+ // completed request?
+ bool has_completed = false;
+ if (req->is_replay() || req->get_retry_attempt()) {
+ ceph_assert(session);
+ inodeno_t created;
+ if (session->have_completed_request(req->get_reqid().tid, &created)) {
+ has_completed = true;
+ if (!session->is_open())
+ return;
+ // Don't send traceless reply if the completed request has created
+ // new inode. Treat the request as lookup request instead.
+ if (req->is_replay() ||
+ ((created == inodeno_t() || !mds->is_clientreplay()) &&
+ req->get_op() != CEPH_MDS_OP_OPEN &&
+ req->get_op() != CEPH_MDS_OP_CREATE)) {
+ dout(5) << "already completed " << req->get_reqid() << dendl;
+ auto reply = make_message<MClientReply>(*req, 0);
+ if (created != inodeno_t()) {
+ bufferlist extra;
+ encode(created, extra);
+ reply->set_extra_bl(extra);
+ }
+ mds->send_message_client(reply, session);
+
+ if (req->is_queued_for_replay())
+ mds->queue_one_replay();
+
+ return;
+ }
+ if (req->get_op() != CEPH_MDS_OP_OPEN &&
+ req->get_op() != CEPH_MDS_OP_CREATE) {
+ dout(10) << " completed request which created new inode " << created
+ << ", convert it to lookup request" << dendl;
+ req->head.op = req->get_dentry_wanted() ? CEPH_MDS_OP_LOOKUP : CEPH_MDS_OP_GETATTR;
+ req->head.args.getattr.mask = CEPH_STAT_CAP_INODE_ALL;
+ }
+ }
+ }
+
+ // trim completed_request list
+ if (req->get_oldest_client_tid() > 0) {
+ dout(15) << " oldest_client_tid=" << req->get_oldest_client_tid() << dendl;
+ ceph_assert(session);
+ if (session->trim_completed_requests(req->get_oldest_client_tid())) {
+ // Sessions 'completed_requests' was dirtied, mark it to be
+ // potentially flushed at segment expiry.
+ mdlog->get_current_segment()->touched_sessions.insert(session->info.inst.name);
+
+ if (session->get_num_trim_requests_warnings() > 0 &&
+ session->get_num_completed_requests() * 2 < g_conf()->mds_max_completed_requests)
+ session->reset_num_trim_requests_warnings();
+ } else {
+ if (session->get_num_completed_requests() >=
+ (g_conf()->mds_max_completed_requests << session->get_num_trim_requests_warnings())) {
+ session->inc_num_trim_requests_warnings();
+ CachedStackStringStream css;
+ *css << "client." << session->get_client() << " does not advance its oldest_client_tid ("
+ << req->get_oldest_client_tid() << "), "
+ << session->get_num_completed_requests()
+ << " completed requests recorded in session\n";
+ mds->clog->warn() << css->strv();
+ dout(20) << __func__ << " " << css->strv() << dendl;
+ }
+ }
+ }
+
+ // register + dispatch
+ MDRequestRef mdr = mdcache->request_start(req);
+ if (!mdr.get())
+ return;
+
+ if (session) {
+ mdr->session = session;
+ session->requests.push_back(&mdr->item_session_request);
+ }
+
+ if (has_completed)
+ mdr->has_completed = true;
+
+ // process embedded cap releases?
+ // (only if NOT replay!)
+ if (!req->releases.empty() && req->is_a_client() && !req->is_replay()) {
+ client_t client = req->get_source().num();
+ for (const auto &r : req->releases) {
+ mds->locker->process_request_cap_release(mdr, client, r.item, r.dname);
+ }
+ req->releases.clear();
+ }
+
+ dispatch_client_request(mdr);
+ return;
+}
+
+void Server::handle_osd_map()
+{
+ /* Note that we check the OSDMAP_FULL flag directly rather than
+ * using osdmap_full_flag(), because we want to know "is the flag set"
+ * rather than "does the flag apply to us?" */
+ mds->objecter->with_osdmap([this](const OSDMap& o) {
+ auto pi = o.get_pg_pool(mds->get_metadata_pool());
+ is_full = pi && pi->has_flag(pg_pool_t::FLAG_FULL);
+ dout(7) << __func__ << ": full = " << is_full << " epoch = "
+ << o.get_epoch() << dendl;
+ });
+}
+
+void Server::dispatch_client_request(MDRequestRef& mdr)
+{
+ // we shouldn't be waiting on anyone.
+ ceph_assert(!mdr->has_more() || mdr->more()->waiting_on_peer.empty());
+
+ if (mdr->killed) {
+ dout(10) << "request " << *mdr << " was killed" << dendl;
+ //if the mdr is a "batch_op" and it has followers, pick a follower as
+ //the new "head of the batch ops" and go on processing the new one.
+ if (mdr->is_batch_head()) {
+ int mask = mdr->client_request->head.args.getattr.mask;
+ auto it = mdr->batch_op_map->find(mask);
+ auto new_batch_head = it->second->find_new_head();
+ if (!new_batch_head) {
+ mdr->batch_op_map->erase(it);
+ return;
+ }
+ mdr = std::move(new_batch_head);
+ } else {
+ return;
+ }
+ } else if (mdr->aborted) {
+ mdr->aborted = false;
+ mdcache->request_kill(mdr);
+ return;
+ }
+
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ if (logger) logger->inc(l_mdss_dispatch_client_request);
+
+ dout(7) << "dispatch_client_request " << *req << dendl;
+
+ if (req->may_write() && mdcache->is_readonly()) {
+ dout(10) << " read-only FS" << dendl;
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+ if (mdr->has_more() && mdr->more()->peer_error) {
+ dout(10) << " got error from peers" << dendl;
+ respond_to_request(mdr, mdr->more()->peer_error);
+ return;
+ }
+
+ if (is_full) {
+ CInode *cur = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!cur) {
+ // the request is already responded to
+ return;
+ }
+ if (req->get_op() == CEPH_MDS_OP_SETLAYOUT ||
+ req->get_op() == CEPH_MDS_OP_SETDIRLAYOUT ||
+ req->get_op() == CEPH_MDS_OP_SETLAYOUT ||
+ req->get_op() == CEPH_MDS_OP_RMXATTR ||
+ req->get_op() == CEPH_MDS_OP_SETXATTR ||
+ req->get_op() == CEPH_MDS_OP_CREATE ||
+ req->get_op() == CEPH_MDS_OP_SYMLINK ||
+ req->get_op() == CEPH_MDS_OP_MKSNAP ||
+ ((req->get_op() == CEPH_MDS_OP_LINK ||
+ req->get_op() == CEPH_MDS_OP_RENAME) &&
+ (!mdr->has_more() || mdr->more()->witnessed.empty())) // haven't started peer request
+ ) {
+
+ if (check_access(mdr, cur, MAY_FULL)) {
+ dout(20) << __func__ << ": full, has FULL caps, permitting op " << ceph_mds_op_name(req->get_op()) << dendl;
+ } else {
+ dout(20) << __func__ << ": full, responding CEPHFS_ENOSPC to op " << ceph_mds_op_name(req->get_op()) << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOSPC);
+ return;
+ }
+ } else {
+ dout(20) << __func__ << ": full, permitting op " << ceph_mds_op_name(req->get_op()) << dendl;
+ }
+ }
+
+ switch (req->get_op()) {
+ case CEPH_MDS_OP_LOOKUPHASH:
+ case CEPH_MDS_OP_LOOKUPINO:
+ handle_client_lookup_ino(mdr, false, false);
+ break;
+ case CEPH_MDS_OP_LOOKUPPARENT:
+ handle_client_lookup_ino(mdr, true, false);
+ break;
+ case CEPH_MDS_OP_LOOKUPNAME:
+ handle_client_lookup_ino(mdr, false, true);
+ break;
+
+ // inodes ops.
+ case CEPH_MDS_OP_LOOKUP:
+ handle_client_getattr(mdr, true);
+ break;
+
+ case CEPH_MDS_OP_LOOKUPSNAP:
+ // lookupsnap does not reference a CDentry; treat it as a getattr
+ case CEPH_MDS_OP_GETATTR:
+ handle_client_getattr(mdr, false);
+ break;
+ case CEPH_MDS_OP_GETVXATTR:
+ handle_client_getvxattr(mdr);
+ break;
+
+ case CEPH_MDS_OP_SETATTR:
+ handle_client_setattr(mdr);
+ break;
+ case CEPH_MDS_OP_SETLAYOUT:
+ handle_client_setlayout(mdr);
+ break;
+ case CEPH_MDS_OP_SETDIRLAYOUT:
+ handle_client_setdirlayout(mdr);
+ break;
+ case CEPH_MDS_OP_SETXATTR:
+ handle_client_setxattr(mdr);
+ break;
+ case CEPH_MDS_OP_RMXATTR:
+ handle_client_removexattr(mdr);
+ break;
+
+ case CEPH_MDS_OP_READDIR:
+ handle_client_readdir(mdr);
+ break;
+
+ case CEPH_MDS_OP_SETFILELOCK:
+ handle_client_file_setlock(mdr);
+ break;
+
+ case CEPH_MDS_OP_GETFILELOCK:
+ handle_client_file_readlock(mdr);
+ break;
+
+ // funky.
+ case CEPH_MDS_OP_CREATE:
+ if (mdr->has_completed)
+ handle_client_open(mdr); // already created.. just open
+ else
+ handle_client_openc(mdr);
+ break;
+
+ case CEPH_MDS_OP_OPEN:
+ handle_client_open(mdr);
+ break;
+
+ // namespace.
+ // no prior locks.
+ case CEPH_MDS_OP_MKNOD:
+ handle_client_mknod(mdr);
+ break;
+ case CEPH_MDS_OP_LINK:
+ handle_client_link(mdr);
+ break;
+ case CEPH_MDS_OP_UNLINK:
+ case CEPH_MDS_OP_RMDIR:
+ handle_client_unlink(mdr);
+ break;
+ case CEPH_MDS_OP_RENAME:
+ handle_client_rename(mdr);
+ break;
+ case CEPH_MDS_OP_MKDIR:
+ handle_client_mkdir(mdr);
+ break;
+ case CEPH_MDS_OP_SYMLINK:
+ handle_client_symlink(mdr);
+ break;
+
+
+ // snaps
+ case CEPH_MDS_OP_LSSNAP:
+ handle_client_lssnap(mdr);
+ break;
+ case CEPH_MDS_OP_MKSNAP:
+ handle_client_mksnap(mdr);
+ break;
+ case CEPH_MDS_OP_RMSNAP:
+ handle_client_rmsnap(mdr);
+ break;
+ case CEPH_MDS_OP_RENAMESNAP:
+ handle_client_renamesnap(mdr);
+ break;
+ case CEPH_MDS_OP_READDIR_SNAPDIFF:
+ handle_client_readdir_snapdiff(mdr);
+ break;
+
+ default:
+ dout(1) << " unknown client op " << req->get_op() << dendl;
+ respond_to_request(mdr, -CEPHFS_EOPNOTSUPP);
+ }
+}
+
+
+// ---------------------------------------
+// PEER REQUESTS
+
+void Server::handle_peer_request(const cref_t<MMDSPeerRequest> &m)
+{
+ dout(4) << "handle_peer_request " << m->get_reqid() << " from " << m->get_source() << dendl;
+ mds_rank_t from = mds_rank_t(m->get_source().num());
+
+ if (logger) logger->inc(l_mdss_handle_peer_request);
+
+ // reply?
+ if (m->is_reply())
+ return handle_peer_request_reply(m);
+
+ // the purpose of rename notify is enforcing causal message ordering. making sure
+ // bystanders have received all messages from rename srcdn's auth MDS.
+ if (m->get_op() == MMDSPeerRequest::OP_RENAMENOTIFY) {
+ auto reply = make_message<MMDSPeerRequest>(m->get_reqid(), m->get_attempt(), MMDSPeerRequest::OP_RENAMENOTIFYACK);
+ mds->send_message(reply, m->get_connection());
+ return;
+ }
+
+ CDentry *straydn = NULL;
+ if (m->straybl.length() > 0) {
+ mdcache->decode_replica_stray(straydn, nullptr, m->straybl, from);
+ ceph_assert(straydn);
+ m->straybl.clear();
+ }
+
+ if (!mds->is_clientreplay() && !mds->is_active() && !mds->is_stopping()) {
+ dout(3) << "not clientreplay|active yet, waiting" << dendl;
+ mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+
+ // am i a new peer?
+ MDRequestRef mdr;
+ if (mdcache->have_request(m->get_reqid())) {
+ // existing?
+ mdr = mdcache->request_get(m->get_reqid());
+
+ // is my request newer?
+ if (mdr->attempt > m->get_attempt()) {
+ dout(10) << "local request " << *mdr << " attempt " << mdr->attempt << " > " << m->get_attempt()
+ << ", dropping " << *m << dendl;
+ return;
+ }
+
+ if (mdr->attempt < m->get_attempt()) {
+ // mine is old, close it out
+ dout(10) << "local request " << *mdr << " attempt " << mdr->attempt << " < " << m->get_attempt()
+ << ", closing out" << dendl;
+ mdcache->request_finish(mdr);
+ mdr.reset();
+ } else if (mdr->peer_to_mds != from) {
+ dout(10) << "local request " << *mdr << " not peer to mds." << from << dendl;
+ return;
+ }
+
+ // may get these while mdr->peer_request is non-null
+ if (m->get_op() == MMDSPeerRequest::OP_DROPLOCKS) {
+ mds->locker->drop_locks(mdr.get());
+ return;
+ }
+ if (m->get_op() == MMDSPeerRequest::OP_FINISH) {
+ if (m->is_abort()) {
+ mdr->aborted = true;
+ if (mdr->peer_request) {
+ // only abort on-going xlock, wrlock and auth pin
+ ceph_assert(!mdr->peer_did_prepare());
+ } else {
+ mdcache->request_finish(mdr);
+ }
+ } else {
+ if (m->inode_export.length() > 0)
+ mdr->more()->inode_import = m->inode_export;
+ // finish off request.
+ mdcache->request_finish(mdr);
+ }
+ return;
+ }
+ }
+ if (!mdr.get()) {
+ // new?
+ if (m->get_op() == MMDSPeerRequest::OP_FINISH) {
+ dout(10) << "missing peer request for " << m->get_reqid()
+ << " OP_FINISH, must have lost race with a forward" << dendl;
+ return;
+ }
+ mdr = mdcache->request_start_peer(m->get_reqid(), m->get_attempt(), m);
+ mdr->set_op_stamp(m->op_stamp);
+ }
+ ceph_assert(mdr->peer_request == 0); // only one at a time, please!
+
+ if (straydn) {
+ mdr->pin(straydn);
+ mdr->straydn = straydn;
+ }
+
+ if (mds->is_clientreplay() && !mds->mdsmap->is_clientreplay(from) &&
+ mdr->locks.empty()) {
+ dout(3) << "not active yet, waiting" << dendl;
+ mds->wait_for_active(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+
+ mdr->reset_peer_request(m);
+
+ dispatch_peer_request(mdr);
+}
+
+void Server::handle_peer_request_reply(const cref_t<MMDSPeerRequest> &m)
+{
+ mds_rank_t from = mds_rank_t(m->get_source().num());
+
+ if (!mds->is_clientreplay() && !mds->is_active() && !mds->is_stopping()) {
+ metareqid_t r = m->get_reqid();
+ if (!mdcache->have_uncommitted_leader(r, from)) {
+ dout(10) << "handle_peer_request_reply ignoring peer reply from mds."
+ << from << " reqid " << r << dendl;
+ return;
+ }
+ dout(3) << "not clientreplay|active yet, waiting" << dendl;
+ mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
+ return;
+ }
+
+ if (m->get_op() == MMDSPeerRequest::OP_COMMITTED) {
+ metareqid_t r = m->get_reqid();
+ mdcache->committed_leader_peer(r, from);
+ return;
+ }
+
+ MDRequestRef mdr = mdcache->request_get(m->get_reqid());
+ if (m->get_attempt() != mdr->attempt) {
+ dout(10) << "handle_peer_request_reply " << *mdr << " ignoring reply from other attempt "
+ << m->get_attempt() << dendl;
+ return;
+ }
+
+ switch (m->get_op()) {
+ case MMDSPeerRequest::OP_XLOCKACK:
+ {
+ // identify lock, leader request
+ SimpleLock *lock = mds->locker->get_lock(m->get_lock_type(),
+ m->get_object_info());
+ mdr->more()->peers.insert(from);
+ lock->decode_locked_state(m->get_lock_data());
+ dout(10) << "got remote xlock on " << *lock << " on " << *lock->get_parent() << dendl;
+ mdr->emplace_lock(lock, MutationImpl::LockOp::XLOCK);
+ mdr->finish_locking(lock);
+ lock->get_xlock(mdr, mdr->get_client());
+
+ ceph_assert(mdr->more()->waiting_on_peer.count(from));
+ mdr->more()->waiting_on_peer.erase(from);
+ ceph_assert(mdr->more()->waiting_on_peer.empty());
+ mdcache->dispatch_request(mdr);
+ }
+ break;
+
+ case MMDSPeerRequest::OP_WRLOCKACK:
+ {
+ // identify lock, leader request
+ SimpleLock *lock = mds->locker->get_lock(m->get_lock_type(),
+ m->get_object_info());
+ mdr->more()->peers.insert(from);
+ dout(10) << "got remote wrlock on " << *lock << " on " << *lock->get_parent() << dendl;
+ auto it = mdr->emplace_lock(lock, MutationImpl::LockOp::REMOTE_WRLOCK, from);
+ ceph_assert(it->is_remote_wrlock());
+ ceph_assert(it->wrlock_target == from);
+
+ mdr->finish_locking(lock);
+
+ ceph_assert(mdr->more()->waiting_on_peer.count(from));
+ mdr->more()->waiting_on_peer.erase(from);
+ ceph_assert(mdr->more()->waiting_on_peer.empty());
+ mdcache->dispatch_request(mdr);
+ }
+ break;
+
+ case MMDSPeerRequest::OP_AUTHPINACK:
+ handle_peer_auth_pin_ack(mdr, m);
+ break;
+
+ case MMDSPeerRequest::OP_LINKPREPACK:
+ handle_peer_link_prep_ack(mdr, m);
+ break;
+
+ case MMDSPeerRequest::OP_RMDIRPREPACK:
+ handle_peer_rmdir_prep_ack(mdr, m);
+ break;
+
+ case MMDSPeerRequest::OP_RENAMEPREPACK:
+ handle_peer_rename_prep_ack(mdr, m);
+ break;
+
+ case MMDSPeerRequest::OP_RENAMENOTIFYACK:
+ handle_peer_rename_notify_ack(mdr, m);
+ break;
+
+ default:
+ ceph_abort_msg("unknown op " + to_string(m->get_op()) + " requested");
+ }
+}
+
+void Server::dispatch_peer_request(MDRequestRef& mdr)
+{
+ dout(7) << "dispatch_peer_request " << *mdr << " " << *mdr->peer_request << dendl;
+
+ if (mdr->aborted) {
+ dout(7) << " abort flag set, finishing" << dendl;
+ mdcache->request_finish(mdr);
+ return;
+ }
+
+ if (logger) logger->inc(l_mdss_dispatch_peer_request);
+
+ int op = mdr->peer_request->get_op();
+ switch (op) {
+ case MMDSPeerRequest::OP_XLOCK:
+ case MMDSPeerRequest::OP_WRLOCK:
+ {
+ // identify object
+ SimpleLock *lock = mds->locker->get_lock(mdr->peer_request->get_lock_type(),
+ mdr->peer_request->get_object_info());
+
+ if (!lock) {
+ dout(10) << "don't have object, dropping" << dendl;
+ ceph_abort_msg("don't have object"); // can this happen, if we auth pinned properly.
+ }
+ if (op == MMDSPeerRequest::OP_XLOCK && !lock->get_parent()->is_auth()) {
+ dout(10) << "not auth for remote xlock attempt, dropping on "
+ << *lock << " on " << *lock->get_parent() << dendl;
+ } else {
+ // use acquire_locks so that we get auth_pinning.
+ MutationImpl::LockOpVec lov;
+ for (const auto& p : mdr->locks) {
+ if (p.is_xlock())
+ lov.add_xlock(p.lock);
+ else if (p.is_wrlock())
+ lov.add_wrlock(p.lock);
+ }
+
+ int replycode = 0;
+ switch (op) {
+ case MMDSPeerRequest::OP_XLOCK:
+ lov.add_xlock(lock);
+ replycode = MMDSPeerRequest::OP_XLOCKACK;
+ break;
+ case MMDSPeerRequest::OP_WRLOCK:
+ lov.add_wrlock(lock);
+ replycode = MMDSPeerRequest::OP_WRLOCKACK;
+ break;
+ }
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ // ack
+ auto r = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, replycode);
+ r->set_lock_type(lock->get_type());
+ lock->get_parent()->set_object_info(r->get_object_info());
+ if (replycode == MMDSPeerRequest::OP_XLOCKACK)
+ lock->encode_locked_state(r->get_lock_data());
+ mds->send_message(r, mdr->peer_request->get_connection());
+ }
+
+ // done.
+ mdr->reset_peer_request();
+ }
+ break;
+
+ case MMDSPeerRequest::OP_UNXLOCK:
+ case MMDSPeerRequest::OP_UNWRLOCK:
+ {
+ SimpleLock *lock = mds->locker->get_lock(mdr->peer_request->get_lock_type(),
+ mdr->peer_request->get_object_info());
+ ceph_assert(lock);
+ auto it = mdr->locks.find(lock);
+ ceph_assert(it != mdr->locks.end());
+ bool need_issue = false;
+ switch (op) {
+ case MMDSPeerRequest::OP_UNXLOCK:
+ mds->locker->xlock_finish(it, mdr.get(), &need_issue);
+ break;
+ case MMDSPeerRequest::OP_UNWRLOCK:
+ mds->locker->wrlock_finish(it, mdr.get(), &need_issue);
+ break;
+ }
+ if (need_issue)
+ mds->locker->issue_caps(static_cast<CInode*>(lock->get_parent()));
+
+ // done. no ack necessary.
+ mdr->reset_peer_request();
+ }
+ break;
+
+ case MMDSPeerRequest::OP_AUTHPIN:
+ handle_peer_auth_pin(mdr);
+ break;
+
+ case MMDSPeerRequest::OP_LINKPREP:
+ case MMDSPeerRequest::OP_UNLINKPREP:
+ handle_peer_link_prep(mdr);
+ break;
+
+ case MMDSPeerRequest::OP_RMDIRPREP:
+ handle_peer_rmdir_prep(mdr);
+ break;
+
+ case MMDSPeerRequest::OP_RENAMEPREP:
+ handle_peer_rename_prep(mdr);
+ break;
+
+ default:
+ ceph_abort_msg("unknown op "+ to_string(op)+ " received");
+ }
+}
+
+void Server::handle_peer_auth_pin(MDRequestRef& mdr)
+{
+ dout(10) << "handle_peer_auth_pin " << *mdr << dendl;
+
+ // build list of objects
+ list<MDSCacheObject*> objects;
+ CInode *auth_pin_freeze = NULL;
+ bool nonblocking = mdr->peer_request->is_nonblocking();
+ bool fail = false, wouldblock = false, readonly = false;
+ ref_t<MMDSPeerRequest> reply;
+
+ if (mdcache->is_readonly()) {
+ dout(10) << " read-only FS" << dendl;
+ readonly = true;
+ fail = true;
+ }
+
+ if (!fail) {
+ for (const auto &oi : mdr->peer_request->get_authpins()) {
+ MDSCacheObject *object = mdcache->get_object(oi);
+ if (!object) {
+ dout(10) << " don't have " << oi << dendl;
+ fail = true;
+ break;
+ }
+
+ objects.push_back(object);
+ if (oi == mdr->peer_request->get_authpin_freeze())
+ auth_pin_freeze = static_cast<CInode*>(object);
+ }
+ }
+
+ // can we auth pin them?
+ if (!fail) {
+ for (const auto& obj : objects) {
+ if (!obj->is_auth()) {
+ dout(10) << " not auth for " << *obj << dendl;
+ fail = true;
+ break;
+ }
+ if (mdr->is_auth_pinned(obj))
+ continue;
+ if (!mdr->can_auth_pin(obj)) {
+ if (nonblocking) {
+ dout(10) << " can't auth_pin (freezing?) " << *obj << " nonblocking" << dendl;
+ fail = true;
+ wouldblock = true;
+ break;
+ }
+ // wait
+ dout(10) << " waiting for authpinnable on " << *obj << dendl;
+ obj->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ mdr->drop_local_auth_pins();
+
+ mds->locker->notify_freeze_waiter(obj);
+ goto blocked;
+ }
+ }
+ }
+
+ if (!fail) {
+ /* freeze authpin wrong inode */
+ if (mdr->has_more() && mdr->more()->is_freeze_authpin &&
+ mdr->more()->rename_inode != auth_pin_freeze)
+ mdr->unfreeze_auth_pin(true);
+
+ /* handle_peer_rename_prep() call freeze_inode() to wait for all other operations
+ * on the source inode to complete. This happens after all locks for the rename
+ * operation are acquired. But to acquire locks, we need auth pin locks' parent
+ * objects first. So there is an ABBA deadlock if someone auth pins the source inode
+ * after locks are acquired and before Server::handle_peer_rename_prep() is called.
+ * The solution is freeze the inode and prevent other MDRequests from getting new
+ * auth pins.
+ */
+ if (auth_pin_freeze) {
+ dout(10) << " freezing auth pin on " << *auth_pin_freeze << dendl;
+ if (!mdr->freeze_auth_pin(auth_pin_freeze)) {
+ auth_pin_freeze->add_waiter(CInode::WAIT_FROZEN, new C_MDS_RetryRequest(mdcache, mdr));
+ mds->mdlog->flush();
+ goto blocked;
+ }
+ }
+ }
+
+ reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_AUTHPINACK);
+
+ if (fail) {
+ mdr->drop_local_auth_pins(); // just in case
+ if (readonly)
+ reply->mark_error_rofs();
+ if (wouldblock)
+ reply->mark_error_wouldblock();
+ } else {
+ // auth pin!
+ for (const auto& obj : objects) {
+ dout(10) << "auth_pinning " << *obj << dendl;
+ mdr->auth_pin(obj);
+ }
+ // return list of my auth_pins (if any)
+ for (const auto &p : mdr->object_states) {
+ if (!p.second.auth_pinned)
+ continue;
+ MDSCacheObjectInfo info;
+ p.first->set_object_info(info);
+ reply->get_authpins().push_back(info);
+ if (p.first == (MDSCacheObject*)auth_pin_freeze)
+ auth_pin_freeze->set_object_info(reply->get_authpin_freeze());
+ }
+ }
+
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+
+ // clean up this request
+ mdr->reset_peer_request();
+ return;
+
+blocked:
+ if (mdr->peer_request->should_notify_blocking()) {
+ reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_AUTHPINACK);
+ reply->mark_req_blocked();
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ mdr->peer_request->clear_notify_blocking();
+ }
+ return;
+}
+
+void Server::handle_peer_auth_pin_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
+{
+ dout(10) << "handle_peer_auth_pin_ack on " << *mdr << " " << *ack << dendl;
+ mds_rank_t from = mds_rank_t(ack->get_source().num());
+
+ if (ack->is_req_blocked()) {
+ mdr->disable_lock_cache();
+ // peer auth pin is blocked, drop locks to avoid deadlock
+ mds->locker->drop_locks(mdr.get(), nullptr);
+ return;
+ }
+
+ // added auth pins?
+ set<MDSCacheObject*> pinned;
+ for (const auto &oi : ack->get_authpins()) {
+ MDSCacheObject *object = mdcache->get_object(oi);
+ ceph_assert(object); // we pinned it
+ dout(10) << " remote has pinned " << *object << dendl;
+ mdr->set_remote_auth_pinned(object, from);
+ if (oi == ack->get_authpin_freeze())
+ mdr->set_remote_frozen_auth_pin(static_cast<CInode *>(object));
+ pinned.insert(object);
+ }
+
+ // removed frozen auth pin ?
+ if (mdr->more()->is_remote_frozen_authpin &&
+ ack->get_authpin_freeze() == MDSCacheObjectInfo()) {
+ auto stat_p = mdr->find_object_state(mdr->more()->rename_inode);
+ ceph_assert(stat_p);
+ if (stat_p->remote_auth_pinned == from) {
+ mdr->more()->is_remote_frozen_authpin = false;
+ }
+ }
+
+ // removed auth pins?
+ for (auto& p : mdr->object_states) {
+ if (p.second.remote_auth_pinned == MDS_RANK_NONE)
+ continue;
+ MDSCacheObject* object = p.first;
+ if (p.second.remote_auth_pinned == from && pinned.count(object) == 0) {
+ dout(10) << " remote has unpinned " << *object << dendl;
+ mdr->_clear_remote_auth_pinned(p.second);
+ }
+ }
+
+ // note peer
+ mdr->more()->peers.insert(from);
+
+ // clear from waiting list
+ auto ret = mdr->more()->waiting_on_peer.erase(from);
+ ceph_assert(ret);
+
+ if (ack->is_error_rofs()) {
+ mdr->more()->peer_error = -CEPHFS_EROFS;
+ } else if (ack->is_error_wouldblock()) {
+ mdr->more()->peer_error = -CEPHFS_EWOULDBLOCK;
+ }
+
+ // go again?
+ if (mdr->more()->waiting_on_peer.empty())
+ mdcache->dispatch_request(mdr);
+ else
+ dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
+}
+
+
+// ---------------------------------------
+// HELPERS
+
+
+/**
+ * check whether we are permitted to complete a request
+ *
+ * Check whether we have permission to perform the operation specified
+ * by mask on the given inode, based on the capability in the mdr's
+ * session.
+ */
+bool Server::check_access(MDRequestRef& mdr, CInode *in, unsigned mask)
+{
+ if (mdr->session) {
+ int r = mdr->session->check_access(
+ in, mask,
+ mdr->client_request->get_caller_uid(),
+ mdr->client_request->get_caller_gid(),
+ &mdr->client_request->get_caller_gid_list(),
+ mdr->client_request->head.args.setattr.uid,
+ mdr->client_request->head.args.setattr.gid);
+ if (r < 0) {
+ respond_to_request(mdr, r);
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * check whether fragment has reached maximum size
+ *
+ */
+bool Server::check_fragment_space(MDRequestRef &mdr, CDir *dir)
+{
+ const auto size = dir->get_frag_size();
+ const auto max = bal_fragment_size_max;
+ if (size >= max) {
+ dout(10) << "fragment " << *dir << " size exceeds " << max << " (CEPHFS_ENOSPC)" << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOSPC);
+ return false;
+ } else {
+ dout(20) << "fragment " << *dir << " size " << size << " < " << max << dendl;
+ }
+
+ return true;
+}
+
+/**
+ * check whether entries in a dir reached maximum size
+ *
+ */
+bool Server::check_dir_max_entries(MDRequestRef &mdr, CDir *in)
+{
+ const uint64_t size = in->inode->get_projected_inode()->dirstat.nfiles +
+ in->inode->get_projected_inode()->dirstat.nsubdirs;
+ if (dir_max_entries && size >= dir_max_entries) {
+ dout(10) << "entries per dir " << *in << " size exceeds " << dir_max_entries << " (ENOSPC)" << dendl;
+ respond_to_request(mdr, -ENOSPC);
+ return false;
+ }
+ return true;
+}
+
+
+CDentry* Server::prepare_stray_dentry(MDRequestRef& mdr, CInode *in)
+{
+ string straydname;
+ in->name_stray_dentry(straydname);
+
+ CDentry *straydn = mdr->straydn;
+ if (straydn) {
+ ceph_assert(straydn->get_name() == straydname);
+ return straydn;
+ }
+ CDir *straydir = mdcache->get_stray_dir(in);
+
+ if (!mdr->client_request->is_replay() &&
+ !check_fragment_space(mdr, straydir))
+ return nullptr;
+
+ straydn = straydir->lookup(straydname);
+ if (!straydn) {
+ if (straydir->is_frozen_dir()) {
+ dout(10) << __func__ << ": " << *straydir << " is frozen, waiting" << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ straydir->add_waiter(CInode::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ return nullptr;
+ }
+ straydn = straydir->add_null_dentry(straydname);
+ straydn->mark_new();
+ } else {
+ ceph_assert(straydn->get_projected_linkage()->is_null());
+ }
+
+ straydn->state_set(CDentry::STATE_STRAY);
+ mdr->straydn = straydn;
+ mdr->pin(straydn);
+
+ return straydn;
+}
+
+/** prepare_new_inode
+ *
+ * create a new inode. set c/m/atime. hit dir pop.
+ */
+CInode* Server::prepare_new_inode(MDRequestRef& mdr, CDir *dir, inodeno_t useino, unsigned mode,
+ const file_layout_t *layout)
+{
+ CInode *in = new CInode(mdcache);
+ auto _inode = in->_get_inode();
+
+ // Server::prepare_force_open_sessions() can re-open session in closing
+ // state. In that corner case, session's prealloc_inos are being freed.
+ // To simplify the code, we disallow using/refilling session's prealloc_ino
+ // while session is opening.
+ bool allow_prealloc_inos = mdr->session->is_open();
+
+ inodeno_t _useino = useino;
+
+ // assign ino
+ do {
+ if (allow_prealloc_inos && (mdr->used_prealloc_ino = _inode->ino = mdr->session->take_ino(_useino))) {
+ if (mdcache->test_and_clear_taken_inos(_inode->ino)) {
+ _inode->ino = 0;
+ dout(10) << "prepare_new_inode used_prealloc " << mdr->used_prealloc_ino
+ << " (" << mdr->session->info.prealloc_inos.size() << " left)"
+ << " but has been taken, will try again!" << dendl;
+ } else {
+ mds->sessionmap.mark_projected(mdr->session);
+ dout(10) << "prepare_new_inode used_prealloc " << mdr->used_prealloc_ino
+ << " (" << mdr->session->info.prealloc_inos.size() << " left)"
+ << dendl;
+ }
+ } else {
+ mdr->alloc_ino =
+ _inode->ino = mds->inotable->project_alloc_id(_useino);
+ if (mdcache->test_and_clear_taken_inos(_inode->ino)) {
+ mds->inotable->apply_alloc_id(_inode->ino);
+ _inode->ino = 0;
+ dout(10) << "prepare_new_inode alloc " << mdr->alloc_ino
+ << " but has been taken, will try again!" << dendl;
+ } else {
+ dout(10) << "prepare_new_inode alloc " << mdr->alloc_ino << dendl;
+ }
+ }
+ _useino = 0;
+ } while (!_inode->ino);
+
+ if (useino && useino != _inode->ino) {
+ dout(0) << "WARNING: client specified " << useino << " and i allocated " << _inode->ino << dendl;
+ mds->clog->error() << mdr->client_request->get_source()
+ << " specified ino " << useino
+ << " but mds." << mds->get_nodeid() << " allocated " << _inode->ino;
+ //ceph_abort(); // just for now.
+ }
+
+ if (allow_prealloc_inos &&
+ mdr->session->get_num_projected_prealloc_inos() < g_conf()->mds_client_prealloc_inos / 2) {
+ int need = g_conf()->mds_client_prealloc_inos - mdr->session->get_num_projected_prealloc_inos();
+ mds->inotable->project_alloc_ids(mdr->prealloc_inos, need);
+ ceph_assert(mdr->prealloc_inos.size()); // or else fix projected increment semantics
+ mdr->session->pending_prealloc_inos.insert(mdr->prealloc_inos);
+ mds->sessionmap.mark_projected(mdr->session);
+ dout(10) << "prepare_new_inode prealloc " << mdr->prealloc_inos << dendl;
+ }
+
+ _inode->version = 1;
+ _inode->xattr_version = 1;
+ _inode->nlink = 1; // FIXME
+
+ _inode->mode = mode;
+
+ // FIPS zeroization audit 20191117: this memset is not security related.
+ memset(&_inode->dir_layout, 0, sizeof(_inode->dir_layout));
+ if (_inode->is_dir()) {
+ _inode->dir_layout.dl_dir_hash = g_conf()->mds_default_dir_hash;
+ } else if (layout) {
+ _inode->layout = *layout;
+ } else {
+ _inode->layout = mdcache->default_file_layout;
+ }
+
+ _inode->truncate_size = -1ull; // not truncated, yet!
+ _inode->truncate_seq = 1; /* starting with 1, 0 is kept for no-truncation logic */
+
+ CInode *diri = dir->get_inode();
+ auto pip = diri->get_projected_inode();
+
+ dout(10) << oct << " dir mode 0" << pip->mode << " new mode 0" << mode << dec << dendl;
+
+ if (pip->mode & S_ISGID) {
+ dout(10) << " dir is sticky" << dendl;
+ _inode->gid = pip->gid;
+ if (S_ISDIR(mode)) {
+ dout(10) << " new dir also sticky" << dendl;
+ _inode->mode |= S_ISGID;
+ }
+ } else {
+ _inode->gid = mdr->client_request->get_owner_gid();
+ ceph_assert(_inode->gid != (unsigned)-1);
+ }
+
+ _inode->uid = mdr->client_request->get_owner_uid();
+ ceph_assert(_inode->uid != (unsigned)-1);
+
+ _inode->btime = _inode->ctime = _inode->mtime = _inode->atime =
+ mdr->get_op_stamp();
+
+ _inode->change_attr = 0;
+
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ dout(10) << "copying fscrypt_auth len " << req->fscrypt_auth.size() << dendl;
+ _inode->fscrypt_auth = req->fscrypt_auth;
+ _inode->fscrypt_file = req->fscrypt_file;
+
+ if (req->get_data().length()) {
+ auto p = req->get_data().cbegin();
+
+ // xattrs on new inode?
+ auto _xattrs = CInode::allocate_xattr_map();
+ decode_noshare(*_xattrs, p);
+ dout(10) << "prepare_new_inode setting xattrs " << *_xattrs << dendl;
+ in->reset_xattrs(std::move(_xattrs));
+ }
+
+ if (!mds->mdsmap->get_inline_data_enabled() ||
+ !mdr->session->get_connection()->has_feature(CEPH_FEATURE_MDS_INLINE_DATA))
+ _inode->inline_data.version = CEPH_INLINE_NONE;
+
+ mdcache->add_inode(in); // add
+ dout(10) << "prepare_new_inode " << *in << dendl;
+ return in;
+}
+
+void Server::journal_allocated_inos(MDRequestRef& mdr, EMetaBlob *blob)
+{
+ dout(20) << "journal_allocated_inos sessionmapv " << mds->sessionmap.get_projected()
+ << " inotablev " << mds->inotable->get_projected_version()
+ << dendl;
+ blob->set_ino_alloc(mdr->alloc_ino,
+ mdr->used_prealloc_ino,
+ mdr->prealloc_inos,
+ mdr->client_request->get_source(),
+ mds->sessionmap.get_projected(),
+ mds->inotable->get_projected_version());
+}
+
+void Server::apply_allocated_inos(MDRequestRef& mdr, Session *session)
+{
+ dout(10) << "apply_allocated_inos " << mdr->alloc_ino
+ << " / " << mdr->prealloc_inos
+ << " / " << mdr->used_prealloc_ino << dendl;
+
+ if (mdr->alloc_ino) {
+ mds->inotable->apply_alloc_id(mdr->alloc_ino);
+ }
+ if (mdr->prealloc_inos.size()) {
+ ceph_assert(session);
+ session->pending_prealloc_inos.subtract(mdr->prealloc_inos);
+ session->free_prealloc_inos.insert(mdr->prealloc_inos);
+ session->info.prealloc_inos.insert(mdr->prealloc_inos);
+ mds->sessionmap.mark_dirty(session, !mdr->used_prealloc_ino);
+ mds->inotable->apply_alloc_ids(mdr->prealloc_inos);
+ }
+ if (mdr->used_prealloc_ino) {
+ ceph_assert(session);
+ session->info.prealloc_inos.erase(mdr->used_prealloc_ino);
+ mds->sessionmap.mark_dirty(session);
+ }
+}
+
+struct C_MDS_TryOpenInode : public ServerContext {
+ MDRequestRef mdr;
+ inodeno_t ino;
+ C_MDS_TryOpenInode(Server *s, MDRequestRef& r, inodeno_t i) :
+ ServerContext(s), mdr(r), ino(i) {}
+ void finish(int r) override {
+ server->_try_open_ino(mdr, r, ino);
+ }
+};
+
+void Server::_try_open_ino(MDRequestRef& mdr, int r, inodeno_t ino)
+{
+ dout(10) << "_try_open_ino " << mdr.get() << " ino " << ino << " r=" << r << dendl;
+
+ // `r` is a rank if >=0, else an error code
+ if (r >= 0) {
+ mds_rank_t dest_rank(r);
+ if (dest_rank == mds->get_nodeid())
+ dispatch_client_request(mdr);
+ else
+ mdcache->request_forward(mdr, dest_rank);
+ return;
+ }
+
+ // give up
+ if (r == -CEPHFS_ENOENT || r == -CEPHFS_ENODATA)
+ r = -CEPHFS_ESTALE;
+ respond_to_request(mdr, r);
+}
+
+class C_MDS_TryFindInode : public ServerContext {
+ MDRequestRef mdr;
+ MDCache *mdcache;
+ inodeno_t ino;
+public:
+ C_MDS_TryFindInode(Server *s, MDRequestRef& r, MDCache *m, inodeno_t i) :
+ ServerContext(s), mdr(r), mdcache(m), ino(i) {}
+ void finish(int r) override {
+ if (r == -CEPHFS_ESTALE) { // :( find_ino_peers failed
+ /*
+ * There has one case that when the MDS crashes and the
+ * openfiletable journal couldn't be flushed and then
+ * the replacing MDS is possibly won't load some already
+ * opened CInodes into the MDCache. And if the clients
+ * will retry some requests after reconnected, the MDS
+ * will return -ESTALE after failing to find the ino in
+ * all active peers.
+ *
+ * As a workaround users can run `ls -R ${mountpoint}`
+ * to list all the sub-files or sub-direcotries from the
+ * mountpoint.
+ *
+ * We need try to open the ino and try it again.
+ */
+ CInode *in = mdcache->get_inode(ino);
+ if (in && in->state_test(CInode::STATE_PURGING))
+ server->respond_to_request(mdr, r);
+ else
+ mdcache->open_ino(ino, (int64_t)-1, new C_MDS_TryOpenInode(server, mdr, ino));
+ } else {
+ server->dispatch_client_request(mdr);
+ }
+ }
+};
+
+/* If this returns null, the request has been handled
+ * as appropriate: forwarded on, or the client's been replied to */
+CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr,
+ bool want_auth,
+ bool no_want_auth)
+{
+ const filepath& refpath = mdr->get_filepath();
+ dout(10) << "rdlock_path_pin_ref " << *mdr << " " << refpath << dendl;
+
+ if (mdr->locking_state & MutationImpl::PATH_LOCKED)
+ return mdr->in[0];
+
+ // traverse
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, true);
+ int flags = 0;
+ if (refpath.is_last_snap()) {
+ if (!no_want_auth)
+ want_auth = true;
+ } else {
+ if (!no_want_auth && forward_all_requests_to_auth)
+ want_auth = true;
+ flags |= MDS_TRAVERSE_RDLOCK_PATH | MDS_TRAVERSE_RDLOCK_SNAP;
+ }
+ if (want_auth)
+ flags |= MDS_TRAVERSE_WANT_AUTH;
+ int r = mdcache->path_traverse(mdr, cf, refpath, flags, &mdr->dn[0], &mdr->in[0]);
+ if (r > 0)
+ return nullptr; // delayed
+ if (r < 0) { // error
+ if (r == -CEPHFS_ENOENT && !mdr->dn[0].empty()) {
+ if (mdr->client_request &&
+ mdr->client_request->get_dentry_wanted())
+ mdr->tracedn = mdr->dn[0].back();
+ respond_to_request(mdr, r);
+ } else if (r == -CEPHFS_ESTALE) {
+ dout(10) << "FAIL on CEPHFS_ESTALE but attempting recovery" << dendl;
+ inodeno_t ino = refpath.get_ino();
+ mdcache->find_ino_peers(ino, new C_MDS_TryFindInode(this, mdr, mdcache, ino));
+ } else {
+ dout(10) << "FAIL on error " << r << dendl;
+ respond_to_request(mdr, r);
+ }
+ return nullptr;
+ }
+ CInode *ref = mdr->in[0];
+ dout(10) << "ref is " << *ref << dendl;
+
+ if (want_auth) {
+ // auth_pin?
+ // do NOT proceed if freezing, as cap release may defer in that case, and
+ // we could deadlock when we try to lock @ref.
+ // if we're already auth_pinned, continue; the release has already been processed.
+ if (ref->is_frozen() || ref->is_frozen_auth_pin() ||
+ (ref->is_freezing() && !mdr->is_auth_pinned(ref))) {
+ dout(7) << "waiting for !frozen/authpinnable on " << *ref << dendl;
+ ref->add_waiter(CInode::WAIT_UNFREEZE, cf.build());
+ if (mdr->is_any_remote_auth_pin())
+ mds->locker->notify_freeze_waiter(ref);
+ return 0;
+ }
+ mdr->auth_pin(ref);
+ }
+
+ // set and pin ref
+ mdr->pin(ref);
+ return ref;
+}
+
+
+/** rdlock_path_xlock_dentry
+ * traverse path to the directory that could/would contain dentry.
+ * make sure i am auth for that dentry (or target inode if it exists and authexist),
+ * forward as necessary. create null dentry in place (or use existing if okexist).
+ * get rdlocks on traversed dentries, xlock on new dentry.
+ *
+ * set authexist true if caller requires the target inode to be auth when it exists.
+ * the tail dentry is not always auth any more if authexist because it is impossible
+ * to ensure tail dentry and target inode are both auth in one mds. the tail dentry
+ * will not be xlocked too if authexist and the target inode exists.
+ */
+CDentry* Server::rdlock_path_xlock_dentry(MDRequestRef& mdr,
+ bool create, bool okexist, bool authexist,
+ bool want_layout)
+{
+ const filepath& refpath = mdr->get_filepath();
+ dout(10) << "rdlock_path_xlock_dentry " << *mdr << " " << refpath << dendl;
+
+ if (mdr->locking_state & MutationImpl::PATH_LOCKED)
+ return mdr->dn[0].back();
+
+ // figure parent dir vs dname
+ if (refpath.depth() == 0) {
+ dout(7) << "invalid path (zero length)" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return nullptr;
+ }
+
+ if (refpath.is_last_snap()) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return nullptr;
+ }
+
+ if (refpath.is_last_dot_or_dotdot()) {
+ dout(7) << "invalid path (last dot or dot_dot)" << dendl;
+ if (create)
+ respond_to_request(mdr, -CEPHFS_EEXIST);
+ else
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return nullptr;
+ }
+
+ // traverse to parent dir
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, true);
+ int flags = MDS_TRAVERSE_RDLOCK_SNAP | MDS_TRAVERSE_RDLOCK_PATH |
+ MDS_TRAVERSE_WANT_DENTRY | MDS_TRAVERSE_XLOCK_DENTRY |
+ MDS_TRAVERSE_WANT_AUTH;
+ if (refpath.depth() == 1 && !mdr->lock_cache_disabled)
+ flags |= MDS_TRAVERSE_CHECK_LOCKCACHE;
+ if (create)
+ flags |= MDS_TRAVERSE_RDLOCK_AUTHLOCK;
+ if (authexist)
+ flags |= MDS_TRAVERSE_WANT_INODE;
+ if (want_layout)
+ flags |= MDS_TRAVERSE_WANT_DIRLAYOUT;
+ int r = mdcache->path_traverse(mdr, cf, refpath, flags, &mdr->dn[0]);
+ if (r > 0)
+ return nullptr; // delayed
+ if (r < 0) {
+ if (r == -CEPHFS_ESTALE) {
+ dout(10) << "FAIL on CEPHFS_ESTALE but attempting recovery" << dendl;
+ inodeno_t ino = refpath.get_ino();
+ mdcache->find_ino_peers(ino, new C_MDS_TryFindInode(this, mdr, mdcache, ino));
+ return nullptr;
+ }
+ respond_to_request(mdr, r);
+ return nullptr;
+ }
+
+ CDentry *dn = mdr->dn[0].back();
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+
+ if (!mdr->reqid.name.is_mds()) {
+ if (diri->is_system() && !diri->is_root() &&
+ (!diri->is_lost_and_found() ||
+ mdr->client_request->get_op() != CEPH_MDS_OP_UNLINK)) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return nullptr;
+ }
+ }
+
+ if (!diri->is_base() && diri->get_projected_parent_dir()->inode->is_stray()) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return nullptr;
+ }
+
+ CDentry::linkage_t *dnl = dn->get_projected_linkage();
+ if (dnl->is_null()) {
+ if (!create && okexist) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return nullptr;
+ }
+
+ snapid_t next_snap = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+ dn->first = std::max(dn->first, next_snap);
+ } else {
+ if (!okexist) {
+ respond_to_request(mdr, -CEPHFS_EEXIST);
+ return nullptr;
+ }
+ mdr->in[0] = dnl->get_inode();
+ }
+
+ return dn;
+}
+
+/** rdlock_two_paths_xlock_destdn
+ * traverse two paths and lock the two paths in proper order.
+ * The order of taking locks is:
+ * 1. Lock directory inodes or dentries according to which trees they
+ * are under. Lock objects under fs root before objects under mdsdir.
+ * 2. Lock directory inodes or dentries according to their depth, in
+ * ascending order.
+ * 3. Lock directory inodes or dentries according to inode numbers or
+ * dentries' parent inode numbers, in ascending order.
+ * 4. Lock dentries in the same directory in order of their keys.
+ * 5. Lock non-directory inodes according to inode numbers, in ascending
+ * order.
+ */
+std::pair<CDentry*, CDentry*>
+Server::rdlock_two_paths_xlock_destdn(MDRequestRef& mdr, bool xlock_srcdn)
+{
+
+ const filepath& refpath = mdr->get_filepath();
+ const filepath& refpath2 = mdr->get_filepath2();
+
+ dout(10) << "rdlock_two_paths_xlock_destdn " << *mdr << " " << refpath << " " << refpath2 << dendl;
+
+ if (mdr->locking_state & MutationImpl::PATH_LOCKED)
+ return std::make_pair(mdr->dn[0].back(), mdr->dn[1].back());
+
+ if (refpath.depth() != 1 || refpath2.depth() != 1) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return std::pair<CDentry*, CDentry*>(nullptr, nullptr);
+ }
+
+ if (refpath.is_last_snap() || refpath2.is_last_snap()) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return std::make_pair(nullptr, nullptr);
+ }
+
+ // traverse to parent dir
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, true);
+ int flags = MDS_TRAVERSE_RDLOCK_SNAP | MDS_TRAVERSE_WANT_DENTRY | MDS_TRAVERSE_WANT_AUTH;
+ int r = mdcache->path_traverse(mdr, cf, refpath, flags, &mdr->dn[0]);
+ if (r != 0) {
+ if (r == -CEPHFS_ESTALE) {
+ dout(10) << "CEPHFS_ESTALE on path, attempting recovery" << dendl;
+ inodeno_t ino = refpath.get_ino();
+ mdcache->find_ino_peers(ino, new C_MDS_TryFindInode(this, mdr, mdcache, ino));
+ } else if (r < 0) {
+ respond_to_request(mdr, r);
+ }
+ return std::make_pair(nullptr, nullptr);
+ }
+
+ flags = MDS_TRAVERSE_RDLOCK_SNAP2 | MDS_TRAVERSE_WANT_DENTRY | MDS_TRAVERSE_DISCOVER;
+ r = mdcache->path_traverse(mdr, cf, refpath2, flags, &mdr->dn[1]);
+ if (r != 0) {
+ if (r == -CEPHFS_ESTALE) {
+ dout(10) << "CEPHFS_ESTALE on path2, attempting recovery" << dendl;
+ inodeno_t ino = refpath2.get_ino();
+ mdcache->find_ino_peers(ino, new C_MDS_TryFindInode(this, mdr, mdcache, ino));
+ } else if (r < 0) {
+ respond_to_request(mdr, r);
+ }
+ return std::make_pair(nullptr, nullptr);
+ }
+
+ CDentry *srcdn = mdr->dn[1].back();
+ CDir *srcdir = srcdn->get_dir();
+ CDentry *destdn = mdr->dn[0].back();
+ CDir *destdir = destdn->get_dir();
+
+ if (!mdr->reqid.name.is_mds()) {
+ if ((srcdir->get_inode()->is_system() && !srcdir->get_inode()->is_root()) ||
+ (destdir->get_inode()->is_system() && !destdir->get_inode()->is_root())) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return std::make_pair(nullptr, nullptr);
+ }
+ }
+
+ if (!destdir->get_inode()->is_base() &&
+ destdir->get_inode()->get_projected_parent_dir()->inode->is_stray()) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return std::make_pair(nullptr, nullptr);
+ }
+
+ MutationImpl::LockOpVec lov;
+ if (srcdir->get_inode() == destdir->get_inode()) {
+ lov.add_wrlock(&destdir->inode->filelock);
+ lov.add_wrlock(&destdir->inode->nestlock);
+ if (xlock_srcdn && srcdir != destdir) {
+ mds_rank_t srcdir_auth = srcdir->authority().first;
+ if (srcdir_auth != mds->get_nodeid()) {
+ lov.add_remote_wrlock(&srcdir->inode->filelock, srcdir_auth);
+ lov.add_remote_wrlock(&srcdir->inode->nestlock, srcdir_auth);
+ }
+ }
+
+ if (srcdn->get_name() > destdn->get_name())
+ lov.add_xlock(&destdn->lock);
+
+ if (xlock_srcdn)
+ lov.add_xlock(&srcdn->lock);
+ else
+ lov.add_rdlock(&srcdn->lock);
+
+ if (srcdn->get_name() < destdn->get_name())
+ lov.add_xlock(&destdn->lock);
+ } else {
+ int cmp = mdr->compare_paths();
+ bool lock_destdir_first =
+ (cmp < 0 || (cmp == 0 && destdir->ino() < srcdir->ino()));
+
+ if (lock_destdir_first) {
+ lov.add_wrlock(&destdir->inode->filelock);
+ lov.add_wrlock(&destdir->inode->nestlock);
+ lov.add_xlock(&destdn->lock);
+ }
+
+ if (xlock_srcdn) {
+ mds_rank_t srcdir_auth = srcdir->authority().first;
+ if (srcdir_auth == mds->get_nodeid()) {
+ lov.add_wrlock(&srcdir->inode->filelock);
+ lov.add_wrlock(&srcdir->inode->nestlock);
+ } else {
+ lov.add_remote_wrlock(&srcdir->inode->filelock, srcdir_auth);
+ lov.add_remote_wrlock(&srcdir->inode->nestlock, srcdir_auth);
+ }
+ lov.add_xlock(&srcdn->lock);
+ } else {
+ lov.add_rdlock(&srcdn->lock);
+ }
+
+ if (!lock_destdir_first) {
+ lov.add_wrlock(&destdir->inode->filelock);
+ lov.add_wrlock(&destdir->inode->nestlock);
+ lov.add_xlock(&destdn->lock);
+ }
+ }
+
+ CInode *auth_pin_freeze = nullptr;
+ // XXX any better way to do this?
+ if (xlock_srcdn && !srcdn->is_auth()) {
+ CDentry::linkage_t *srcdnl = srcdn->get_projected_linkage();
+ auth_pin_freeze = srcdnl->is_primary() ? srcdnl->get_inode() : nullptr;
+ }
+ if (!mds->locker->acquire_locks(mdr, lov, auth_pin_freeze))
+ return std::make_pair(nullptr, nullptr);
+
+ if (srcdn->get_projected_linkage()->is_null()) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return std::make_pair(nullptr, nullptr);
+ }
+
+ if (destdn->get_projected_linkage()->is_null()) {
+ snapid_t next_snap = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+ destdn->first = std::max(destdn->first, next_snap);
+ }
+
+ mdr->locking_state |= MutationImpl::PATH_LOCKED;
+
+ return std::make_pair(destdn, srcdn);
+}
+
+/**
+ * try_open_auth_dirfrag -- open dirfrag, or forward to dirfrag auth
+ *
+ * @param diri base inode
+ * @param fg the exact frag we want
+ * @param mdr request
+ * @returns the pointer, or NULL if it had to be delayed (but mdr is taken care of)
+ */
+CDir* Server::try_open_auth_dirfrag(CInode *diri, frag_t fg, MDRequestRef& mdr)
+{
+ CDir *dir = diri->get_dirfrag(fg);
+
+ if (dir) {
+ // am i auth for the dirfrag?
+ if (!dir->is_auth()) {
+ mds_rank_t auth = dir->authority().first;
+ dout(7) << "try_open_auth_dirfrag: not auth for " << *dir
+ << ", fw to mds." << auth << dendl;
+ mdcache->request_forward(mdr, auth);
+ return nullptr;
+ }
+ } else {
+ // not open and inode not mine?
+ if (!diri->is_auth()) {
+ mds_rank_t inauth = diri->authority().first;
+ dout(7) << "try_open_auth_dirfrag: not open, not inode auth, fw to mds." << inauth << dendl;
+ mdcache->request_forward(mdr, inauth);
+ return nullptr;
+ }
+
+ // not open and inode frozen?
+ if (diri->is_frozen()) {
+ dout(10) << "try_open_auth_dirfrag: dir inode is frozen, waiting " << *diri << dendl;
+ ceph_assert(diri->get_parent_dir());
+ diri->add_waiter(CInode::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ return nullptr;
+ }
+
+ // invent?
+ dir = diri->get_or_open_dirfrag(mdcache, fg);
+ }
+
+ return dir;
+}
+
+
+// ===============================================================================
+// STAT
+
+void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ if (req->get_filepath().depth() == 0 && is_lookup) {
+ // refpath can't be empty for lookup but it can for
+ // getattr (we do getattr with empty refpath for mount of '/')
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ bool want_auth = false;
+ int mask = req->head.args.getattr.mask;
+ if (mask & CEPH_STAT_RSTAT)
+ want_auth = true; // set want_auth for CEPH_STAT_RSTAT mask
+
+ if (!mdr->is_batch_head() && mdr->can_batch()) {
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, false);
+ int r = mdcache->path_traverse(mdr, cf, mdr->get_filepath(),
+ (want_auth ? MDS_TRAVERSE_WANT_AUTH : 0),
+ &mdr->dn[0], &mdr->in[0]);
+ if (r > 0)
+ return; // delayed
+
+ if (r < 0) {
+ // fall-thru. let rdlock_path_pin_ref() check again.
+ } else if (is_lookup) {
+ CDentry* dn = mdr->dn[0].back();
+ mdr->pin(dn);
+ auto em = dn->batch_ops.emplace(std::piecewise_construct, std::forward_as_tuple(mask), std::forward_as_tuple());
+ if (em.second) {
+ em.first->second = std::make_unique<Batch_Getattr_Lookup>(this, mdr);
+ } else {
+ dout(20) << __func__ << ": LOOKUP op, wait for previous same getattr ops to respond. " << *mdr << dendl;
+ em.first->second->add_request(mdr);
+ mdr->mark_event("joining batch lookup");
+ return;
+ }
+ } else {
+ CInode *in = mdr->in[0];
+ mdr->pin(in);
+ auto em = in->batch_ops.emplace(std::piecewise_construct, std::forward_as_tuple(mask), std::forward_as_tuple());
+ if (em.second) {
+ em.first->second = std::make_unique<Batch_Getattr_Lookup>(this, mdr);
+ } else {
+ dout(20) << __func__ << ": GETATTR op, wait for previous same getattr ops to respond. " << *mdr << dendl;
+ em.first->second->add_request(mdr);
+ mdr->mark_event("joining batch getattr");
+ return;
+ }
+ }
+ }
+
+ CInode *ref = rdlock_path_pin_ref(mdr, want_auth, false);
+ if (!ref)
+ return;
+
+ /*
+ * if client currently holds the EXCL cap on a field, do not rdlock
+ * it; client's stat() will result in valid info if _either_ EXCL
+ * cap is held or MDS rdlocks and reads the value here.
+ *
+ * handling this case here is easier than weakening rdlock
+ * semantics... that would cause problems elsewhere.
+ */
+ client_t client = mdr->get_client();
+ int issued = 0;
+ Capability *cap = ref->get_client_cap(client);
+ if (cap && (mdr->snapid == CEPH_NOSNAP ||
+ mdr->snapid <= cap->client_follows))
+ issued = cap->issued();
+
+ // FIXME
+ MutationImpl::LockOpVec lov;
+ if ((mask & CEPH_CAP_LINK_SHARED) && !(issued & CEPH_CAP_LINK_EXCL))
+ lov.add_rdlock(&ref->linklock);
+ if ((mask & CEPH_CAP_AUTH_SHARED) && !(issued & CEPH_CAP_AUTH_EXCL))
+ lov.add_rdlock(&ref->authlock);
+ if ((mask & CEPH_CAP_XATTR_SHARED) && !(issued & CEPH_CAP_XATTR_EXCL))
+ lov.add_rdlock(&ref->xattrlock);
+ if ((mask & CEPH_CAP_FILE_SHARED) && !(issued & CEPH_CAP_FILE_EXCL)) {
+ // Don't wait on unstable filelock if client is allowed to read file size.
+ // This can reduce the response time of getattr in the case that multiple
+ // clients do stat(2) and there are writers.
+ // The downside of this optimization is that mds may not issue Fs caps along
+ // with getattr reply. Client may need to send more getattr requests.
+ if (mdr->is_rdlocked(&ref->filelock)) {
+ lov.add_rdlock(&ref->filelock);
+ } else if (ref->filelock.is_stable() ||
+ ref->filelock.get_num_wrlocks() > 0 ||
+ !ref->filelock.can_read(mdr->get_client())) {
+ /* Since we're taking advantage of an optimization here:
+ *
+ * We cannot suddenly, due to a changing condition, add this filelock as
+ * it can cause lock-order deadlocks. In this case, that condition is the
+ * lock state changes between request retries. If that happens, we need
+ * to check if we've acquired the other locks in this vector. If we have,
+ * then we need to drop those locks and retry.
+ */
+ if (mdr->is_rdlocked(&ref->linklock) ||
+ mdr->is_rdlocked(&ref->authlock) ||
+ mdr->is_rdlocked(&ref->xattrlock)) {
+ /* start over */
+ dout(20) << " dropping locks and restarting request because filelock state change" << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ mds->queue_waiter(new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ lov.add_rdlock(&ref->filelock);
+ mdr->locking_state &= ~MutationImpl::ALL_LOCKED;
+ }
+ }
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, ref, MAY_READ))
+ return;
+
+ utime_t now = ceph_clock_now();
+ mdr->set_mds_stamp(now);
+
+ // note which caps are requested, so we return at least a snapshot
+ // value for them. (currently this matters for xattrs and inline data)
+ mdr->getattr_caps = mask;
+
+ mds->balancer->hit_inode(ref, META_POP_IRD);
+
+ // reply
+ dout(10) << "reply to stat on " << *req << dendl;
+ mdr->tracei = ref;
+ if (is_lookup)
+ mdr->tracedn = mdr->dn[0].back();
+ respond_to_request(mdr, 0);
+}
+
+struct C_MDS_LookupIno2 : public ServerContext {
+ MDRequestRef mdr;
+ C_MDS_LookupIno2(Server *s, MDRequestRef& r) : ServerContext(s), mdr(r) {}
+ void finish(int r) override {
+ server->_lookup_ino_2(mdr, r);
+ }
+};
+
+/*
+ * filepath: ino
+ */
+void Server::handle_client_lookup_ino(MDRequestRef& mdr,
+ bool want_parent, bool want_dentry)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ if ((uint64_t)req->head.args.lookupino.snapid > 0)
+ return _lookup_snap_ino(mdr);
+
+ inodeno_t ino = req->get_filepath().get_ino();
+ auto _ino = ino.val;
+
+ /* It's been observed [1] that a client may lookup a private ~mdsdir inode.
+ * I do not have an explanation for how that happened organically but this
+ * check will ensure that the client can no longer do that.
+ *
+ * [1] https://tracker.ceph.com/issues/49922
+ */
+ if (MDS_IS_PRIVATE_INO(_ino)) {
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ return;
+ }
+
+ CInode *in = mdcache->get_inode(ino);
+ if (in && in->state_test(CInode::STATE_PURGING)) {
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ return;
+ }
+ if (!in) {
+ mdcache->open_ino(ino, (int64_t)-1, new C_MDS_LookupIno2(this, mdr), false);
+ return;
+ }
+
+ // check for nothing (not read or write); this still applies the
+ // path check.
+ if (!check_access(mdr, in, 0))
+ return;
+
+ CDentry *dn = in->get_projected_parent_dn();
+ CInode *diri = dn ? dn->get_dir()->inode : NULL;
+
+ MutationImpl::LockOpVec lov;
+ if (dn && (want_parent || want_dentry)) {
+ mdr->pin(dn);
+ lov.add_rdlock(&dn->lock);
+ }
+
+ unsigned mask = req->head.args.lookupino.mask;
+ if (mask) {
+ Capability *cap = in->get_client_cap(mdr->get_client());
+ int issued = 0;
+ if (cap && (mdr->snapid == CEPH_NOSNAP || mdr->snapid <= cap->client_follows))
+ issued = cap->issued();
+ // FIXME
+ // permission bits, ACL/security xattrs
+ if ((mask & CEPH_CAP_AUTH_SHARED) && (issued & CEPH_CAP_AUTH_EXCL) == 0)
+ lov.add_rdlock(&in->authlock);
+ if ((mask & CEPH_CAP_XATTR_SHARED) && (issued & CEPH_CAP_XATTR_EXCL) == 0)
+ lov.add_rdlock(&in->xattrlock);
+
+ mdr->getattr_caps = mask;
+ }
+
+ if (!lov.empty()) {
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (diri != NULL) {
+ // need read access to directory inode
+ if (!check_access(mdr, diri, MAY_READ))
+ return;
+ }
+ }
+
+ if (want_parent) {
+ if (in->is_base()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (!diri || diri->is_stray()) {
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ return;
+ }
+ dout(10) << "reply to lookup_parent " << *in << dendl;
+ mdr->tracei = diri;
+ respond_to_request(mdr, 0);
+ } else {
+ if (want_dentry) {
+ inodeno_t dirino = req->get_filepath2().get_ino();
+ if (!diri || (dirino != inodeno_t() && diri->ino() != dirino)) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return;
+ }
+ dout(10) << "reply to lookup_name " << *in << dendl;
+ } else
+ dout(10) << "reply to lookup_ino " << *in << dendl;
+
+ mdr->tracei = in;
+ if (want_dentry)
+ mdr->tracedn = dn;
+ respond_to_request(mdr, 0);
+ }
+}
+
+void Server::_lookup_snap_ino(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ vinodeno_t vino;
+ vino.ino = req->get_filepath().get_ino();
+ vino.snapid = (__u64)req->head.args.lookupino.snapid;
+ inodeno_t parent_ino = (__u64)req->head.args.lookupino.parent;
+ __u32 hash = req->head.args.lookupino.hash;
+
+ dout(7) << "lookup_snap_ino " << vino << " parent " << parent_ino << " hash " << hash << dendl;
+
+ CInode *in = mdcache->lookup_snap_inode(vino);
+ if (!in) {
+ in = mdcache->get_inode(vino.ino);
+ if (in) {
+ if (in->state_test(CInode::STATE_PURGING) ||
+ !in->has_snap_data(vino.snapid)) {
+ if (in->is_dir() || !parent_ino) {
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ return;
+ }
+ in = NULL;
+ }
+ }
+ }
+
+ if (in) {
+ dout(10) << "reply to lookup_snap_ino " << *in << dendl;
+ mdr->snapid = vino.snapid;
+ mdr->tracei = in;
+ respond_to_request(mdr, 0);
+ return;
+ }
+
+ CInode *diri = NULL;
+ if (parent_ino) {
+ diri = mdcache->get_inode(parent_ino);
+ if (!diri) {
+ mdcache->open_ino(parent_ino, mds->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr));
+ return;
+ }
+
+ if (!diri->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ MutationImpl::LockOpVec lov;
+ lov.add_rdlock(&diri->dirfragtreelock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ frag_t frag = diri->dirfragtree[hash];
+ CDir *dir = try_open_auth_dirfrag(diri, frag, mdr);
+ if (!dir)
+ return;
+
+ if (!dir->is_complete()) {
+ if (dir->is_frozen()) {
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ dir->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ dir->fetch(new C_MDS_RetryRequest(mdcache, mdr), true);
+ return;
+ }
+
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ } else {
+ mdcache->open_ino(vino.ino, mds->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr), false);
+ }
+}
+
+void Server::_lookup_ino_2(MDRequestRef& mdr, int r)
+{
+ inodeno_t ino = mdr->client_request->get_filepath().get_ino();
+ dout(10) << "_lookup_ino_2 " << mdr.get() << " ino " << ino << " r=" << r << dendl;
+
+ // `r` is a rank if >=0, else an error code
+ if (r >= 0) {
+ mds_rank_t dest_rank(r);
+ if (dest_rank == mds->get_nodeid())
+ dispatch_client_request(mdr);
+ else
+ mdcache->request_forward(mdr, dest_rank);
+ return;
+ }
+
+ // give up
+ if (r == -CEPHFS_ENOENT || r == -CEPHFS_ENODATA)
+ r = -CEPHFS_ESTALE;
+ respond_to_request(mdr, r);
+}
+
+
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_open(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ dout(7) << "open on " << req->get_filepath() << dendl;
+
+ int flags = req->head.args.open.flags;
+ int cmode = ceph_flags_to_mode(flags);
+ if (cmode < 0) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ bool need_auth = !file_mode_is_readonly(cmode) ||
+ (flags & (CEPH_O_TRUNC | CEPH_O_DIRECTORY));
+
+ if ((cmode & CEPH_FILE_MODE_WR) && mdcache->is_readonly()) {
+ dout(7) << "read-only FS" << dendl;
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+
+ CInode *cur = rdlock_path_pin_ref(mdr, need_auth);
+ if (!cur)
+ return;
+
+ if (cur->is_frozen() || cur->state_test(CInode::STATE_EXPORTINGCAPS)) {
+ ceph_assert(!need_auth);
+ mdr->locking_state &= ~(MutationImpl::PATH_LOCKED | MutationImpl::ALL_LOCKED);
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur)
+ return;
+ }
+
+ if (!cur->is_file()) {
+ // can only open non-regular inode with mode FILE_MODE_PIN, at least for now.
+ cmode = CEPH_FILE_MODE_PIN;
+ // the inode is symlink and client wants to follow it, ignore the O_TRUNC flag.
+ if (cur->is_symlink() && !(flags & CEPH_O_NOFOLLOW))
+ flags &= ~CEPH_O_TRUNC;
+ }
+
+ dout(10) << "open flags = " << flags
+ << ", filemode = " << cmode
+ << ", need_auth = " << need_auth
+ << dendl;
+
+ // regular file?
+ /*if (!cur->inode.is_file() && !cur->inode.is_dir()) {
+ dout(7) << "not a file or dir " << *cur << dendl;
+ respond_to_request(mdr, -CEPHFS_ENXIO); // FIXME what error do we want?
+ return;
+ }*/
+ if ((flags & CEPH_O_DIRECTORY) && !cur->is_dir() && !cur->is_symlink()) {
+ dout(7) << "specified O_DIRECTORY on non-directory " << *cur << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if ((flags & CEPH_O_TRUNC) && !cur->is_file()) {
+ dout(7) << "specified O_TRUNC on !(file|symlink) " << *cur << dendl;
+ // we should return -CEPHFS_EISDIR for directory, return -CEPHFS_EINVAL for other non-regular
+ respond_to_request(mdr, cur->is_dir() ? -CEPHFS_EISDIR : -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (cur->get_inode()->inline_data.version != CEPH_INLINE_NONE &&
+ !mdr->session->get_connection()->has_feature(CEPH_FEATURE_MDS_INLINE_DATA)) {
+ dout(7) << "old client cannot open inline data file " << *cur << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ // snapped data is read only
+ if (mdr->snapid != CEPH_NOSNAP &&
+ ((cmode & CEPH_FILE_MODE_WR) || req->may_write())) {
+ dout(7) << "snap " << mdr->snapid << " is read-only " << *cur << dendl;
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+
+ MutationImpl::LockOpVec lov;
+ lov.add_rdlock(&cur->snaplock);
+
+ unsigned mask = req->head.args.open.mask;
+ if (mask) {
+ Capability *cap = cur->get_client_cap(mdr->get_client());
+ int issued = 0;
+ if (cap && (mdr->snapid == CEPH_NOSNAP || mdr->snapid <= cap->client_follows))
+ issued = cap->issued();
+ // permission bits, ACL/security xattrs
+ if ((mask & CEPH_CAP_AUTH_SHARED) && (issued & CEPH_CAP_AUTH_EXCL) == 0)
+ lov.add_rdlock(&cur->authlock);
+ if ((mask & CEPH_CAP_XATTR_SHARED) && (issued & CEPH_CAP_XATTR_EXCL) == 0)
+ lov.add_rdlock(&cur->xattrlock);
+
+ mdr->getattr_caps = mask;
+ }
+
+ // O_TRUNC
+ if ((flags & CEPH_O_TRUNC) && !mdr->has_completed) {
+ ceph_assert(cur->is_auth());
+
+ lov.add_xlock(&cur->filelock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, cur, MAY_WRITE))
+ return;
+
+ // wait for pending truncate?
+ const auto& pi = cur->get_projected_inode();
+ if (pi->is_truncating()) {
+ dout(10) << " waiting for pending truncate from " << pi->truncate_from
+ << " to " << pi->truncate_size << " to complete on " << *cur << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ cur->add_waiter(CInode::WAIT_TRUNC, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ do_open_truncate(mdr, cmode);
+ return;
+ }
+
+ // sync filelock if snapped.
+ // this makes us wait for writers to flushsnaps, ensuring we get accurate metadata,
+ // and that data itself is flushed so that we can read the snapped data off disk.
+ if (mdr->snapid != CEPH_NOSNAP && !cur->is_dir()) {
+ lov.add_rdlock(&cur->filelock);
+ }
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ mask = MAY_READ;
+ if (cmode & CEPH_FILE_MODE_WR)
+ mask |= MAY_WRITE;
+ if (!check_access(mdr, cur, mask))
+ return;
+
+ utime_t now = ceph_clock_now();
+ mdr->set_mds_stamp(now);
+
+ if (cur->is_file() || cur->is_dir()) {
+ if (mdr->snapid == CEPH_NOSNAP) {
+ // register new cap
+ Capability *cap = mds->locker->issue_new_caps(cur, cmode, mdr, nullptr);
+ if (cap)
+ dout(12) << "open issued caps " << ccap_string(cap->pending())
+ << " for " << req->get_source()
+ << " on " << *cur << dendl;
+ } else {
+ int caps = ceph_caps_for_mode(cmode);
+ dout(12) << "open issued IMMUTABLE SNAP caps " << ccap_string(caps)
+ << " for " << req->get_source()
+ << " snapid " << mdr->snapid
+ << " on " << *cur << dendl;
+ mdr->snap_caps = caps;
+ }
+ }
+
+ // increase max_size?
+ if (cmode & CEPH_FILE_MODE_WR)
+ mds->locker->check_inode_max_size(cur);
+
+ // make sure this inode gets into the journal
+ if (cur->is_auth() && cur->last == CEPH_NOSNAP &&
+ mdcache->open_file_table.should_log_open(cur)) {
+ EOpen *le = new EOpen(mds->mdlog);
+ mdlog->start_entry(le);
+ le->add_clean_inode(cur);
+ mdlog->submit_entry(le);
+ }
+
+ // hit pop
+ if (cmode & CEPH_FILE_MODE_WR)
+ mds->balancer->hit_inode(cur, META_POP_IWR);
+ else
+ mds->balancer->hit_inode(cur, META_POP_IRD);
+
+ CDentry *dn = 0;
+ if (req->get_dentry_wanted()) {
+ ceph_assert(mdr->dn[0].size());
+ dn = mdr->dn[0].back();
+ }
+
+ mdr->tracei = cur;
+ mdr->tracedn = dn;
+ respond_to_request(mdr, 0);
+}
+
+class C_MDS_openc_finish : public ServerLogContext {
+ CDentry *dn;
+ CInode *newi;
+public:
+ C_MDS_openc_finish(Server *s, MDRequestRef& r, CDentry *d, CInode *ni) :
+ ServerLogContext(s, r), dn(d), newi(ni) {}
+ void finish(int r) override {
+ ceph_assert(r == 0);
+
+ // crash current MDS and the replacing MDS will test the journal
+ ceph_assert(!g_conf()->mds_kill_skip_replaying_inotable);
+
+ dn->pop_projected_linkage();
+
+ // dirty inode, dn, dir
+ newi->mark_dirty(mdr->ls);
+ newi->mark_dirty_parent(mdr->ls, true);
+
+ mdr->apply();
+
+ get_mds()->locker->share_inode_max_size(newi);
+
+ MDRequestRef null_ref;
+ get_mds()->mdcache->send_dentry_link(dn, null_ref);
+
+ get_mds()->balancer->hit_inode(newi, META_POP_IWR);
+
+ server->respond_to_request(mdr, 0);
+
+ ceph_assert(g_conf()->mds_kill_openc_at != 1);
+ }
+};
+
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_openc(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ client_t client = mdr->get_client();
+
+ dout(7) << "open w/ O_CREAT on " << req->get_filepath() << dendl;
+
+ int cmode = ceph_flags_to_mode(req->head.args.open.flags);
+ if (cmode < 0) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ bool excl = req->head.args.open.flags & CEPH_O_EXCL;
+ CDentry *dn = rdlock_path_xlock_dentry(mdr, true, !excl, true, true);
+ if (!dn)
+ return;
+
+ CDentry::linkage_t *dnl = dn->get_projected_linkage();
+ if (!excl && !dnl->is_null()) {
+ // it existed.
+ ceph_assert(mdr.get()->is_rdlocked(&dn->lock));
+
+ handle_client_open(mdr);
+ return;
+ }
+
+ ceph_assert(dnl->is_null());
+
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ return;
+ }
+ dn->set_alternate_name(req->get_alternate_name());
+
+ // set layout
+ file_layout_t layout;
+ if (mdr->dir_layout != file_layout_t())
+ layout = mdr->dir_layout;
+ else
+ layout = mdcache->default_file_layout;
+
+ // What kind of client caps are required to complete this operation
+ uint64_t access = MAY_WRITE;
+
+ const auto default_layout = layout;
+
+ // fill in any special params from client
+ if (req->head.args.open.stripe_unit)
+ layout.stripe_unit = req->head.args.open.stripe_unit;
+ if (req->head.args.open.stripe_count)
+ layout.stripe_count = req->head.args.open.stripe_count;
+ if (req->head.args.open.object_size)
+ layout.object_size = req->head.args.open.object_size;
+ if (req->get_connection()->has_feature(CEPH_FEATURE_CREATEPOOLID) &&
+ (__s32)req->head.args.open.pool >= 0) {
+ layout.pool_id = req->head.args.open.pool;
+
+ // make sure we have as new a map as the client
+ if (req->get_mdsmap_epoch() > mds->mdsmap->get_epoch()) {
+ mds->wait_for_mdsmap(req->get_mdsmap_epoch(), new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ }
+
+ // If client doesn't have capability to modify layout pools, then
+ // only permit this request if the requested pool matches what the
+ // file would have inherited anyway from its parent.
+ if (default_layout != layout) {
+ access |= MAY_SET_VXATTR;
+ }
+
+ if (!layout.is_valid()) {
+ dout(10) << " invalid initial file layout" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (!mds->mdsmap->is_data_pool(layout.pool_id)) {
+ dout(10) << " invalid data pool " << layout.pool_id << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ // created null dn.
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+ if (!check_access(mdr, diri, access))
+ return;
+ if (!check_fragment_space(mdr, dir))
+ return;
+ if (!check_dir_max_entries(mdr, dir))
+ return;
+
+ if (mdr->dn[0].size() == 1)
+ mds->locker->create_lock_cache(mdr, diri, &mdr->dir_layout);
+
+ // create inode.
+ CInode *newi = prepare_new_inode(mdr, dn->get_dir(), inodeno_t(req->head.ino),
+ req->head.args.open.mode | S_IFREG, &layout);
+ ceph_assert(newi);
+
+ // it's a file.
+ dn->push_projected_linkage(newi);
+
+ auto _inode = newi->_get_inode();
+ _inode->version = dn->pre_dirty();
+ if (layout.pool_id != mdcache->default_file_layout.pool_id)
+ _inode->add_old_pool(mdcache->default_file_layout.pool_id);
+ _inode->update_backtrace();
+ _inode->rstat.rfiles = 1;
+ _inode->accounted_rstat = _inode->rstat;
+
+ SnapRealm *realm = diri->find_snaprealm();
+ snapid_t follows = mdcache->get_global_snaprealm()->get_newest_seq();
+ ceph_assert(follows >= realm->get_newest_seq());
+
+ ceph_assert(dn->first == follows+1);
+ newi->first = dn->first;
+
+ // do the open
+ Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr, realm);
+ newi->authlock.set_state(LOCK_EXCL);
+ newi->xattrlock.set_state(LOCK_EXCL);
+
+ if (cap && (cmode & CEPH_FILE_MODE_WR)) {
+ _inode->client_ranges[client].range.first = 0;
+ _inode->client_ranges[client].range.last = _inode->layout.stripe_unit;
+ _inode->client_ranges[client].follows = follows;
+ newi->mark_clientwriteable();
+ cap->mark_clientwriteable();
+ }
+
+ // prepare finisher
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "openc");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ journal_allocated_inos(mdr, &le->metablob);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, newi, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+ le->metablob.add_primary_dentry(dn, newi, true, true, true);
+
+ // make sure this inode gets into the journal
+ le->metablob.add_opened_ino(newi->ino());
+
+ C_MDS_openc_finish *fin = new C_MDS_openc_finish(this, mdr, dn, newi);
+
+ if (mdr->session->info.has_feature(CEPHFS_FEATURE_DELEG_INO)) {
+ openc_response_t ocresp;
+
+ dout(10) << "adding created_ino and delegated_inos" << dendl;
+ ocresp.created_ino = _inode->ino;
+
+ if (delegate_inos_pct && !req->is_queued_for_replay()) {
+ // Try to delegate some prealloc_inos to the client, if it's down to half the max
+ unsigned frac = 100 / delegate_inos_pct;
+ if (mdr->session->delegated_inos.size() < (unsigned)g_conf()->mds_client_prealloc_inos / frac / 2)
+ mdr->session->delegate_inos(g_conf()->mds_client_prealloc_inos / frac, ocresp.delegated_inos);
+ }
+
+ encode(ocresp, mdr->reply_extra_bl);
+ } else if (mdr->client_request->get_connection()->has_feature(CEPH_FEATURE_REPLY_CREATE_INODE)) {
+ dout(10) << "adding ino to reply to indicate inode was created" << dendl;
+ // add the file created flag onto the reply if create_flags features is supported
+ encode(newi->ino(), mdr->reply_extra_bl);
+ }
+
+ journal_and_reply(mdr, newi, dn, le, fin);
+
+ // We hit_dir (via hit_inode) in our finish callback, but by then we might
+ // have overshot the split size (multiple opencs in flight), so here is
+ // an early chance to split the dir if this openc makes it oversized.
+ mds->balancer->maybe_fragment(dir, false);
+}
+
+
+void Server::_finalize_readdir(MDRequestRef& mdr,
+ CInode *diri,
+ CDir* dir,
+ bool start,
+ bool end,
+ __u16 flags,
+ __u32 numfiles,
+ bufferlist& dirbl,
+ bufferlist& dnbl)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ Session *session = mds->get_session(req);
+
+ session->touch_readdir_cap(numfiles);
+
+ if (end) {
+ flags |= CEPH_READDIR_FRAG_END;
+ if (start)
+ flags |= CEPH_READDIR_FRAG_COMPLETE; // FIXME: what purpose does this serve
+ }
+
+ // finish final blob
+ encode(numfiles, dirbl);
+ encode(flags, dirbl);
+ dirbl.claim_append(dnbl);
+
+ // yay, reply
+ dout(10) << "reply to " << *req << " readdir num=" << numfiles
+ << " bytes=" << dirbl.length()
+ << " start=" << (int)start
+ << " end=" << (int)end
+ << dendl;
+ mdr->reply_extra_bl = dirbl;
+
+ // bump popularity. NOTE: this doesn't quite capture it.
+ mds->balancer->hit_dir(dir, META_POP_READDIR, numfiles);
+
+ // reply
+ mdr->tracei = diri;
+ respond_to_request(mdr, 0);
+}
+
+void Server::handle_client_readdir(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ Session *session = mds->get_session(req);
+ client_t client = req->get_source().num();
+ MutationImpl::LockOpVec lov;
+ CInode *diri = rdlock_path_pin_ref(mdr, false, true);
+ if (!diri) return;
+
+ // it's a directory, right?
+ if (!diri->is_dir()) {
+ // not a dir
+ dout(10) << "reply to " << *req << " readdir -CEPHFS_ENOTDIR" << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+
+ auto num_caps = session->get_num_caps();
+ auto session_cap_acquisition = session->get_cap_acquisition();
+
+ if (num_caps > static_cast<uint64_t>(max_caps_per_client * max_caps_throttle_ratio) && session_cap_acquisition >= cap_acquisition_throttle) {
+ dout(20) << "readdir throttled. max_caps_per_client: " << max_caps_per_client << " num_caps: " << num_caps
+ << " session_cap_acquistion: " << session_cap_acquisition << " cap_acquisition_throttle: " << cap_acquisition_throttle << dendl;
+ if (logger)
+ logger->inc(l_mdss_cap_acquisition_throttle);
+
+ mdr->mark_event("cap_acquisition_throttle");
+ mds->timer.add_event_after(caps_throttle_retry_request_timeout, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ lov.add_rdlock(&diri->filelock);
+ lov.add_rdlock(&diri->dirfragtreelock);
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, diri, MAY_READ))
+ return;
+
+ // which frag?
+ frag_t fg = (__u32)req->head.args.readdir.frag;
+ unsigned req_flags = (__u32)req->head.args.readdir.flags;
+ string offset_str = req->get_path2();
+
+ __u32 offset_hash = 0;
+ if (!offset_str.empty())
+ offset_hash = ceph_frag_value(diri->hash_dentry_name(offset_str));
+ else
+ offset_hash = (__u32)req->head.args.readdir.offset_hash;
+
+ dout(10) << " frag " << fg << " offset '" << offset_str << "'"
+ << " offset_hash " << offset_hash << " flags " << req_flags << dendl;
+
+ // does the frag exist?
+ if (diri->dirfragtree[fg.value()] != fg) {
+ frag_t newfg;
+ if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+ if (fg.contains((unsigned)offset_hash)) {
+ newfg = diri->dirfragtree[offset_hash];
+ } else {
+ // client actually wants next frag
+ newfg = diri->dirfragtree[fg.value()];
+ }
+ } else {
+ offset_str.clear();
+ newfg = diri->dirfragtree[fg.value()];
+ }
+ dout(10) << " adjust frag " << fg << " -> " << newfg << " " << diri->dirfragtree << dendl;
+ fg = newfg;
+ }
+
+ CDir *dir = try_open_auth_dirfrag(diri, fg, mdr);
+ if (!dir) return;
+
+ // ok!
+ dout(10) << "handle_client_readdir on " << *dir << dendl;
+ ceph_assert(dir->is_auth());
+
+ if (!dir->is_complete()) {
+ if (dir->is_frozen()) {
+ dout(7) << "dir is frozen " << *dir << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ dir->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ // fetch
+ dout(10) << " incomplete dir contents for readdir on " << *dir << ", fetching" << dendl;
+ dir->fetch(new C_MDS_RetryRequest(mdcache, mdr), true);
+ return;
+ }
+
+#ifdef MDS_VERIFY_FRAGSTAT
+ dir->verify_fragstat();
+#endif
+
+ utime_t now = ceph_clock_now();
+ mdr->set_mds_stamp(now);
+
+ snapid_t snapid = mdr->snapid;
+ dout(10) << "snapid " << snapid << dendl;
+
+ SnapRealm *realm = diri->find_snaprealm();
+
+ unsigned max = req->head.args.readdir.max_entries;
+ if (!max)
+ max = dir->get_num_any(); // whatever, something big.
+ unsigned max_bytes = req->head.args.readdir.max_bytes;
+ if (!max_bytes)
+ // make sure at least one item can be encoded
+ max_bytes = (512 << 10) + g_conf()->mds_max_xattr_pairs_size;
+
+ // start final blob
+ bufferlist dirbl;
+ DirStat ds;
+ ds.frag = dir->get_frag();
+ ds.auth = dir->get_dir_auth().first;
+ if (dir->is_auth() && !forward_all_requests_to_auth)
+ dir->get_dist_spec(ds.dist, mds->get_nodeid());
+
+ dir->encode_dirstat(dirbl, mdr->session->info, ds);
+
+ // count bytes available.
+ // this isn't perfect, but we should capture the main variable/unbounded size items!
+ int front_bytes = dirbl.length() + sizeof(__u32) + sizeof(__u8)*2;
+ int bytes_left = max_bytes - front_bytes;
+ bytes_left -= get_snap_trace(session, realm).length();
+
+ // build dir contents
+ bufferlist dnbl;
+ __u32 numfiles = 0;
+ bool start = !offset_hash && offset_str.empty();
+ // skip all dns < dentry_key_t(snapid, offset_str, offset_hash)
+ dentry_key_t skip_key(snapid, offset_str.c_str(), offset_hash);
+ auto it = start ? dir->begin() : dir->lower_bound(skip_key);
+ bool end = (it == dir->end());
+ for (; !end && numfiles < max; end = (it == dir->end())) {
+ CDentry *dn = it->second;
+ ++it;
+
+ if (dn->state_test(CDentry::STATE_PURGING))
+ continue;
+
+ bool dnp = dn->use_projected(client, mdr);
+ CDentry::linkage_t *dnl = dnp ? dn->get_projected_linkage() : dn->get_linkage();
+
+ if (dnl->is_null()) {
+ if (dn->get_num_ref() == 0 && !dn->is_projected())
+ dir->remove_dentry(dn);
+ continue;
+ }
+
+ if (dn->last < snapid || dn->first > snapid) {
+ dout(20) << "skipping non-overlapping snap " << *dn << dendl;
+ continue;
+ }
+
+ if (!start) {
+ dentry_key_t offset_key(dn->last, offset_str.c_str(), offset_hash);
+ if (!(offset_key < dn->key()))
+ continue;
+ }
+
+ CInode *in = dnl->get_inode();
+
+ if (in && in->ino() == CEPH_INO_CEPH)
+ continue;
+
+ // remote link?
+ // better for the MDS to do the work, if we think the client will stat any of these files.
+ if (dnl->is_remote() && !in) {
+ in = mdcache->get_inode(dnl->get_remote_ino());
+ if (in) {
+ dn->link_remote(dnl, in);
+ } else if (dn->state_test(CDentry::STATE_BADREMOTEINO)) {
+ dout(10) << "skipping bad remote ino on " << *dn << dendl;
+ continue;
+ } else {
+ // touch everything i _do_ have
+ for (auto &p : *dir) {
+ if (!p.second->get_linkage()->is_null())
+ mdcache->lru.lru_touch(p.second);
+ }
+
+ // already issued caps and leases, reply immediately.
+ if (dnbl.length() > 0) {
+ mdcache->open_remote_dentry(dn, dnp, new C_MDSInternalNoop);
+ dout(10) << " open remote dentry after caps were issued, stopping at "
+ << dnbl.length() << " < " << bytes_left << dendl;
+ break;
+ }
+
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ mdcache->open_remote_dentry(dn, dnp, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ }
+ ceph_assert(in);
+
+ if ((int)(dnbl.length() + dn->get_name().length() + sizeof(__u32) + sizeof(LeaseStat)) > bytes_left) {
+ dout(10) << " ran out of room, stopping at " << dnbl.length() << " < " << bytes_left << dendl;
+ break;
+ }
+
+ unsigned start_len = dnbl.length();
+
+ // dentry
+ dout(12) << "including dn " << *dn << dendl;
+ encode(dn->get_name(), dnbl);
+ mds->locker->issue_client_lease(dn, in, mdr, now, dnbl);
+
+ // inode
+ dout(12) << "including inode in " << *in << " snap " << snapid << dendl;
+ int r = in->encode_inodestat(dnbl, mdr->session, realm, snapid, bytes_left - (int)dnbl.length());
+ if (r < 0) {
+ // chop off dn->name, lease
+ dout(10) << " ran out of room, stopping at " << start_len << " < " << bytes_left << dendl;
+ bufferlist keep;
+ keep.substr_of(dnbl, 0, start_len);
+ dnbl.swap(keep);
+ break;
+ }
+ ceph_assert(r >= 0);
+ numfiles++;
+
+ // touch dn
+ mdcache->lru.lru_touch(dn);
+ }
+ __u16 flags = 0;
+ // client only understand END and COMPLETE flags ?
+ if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+ flags |= CEPH_READDIR_HASH_ORDER | CEPH_READDIR_OFFSET_HASH;
+ }
+ _finalize_readdir(mdr, diri, dir, start, end, flags, numfiles, dirbl, dnbl);
+}
+
+
+
+// ===============================================================================
+// INODE UPDATES
+
+
+/*
+ * finisher for basic inode updates
+ */
+class C_MDS_inode_update_finish : public ServerLogContext {
+ CInode *in;
+ bool truncating_smaller, changed_ranges, adjust_realm;
+public:
+ C_MDS_inode_update_finish(Server *s, MDRequestRef& r, CInode *i,
+ bool sm=false, bool cr=false, bool ar=false) :
+ ServerLogContext(s, r), in(i),
+ truncating_smaller(sm), changed_ranges(cr), adjust_realm(ar) { }
+ void finish(int r) override {
+ ceph_assert(r == 0);
+
+ int snap_op = (in->snaprealm ? CEPH_SNAP_OP_UPDATE : CEPH_SNAP_OP_SPLIT);
+
+ // apply
+ mdr->apply();
+
+ MDSRank *mds = get_mds();
+
+ // notify any clients
+ if (truncating_smaller && in->get_inode()->is_truncating()) {
+ mds->locker->issue_truncate(in);
+ mds->mdcache->truncate_inode(in, mdr->ls);
+ }
+
+ if (adjust_realm) {
+ mds->mdcache->send_snap_update(in, 0, snap_op);
+ mds->mdcache->do_realm_invalidate_and_update_notify(in, snap_op);
+ }
+
+ get_mds()->balancer->hit_inode(in, META_POP_IWR);
+
+ server->respond_to_request(mdr, 0);
+
+ if (changed_ranges)
+ get_mds()->locker->share_inode_max_size(in);
+ }
+};
+
+void Server::handle_client_file_setlock(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ MutationImpl::LockOpVec lov;
+
+ // get the inode to operate on, and set up any locks needed for that
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur)
+ return;
+
+ lov.add_xlock(&cur->flocklock);
+ /* acquire_locks will return true if it gets the locks. If it fails,
+ it will redeliver this request at a later date, so drop the request.
+ */
+ if (!mds->locker->acquire_locks(mdr, lov)) {
+ dout(10) << "handle_client_file_setlock could not get locks!" << dendl;
+ return;
+ }
+
+ // copy the lock change into a ceph_filelock so we can store/apply it
+ ceph_filelock set_lock;
+ set_lock.start = req->head.args.filelock_change.start;
+ set_lock.length = req->head.args.filelock_change.length;
+ set_lock.client = req->get_orig_source().num();
+ set_lock.owner = req->head.args.filelock_change.owner;
+ set_lock.pid = req->head.args.filelock_change.pid;
+ set_lock.type = req->head.args.filelock_change.type;
+ bool will_wait = req->head.args.filelock_change.wait;
+
+ dout(10) << "handle_client_file_setlock: " << set_lock << dendl;
+
+ ceph_lock_state_t *lock_state = NULL;
+ bool interrupt = false;
+
+ // get the appropriate lock state
+ switch (req->head.args.filelock_change.rule) {
+ case CEPH_LOCK_FLOCK_INTR:
+ interrupt = true;
+ // fall-thru
+ case CEPH_LOCK_FLOCK:
+ lock_state = cur->get_flock_lock_state();
+ break;
+
+ case CEPH_LOCK_FCNTL_INTR:
+ interrupt = true;
+ // fall-thru
+ case CEPH_LOCK_FCNTL:
+ lock_state = cur->get_fcntl_lock_state();
+ break;
+
+ default:
+ dout(10) << "got unknown lock type " << set_lock.type
+ << ", dropping request!" << dendl;
+ respond_to_request(mdr, -CEPHFS_EOPNOTSUPP);
+ return;
+ }
+
+ dout(10) << " state prior to lock change: " << *lock_state << dendl;
+ if (CEPH_LOCK_UNLOCK == set_lock.type) {
+ list<ceph_filelock> activated_locks;
+ MDSContext::vec waiters;
+ if (lock_state->is_waiting(set_lock)) {
+ dout(10) << " unlock removing waiting lock " << set_lock << dendl;
+ lock_state->remove_waiting(set_lock);
+ cur->take_waiting(CInode::WAIT_FLOCK, waiters);
+ } else if (!interrupt) {
+ dout(10) << " unlock attempt on " << set_lock << dendl;
+ lock_state->remove_lock(set_lock, activated_locks);
+ cur->take_waiting(CInode::WAIT_FLOCK, waiters);
+ }
+ mds->queue_waiters(waiters);
+
+ respond_to_request(mdr, 0);
+ } else {
+ dout(10) << " lock attempt on " << set_lock << dendl;
+ bool deadlock = false;
+ if (mdr->more()->flock_was_waiting &&
+ !lock_state->is_waiting(set_lock)) {
+ dout(10) << " was waiting for lock but not anymore, must have been canceled " << set_lock << dendl;
+ respond_to_request(mdr, -CEPHFS_EINTR);
+ } else if (!lock_state->add_lock(set_lock, will_wait, mdr->more()->flock_was_waiting, &deadlock)) {
+ dout(10) << " it failed on this attempt" << dendl;
+ // couldn't set lock right now
+ if (deadlock) {
+ respond_to_request(mdr, -CEPHFS_EDEADLK);
+ } else if (!will_wait) {
+ respond_to_request(mdr, -CEPHFS_EWOULDBLOCK);
+ } else {
+ dout(10) << " added to waiting list" << dendl;
+ ceph_assert(lock_state->is_waiting(set_lock));
+ mdr->more()->flock_was_waiting = true;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ mdr->mark_event("failed to add lock, waiting");
+ mdr->mark_nowarn();
+ cur->add_waiter(CInode::WAIT_FLOCK, new C_MDS_RetryRequest(mdcache, mdr));
+ }
+ } else
+ respond_to_request(mdr, 0);
+ }
+ dout(10) << " state after lock change: " << *lock_state << dendl;
+}
+
+void Server::handle_client_file_readlock(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ MutationImpl::LockOpVec lov;
+
+ // get the inode to operate on, and set up any locks needed for that
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur)
+ return;
+
+ /* acquire_locks will return true if it gets the locks. If it fails,
+ it will redeliver this request at a later date, so drop the request.
+ */
+ lov.add_rdlock(&cur->flocklock);
+ if (!mds->locker->acquire_locks(mdr, lov)) {
+ dout(10) << "handle_client_file_readlock could not get locks!" << dendl;
+ return;
+ }
+
+ // copy the lock change into a ceph_filelock so we can store/apply it
+ ceph_filelock checking_lock;
+ checking_lock.start = req->head.args.filelock_change.start;
+ checking_lock.length = req->head.args.filelock_change.length;
+ checking_lock.client = req->get_orig_source().num();
+ checking_lock.owner = req->head.args.filelock_change.owner;
+ checking_lock.pid = req->head.args.filelock_change.pid;
+ checking_lock.type = req->head.args.filelock_change.type;
+
+ // get the appropriate lock state
+ ceph_lock_state_t *lock_state = NULL;
+ switch (req->head.args.filelock_change.rule) {
+ case CEPH_LOCK_FLOCK:
+ lock_state = cur->get_flock_lock_state();
+ break;
+
+ case CEPH_LOCK_FCNTL:
+ lock_state = cur->get_fcntl_lock_state();
+ break;
+
+ default:
+ dout(10) << "got unknown lock type " << checking_lock.type << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ lock_state->look_for_lock(checking_lock);
+
+ bufferlist lock_bl;
+ encode(checking_lock, lock_bl);
+
+ mdr->reply_extra_bl = lock_bl;
+ respond_to_request(mdr, 0);
+}
+
+void Server::handle_client_setattr(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ MutationImpl::LockOpVec lov;
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur) return;
+
+ if (mdr->snapid != CEPH_NOSNAP) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+ if (cur->ino() < MDS_INO_SYSTEM_BASE && !cur->is_base()) {
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ __u32 mask = req->head.args.setattr.mask;
+ __u32 access_mask = MAY_WRITE;
+
+ if (req->get_header().version < 6) {
+ // No changes to fscrypted inodes by downrevved clients
+ if (!cur->get_inode()->fscrypt_auth.empty()) {
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ // Only allow fscrypt field changes by capable clients
+ if (mask & (CEPH_SETATTR_FSCRYPT_FILE|CEPH_SETATTR_FSCRYPT_AUTH)) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ }
+
+ // xlock inode
+ if (mask & (CEPH_SETATTR_MODE|CEPH_SETATTR_UID|CEPH_SETATTR_GID|CEPH_SETATTR_BTIME|CEPH_SETATTR_KILL_SGUID|CEPH_SETATTR_FSCRYPT_AUTH|CEPH_SETATTR_KILL_SUID|CEPH_SETATTR_KILL_SGID))
+ lov.add_xlock(&cur->authlock);
+ if (mask & (CEPH_SETATTR_MTIME|CEPH_SETATTR_ATIME|CEPH_SETATTR_SIZE|CEPH_SETATTR_FSCRYPT_FILE))
+ lov.add_xlock(&cur->filelock);
+ if (mask & CEPH_SETATTR_CTIME)
+ lov.add_wrlock(&cur->versionlock);
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if ((mask & CEPH_SETATTR_UID) && (cur->get_inode()->uid != req->head.args.setattr.uid))
+ access_mask |= MAY_CHOWN;
+
+ if ((mask & CEPH_SETATTR_GID) && (cur->get_inode()->gid != req->head.args.setattr.gid))
+ access_mask |= MAY_CHGRP;
+
+ if (!check_access(mdr, cur, access_mask))
+ return;
+
+ // trunc from bigger -> smaller?
+ const auto& pip = cur->get_projected_inode();
+
+ uint64_t old_size = std::max<uint64_t>(pip->size, req->head.args.setattr.old_size);
+
+ // CEPHFS_ENOSPC on growing file while full, but allow shrinks
+ if (is_full && req->head.args.setattr.size > old_size) {
+ dout(20) << __func__ << ": full, responding CEPHFS_ENOSPC to setattr with larger size" << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOSPC);
+ return;
+ }
+
+ bool truncating_smaller = false;
+ if (mask & CEPH_SETATTR_SIZE) {
+ if (req->get_data().length() >
+ sizeof(struct ceph_fscrypt_last_block_header) + fscrypt_last_block_max_size) {
+ dout(10) << __func__ << ": the last block size is too large" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ truncating_smaller = req->head.args.setattr.size < old_size ||
+ (req->head.args.setattr.size == old_size && req->get_data().length());
+ if (truncating_smaller && pip->is_truncating()) {
+ dout(10) << " waiting for pending truncate from " << pip->truncate_from
+ << " to " << pip->truncate_size << " to complete on " << *cur << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ cur->add_waiter(CInode::WAIT_TRUNC, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ if (truncating_smaller && req->get_data().length()) {
+ struct ceph_fscrypt_last_block_header header;
+ memset(&header, 0, sizeof(header));
+ auto bl = req->get_data().cbegin();
+ DECODE_START(1, bl);
+ decode(header.change_attr, bl);
+ DECODE_FINISH(bl);
+
+ dout(20) << __func__ << " mdr->retry:" << mdr->retry
+ << " header.change_attr: " << header.change_attr
+ << " header.file_offset: " << header.file_offset
+ << " header.block_size: " << header.block_size
+ << dendl;
+
+ if (header.change_attr != pip->change_attr) {
+ dout(5) << __func__ << ": header.change_attr:" << header.change_attr
+ << " != current change_attr:" << pip->change_attr
+ << ", let client retry it!" << dendl;
+ // flush the journal to make sure the clients will get the lasted
+ // change_attr as possible for the next retry
+ mds->mdlog->flush();
+ respond_to_request(mdr, -CEPHFS_EAGAIN);
+ return;
+ }
+ }
+ }
+
+ bool changed_ranges = false;
+
+ // project update
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "setattr");
+ mdlog->start_entry(le);
+
+ auto pi = cur->project_inode(mdr);
+
+ if (mask & CEPH_SETATTR_UID)
+ pi.inode->uid = req->head.args.setattr.uid;
+ if (mask & CEPH_SETATTR_GID)
+ pi.inode->gid = req->head.args.setattr.gid;
+
+ if (mask & CEPH_SETATTR_MODE)
+ pi.inode->mode = (pi.inode->mode & ~07777) | (req->head.args.setattr.mode & 07777);
+ else if ((mask & (CEPH_SETATTR_UID|CEPH_SETATTR_GID|CEPH_SETATTR_KILL_SGUID|
+ CEPH_SETATTR_KILL_SUID|CEPH_SETATTR_KILL_SGID)) &&
+ S_ISREG(pi.inode->mode)) {
+ if (mask & (CEPH_SETATTR_UID|CEPH_SETATTR_GID|CEPH_SETATTR_KILL_SGUID) &&
+ (pi.inode->mode & (S_IXUSR|S_IXGRP|S_IXOTH))) {
+ pi.inode->mode &= ~(S_ISUID|S_ISGID);
+ } else {
+ if (mask & CEPH_SETATTR_KILL_SUID) {
+ pi.inode->mode &= ~S_ISUID;
+ }
+ if (mask & CEPH_SETATTR_KILL_SGID) {
+ pi.inode->mode &= ~S_ISGID;
+ }
+ }
+ }
+
+ if (mask & CEPH_SETATTR_MTIME)
+ pi.inode->mtime = req->head.args.setattr.mtime;
+ if (mask & CEPH_SETATTR_ATIME)
+ pi.inode->atime = req->head.args.setattr.atime;
+ if (mask & CEPH_SETATTR_BTIME)
+ pi.inode->btime = req->head.args.setattr.btime;
+ if (mask & (CEPH_SETATTR_ATIME | CEPH_SETATTR_MTIME | CEPH_SETATTR_BTIME))
+ pi.inode->time_warp_seq++; // maybe not a timewarp, but still a serialization point.
+ if (mask & CEPH_SETATTR_SIZE) {
+ if (truncating_smaller) {
+ pi.inode->truncate(old_size, req->head.args.setattr.size, req->get_data());
+ le->metablob.add_truncate_start(cur->ino());
+ } else {
+ pi.inode->size = req->head.args.setattr.size;
+ pi.inode->rstat.rbytes = pi.inode->size;
+ }
+ pi.inode->mtime = mdr->get_op_stamp();
+
+ // adjust client's max_size?
+ if (mds->locker->calc_new_client_ranges(cur, pi.inode->size)) {
+ dout(10) << " client_ranges " << cur->get_previous_projected_inode()->client_ranges
+ << " -> " << pi.inode->client_ranges << dendl;
+ changed_ranges = true;
+ }
+ }
+
+ if (mask & CEPH_SETATTR_FSCRYPT_AUTH)
+ pi.inode->fscrypt_auth = req->fscrypt_auth;
+ if (mask & CEPH_SETATTR_FSCRYPT_FILE)
+ pi.inode->fscrypt_file = req->fscrypt_file;
+
+ pi.inode->version = cur->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+
+ // log + wait
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur,
+ truncating_smaller, changed_ranges));
+
+ // flush immediately if there are readers/writers waiting
+ if (mdr->is_xlocked(&cur->filelock) &&
+ (cur->get_caps_wanted() & (CEPH_CAP_FILE_RD|CEPH_CAP_FILE_WR)))
+ mds->mdlog->flush();
+}
+
+/* Takes responsibility for mdr */
+void Server::do_open_truncate(MDRequestRef& mdr, int cmode)
+{
+ CInode *in = mdr->in[0];
+ client_t client = mdr->get_client();
+ ceph_assert(in);
+
+ dout(10) << "do_open_truncate " << *in << dendl;
+
+ SnapRealm *realm = in->find_snaprealm();
+ Capability *cap = mds->locker->issue_new_caps(in, cmode, mdr, realm);
+
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "open_truncate");
+ mdlog->start_entry(le);
+
+ // prepare
+ auto pi = in->project_inode(mdr);
+ pi.inode->version = in->pre_dirty();
+ pi.inode->mtime = pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+
+ uint64_t old_size = std::max<uint64_t>(pi.inode->size, mdr->client_request->head.args.open.old_size);
+ if (old_size > 0) {
+ pi.inode->truncate(old_size, 0);
+ le->metablob.add_truncate_start(in->ino());
+ }
+
+ bool changed_ranges = false;
+ if (cap && (cmode & CEPH_FILE_MODE_WR)) {
+ pi.inode->client_ranges[client].range.first = 0;
+ pi.inode->client_ranges[client].range.last = pi.inode->get_layout_size_increment();
+ pi.inode->client_ranges[client].follows = realm->get_newest_seq();
+ changed_ranges = true;
+ in->mark_clientwriteable();
+ cap->mark_clientwriteable();
+ }
+
+ le->metablob.add_client_req(mdr->reqid, mdr->client_request->get_oldest_client_tid());
+
+ mdcache->predirty_journal_parents(mdr, &le->metablob, in, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, in);
+
+ // make sure ino gets into the journal
+ le->metablob.add_opened_ino(in->ino());
+
+ mdr->o_trunc = true;
+
+ CDentry *dn = 0;
+ if (mdr->client_request->get_dentry_wanted()) {
+ ceph_assert(mdr->dn[0].size());
+ dn = mdr->dn[0].back();
+ }
+
+ journal_and_reply(mdr, in, dn, le, new C_MDS_inode_update_finish(this, mdr, in, old_size > 0,
+ changed_ranges));
+ // Although the `open` part can give an early reply, the truncation won't
+ // happen until our EUpdate is persistent, to give the client a prompt
+ // response we must also flush that event.
+ mdlog->flush();
+}
+
+
+/* This function cleans up the passed mdr */
+void Server::handle_client_setlayout(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur) return;
+
+ if (mdr->snapid != CEPH_NOSNAP) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+ if (!cur->is_file()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (cur->get_projected_inode()->size ||
+ cur->get_projected_inode()->truncate_seq > 1) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+
+ // validate layout
+ file_layout_t layout = cur->get_projected_inode()->layout;
+ // save existing layout for later
+ const auto old_layout = layout;
+
+ int access = MAY_WRITE;
+
+ if (req->head.args.setlayout.layout.fl_object_size > 0)
+ layout.object_size = req->head.args.setlayout.layout.fl_object_size;
+ if (req->head.args.setlayout.layout.fl_stripe_unit > 0)
+ layout.stripe_unit = req->head.args.setlayout.layout.fl_stripe_unit;
+ if (req->head.args.setlayout.layout.fl_stripe_count > 0)
+ layout.stripe_count=req->head.args.setlayout.layout.fl_stripe_count;
+ if (req->head.args.setlayout.layout.fl_pg_pool > 0) {
+ layout.pool_id = req->head.args.setlayout.layout.fl_pg_pool;
+
+ // make sure we have as new a map as the client
+ if (req->get_mdsmap_epoch() > mds->mdsmap->get_epoch()) {
+ mds->wait_for_mdsmap(req->get_mdsmap_epoch(), new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ }
+
+ // Don't permit layout modifications without 'p' caps
+ if (layout != old_layout) {
+ access |= MAY_SET_VXATTR;
+ }
+
+ if (!layout.is_valid()) {
+ dout(10) << "bad layout" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (!mds->mdsmap->is_data_pool(layout.pool_id)) {
+ dout(10) << " invalid data pool " << layout.pool_id << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&cur->filelock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, cur, access))
+ return;
+
+ // project update
+ auto pi = cur->project_inode(mdr);
+ pi.inode->layout = layout;
+ // add the old pool to the inode
+ pi.inode->add_old_pool(old_layout.pool_id);
+ pi.inode->version = cur->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "setlayout");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+}
+
+bool Server::xlock_policylock(MDRequestRef& mdr, CInode *in, bool want_layout, bool xlock_snaplock)
+{
+ if (mdr->locking_state & MutationImpl::ALL_LOCKED)
+ return true;
+
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&in->policylock);
+ if (xlock_snaplock)
+ lov.add_xlock(&in->snaplock);
+ else
+ lov.add_rdlock(&in->snaplock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return false;
+
+ if (want_layout && in->get_projected_inode()->has_layout()) {
+ mdr->dir_layout = in->get_projected_inode()->layout;
+ want_layout = false;
+ }
+ if (CDentry *pdn = in->get_projected_parent_dn(); pdn) {
+ if (!mds->locker->try_rdlock_snap_layout(pdn->get_dir()->get_inode(), mdr, 0, want_layout))
+ return false;
+ }
+
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ return true;
+}
+
+CInode* Server::try_get_auth_inode(MDRequestRef& mdr, inodeno_t ino)
+{
+ CInode *in = mdcache->get_inode(ino);
+ if (!in || in->state_test(CInode::STATE_PURGING)) {
+ respond_to_request(mdr, -CEPHFS_ESTALE);
+ return nullptr;
+ }
+ if (!in->is_auth()) {
+ mdcache->request_forward(mdr, in->authority().first);
+ return nullptr;
+ }
+
+ return in;
+}
+
+void Server::handle_client_setdirlayout(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ // can't use rdlock_path_pin_ref because we need to xlock snaplock/policylock
+ CInode *cur = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!cur)
+ return;
+
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+
+ if (!xlock_policylock(mdr, cur, true))
+ return;
+
+ // validate layout
+ const auto& old_pi = cur->get_projected_inode();
+ file_layout_t layout;
+ if (old_pi->has_layout())
+ layout = old_pi->layout;
+ else if (mdr->dir_layout != file_layout_t())
+ layout = mdr->dir_layout;
+ else
+ layout = mdcache->default_file_layout;
+
+ // Level of access required to complete
+ int access = MAY_WRITE;
+
+ const auto old_layout = layout;
+
+ if (req->head.args.setlayout.layout.fl_object_size > 0)
+ layout.object_size = req->head.args.setlayout.layout.fl_object_size;
+ if (req->head.args.setlayout.layout.fl_stripe_unit > 0)
+ layout.stripe_unit = req->head.args.setlayout.layout.fl_stripe_unit;
+ if (req->head.args.setlayout.layout.fl_stripe_count > 0)
+ layout.stripe_count=req->head.args.setlayout.layout.fl_stripe_count;
+ if (req->head.args.setlayout.layout.fl_pg_pool > 0) {
+ layout.pool_id = req->head.args.setlayout.layout.fl_pg_pool;
+ // make sure we have as new a map as the client
+ if (req->get_mdsmap_epoch() > mds->mdsmap->get_epoch()) {
+ mds->wait_for_mdsmap(req->get_mdsmap_epoch(), new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ }
+
+ if (layout != old_layout) {
+ access |= MAY_SET_VXATTR;
+ }
+
+ if (!layout.is_valid()) {
+ dout(10) << "bad layout" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (!mds->mdsmap->is_data_pool(layout.pool_id)) {
+ dout(10) << " invalid data pool " << layout.pool_id << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!check_access(mdr, cur, access))
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ pi.inode->layout = layout;
+ pi.inode->version = cur->pre_dirty();
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "setlayout");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ mdr->no_early_reply = true;
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+}
+
+// XATTRS
+int Server::parse_layout_vxattr_json(
+ string name, string value, const OSDMap& osdmap, file_layout_t *layout)
+{
+ auto parse_pool = [&](std::string pool_name, int64_t pool_id) -> int64_t {
+ if (pool_name != "") {
+ int64_t _pool_id = osdmap.lookup_pg_pool_name(pool_name);
+ if (_pool_id < 0) {
+ dout(10) << __func__ << ": unknown pool name:" << pool_name << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return _pool_id;
+ } else if (pool_id >= 0) {
+ const auto pools = osdmap.get_pools();
+ if (pools.find(pool_id) == pools.end()) {
+ dout(10) << __func__ << ": unknown pool id:" << pool_id << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return pool_id;
+ } else {
+ return -CEPHFS_EINVAL;
+ }
+ };
+
+ try {
+ if (name == "layout.json") {
+ JSONParser json_parser;
+ if (json_parser.parse(value.c_str(), value.length()) and json_parser.is_object()) {
+ std::string field;
+ try {
+ field = "object_size";
+ JSONDecoder::decode_json("object_size", layout->object_size, &json_parser, true);
+
+ field = "stripe_unit";
+ JSONDecoder::decode_json("stripe_unit", layout->stripe_unit, &json_parser, true);
+
+ field = "stripe_count";
+ JSONDecoder::decode_json("stripe_count", layout->stripe_count, &json_parser, true);
+
+ field = "pool_namespace";
+ JSONDecoder::decode_json("pool_namespace", layout->pool_ns, &json_parser, false);
+
+ field = "pool_id";
+ int64_t pool_id = 0;
+ JSONDecoder::decode_json("pool_id", pool_id, &json_parser, false);
+
+ field = "pool_name";
+ std::string pool_name;
+ JSONDecoder::decode_json("pool_name", pool_name, &json_parser, false);
+
+ pool_id = parse_pool(pool_name, pool_id);
+ if (pool_id < 0) {
+ return (int)pool_id;
+ }
+ layout->pool_id = pool_id;
+ } catch (JSONDecoder::err&) {
+ dout(10) << __func__ << ": json is missing a mandatory field named "
+ << field << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ } else {
+ dout(10) << __func__ << ": bad json" << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ } else {
+ dout(10) << __func__ << ": unknown layout vxattr " << name << dendl;
+ return -CEPHFS_ENODATA; // no such attribute
+ }
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << __func__ << ": bad vxattr value:" << value
+ << ", unable to parse for xattr:" << name << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return 0;
+}
+
+// parse old style layout string
+int Server::parse_layout_vxattr_string(
+ string name, string value, const OSDMap& osdmap, file_layout_t *layout)
+{
+ try {
+ if (name == "layout") {
+ string::iterator begin = value.begin();
+ string::iterator end = value.end();
+ keys_and_values<string::iterator> p; // create instance of parser
+ std::map<string, string> m; // map to receive results
+ if (!qi::parse(begin, end, p, m)) { // returns true if successful
+ return -CEPHFS_EINVAL;
+ }
+ string left(begin, end);
+ dout(10) << __func__ << ": parsed " << m << " left '" << left << "'" << dendl;
+ if (begin != end)
+ return -CEPHFS_EINVAL;
+ for (map<string,string>::iterator q = m.begin(); q != m.end(); ++q) {
+ // Skip validation on each attr, we do it once at the end (avoid
+ // rejecting intermediate states if the overall result is ok)
+ int r = parse_layout_vxattr_string(string("layout.") + q->first, q->second,
+ osdmap, layout);
+ if (r < 0)
+ return r;
+ }
+ } else if (name == "layout.object_size") {
+ layout->object_size = boost::lexical_cast<unsigned>(value);
+ } else if (name == "layout.stripe_unit") {
+ layout->stripe_unit = boost::lexical_cast<unsigned>(value);
+ } else if (name == "layout.stripe_count") {
+ layout->stripe_count = boost::lexical_cast<unsigned>(value);
+ } else if (name == "layout.pool") {
+ try {
+ layout->pool_id = boost::lexical_cast<unsigned>(value);
+ } catch (boost::bad_lexical_cast const&) {
+ int64_t pool = osdmap.lookup_pg_pool_name(value);
+ if (pool < 0) {
+ dout(10) << __func__ << ": unknown pool " << value << dendl;
+ return -CEPHFS_ENOENT;
+ }
+ layout->pool_id = pool;
+ }
+ } else if (name == "layout.pool_id") {
+ layout->pool_id = boost::lexical_cast<int64_t>(value);
+ } else if (name == "layout.pool_name") {
+ layout->pool_id = osdmap.lookup_pg_pool_name(value);
+ if (layout->pool_id < 0) {
+ dout(10) << __func__ << ": unknown pool " << value << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ } else if (name == "layout.pool_namespace") {
+ layout->pool_ns = value;
+ } else {
+ dout(10) << __func__ << ": unknown layout vxattr " << name << dendl;
+ return -CEPHFS_ENODATA; // no such attribute
+ }
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << __func__ << ": bad vxattr value, unable to parse int for "
+ << name << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return 0;
+}
+
+int Server::parse_layout_vxattr(string name, string value, const OSDMap& osdmap,
+ file_layout_t *layout, bool validate)
+{
+ dout(20) << __func__ << ": name:" << name << " value:'" << value << "'" << dendl;
+
+ int r;
+ if (name == "layout.json") {
+ r = parse_layout_vxattr_json(name, value, osdmap, layout);
+ } else {
+ r = parse_layout_vxattr_string(name, value, osdmap, layout);
+ }
+ if (r < 0) {
+ return r;
+ }
+
+ if (validate && !layout->is_valid()) {
+ dout(10) << __func__ << ": bad layout" << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ if (!mds->mdsmap->is_data_pool(layout->pool_id)) {
+ dout(10) << __func__ << ": invalid data pool " << layout->pool_id << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return 0;
+}
+
+int Server::parse_quota_vxattr(string name, string value, quota_info_t *quota)
+{
+ dout(20) << "parse_quota_vxattr name " << name << " value '" << value << "'" << dendl;
+ try {
+ if (name == "quota") {
+ string::iterator begin = value.begin();
+ string::iterator end = value.end();
+ if (begin == end) {
+ // keep quota unchanged. (for create_quota_realm())
+ return 0;
+ }
+ keys_and_values<string::iterator> p; // create instance of parser
+ std::map<string, string> m; // map to receive results
+ if (!qi::parse(begin, end, p, m)) { // returns true if successful
+ return -CEPHFS_EINVAL;
+ }
+ string left(begin, end);
+ dout(10) << " parsed " << m << " left '" << left << "'" << dendl;
+ if (begin != end)
+ return -CEPHFS_EINVAL;
+ for (map<string,string>::iterator q = m.begin(); q != m.end(); ++q) {
+ int r = parse_quota_vxattr(string("quota.") + q->first, q->second, quota);
+ if (r < 0)
+ return r;
+ }
+ } else if (name == "quota.max_bytes") {
+ int64_t q = boost::lexical_cast<int64_t>(value);
+ if (q < 0)
+ return -CEPHFS_EINVAL;
+ quota->max_bytes = q;
+ } else if (name == "quota.max_files") {
+ int64_t q = boost::lexical_cast<int64_t>(value);
+ if (q < 0)
+ return -CEPHFS_EINVAL;
+ quota->max_files = q;
+ } else {
+ dout(10) << " unknown quota vxattr " << name << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << "bad vxattr value, unable to parse int for " << name << dendl;
+ return -CEPHFS_EINVAL;
+ }
+
+ if (!quota->is_valid()) {
+ dout(10) << "bad quota" << dendl;
+ return -CEPHFS_EINVAL;
+ }
+ return 0;
+}
+
+void Server::create_quota_realm(CInode *in)
+{
+ dout(10) << __func__ << " " << *in << dendl;
+
+ auto req = make_message<MClientRequest>(CEPH_MDS_OP_SETXATTR);
+ req->set_filepath(filepath(in->ino()));
+ req->set_string2("ceph.quota");
+ // empty vxattr value
+ req->set_tid(mds->issue_tid());
+
+ mds->send_message_mds(req, in->authority().first);
+}
+
+/*
+ * Verify that the file layout attribute carried by client
+ * is well-formatted.
+ * Return 0 on success, otherwise this function takes
+ * responsibility for the passed mdr.
+ */
+int Server::check_layout_vxattr(MDRequestRef& mdr,
+ string name,
+ string value,
+ file_layout_t *layout)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ epoch_t epoch;
+ int r;
+
+ mds->objecter->with_osdmap([&](const OSDMap& osdmap) {
+ r = parse_layout_vxattr(name, value, osdmap, layout);
+ epoch = osdmap.get_epoch();
+ });
+
+ if (r == -CEPHFS_ENOENT) {
+
+ // we don't have the specified pool, make sure our map
+ // is newer than or as new as the client.
+ epoch_t req_epoch = req->get_osdmap_epoch();
+
+ if (req_epoch > epoch) {
+
+ // well, our map is older. consult mds.
+ auto fin = new C_IO_Wrapper(mds, new C_MDS_RetryRequest(mdcache, mdr));
+
+ mds->objecter->wait_for_map(req_epoch, lambdafy(fin));
+ return r;
+ } else if (req_epoch == 0 && !mdr->waited_for_osdmap) {
+
+ // For compatibility with client w/ old code, we still need get the
+ // latest map. One day if COMPACT_VERSION of MClientRequest >=3,
+ // we can remove those code.
+ mdr->waited_for_osdmap = true;
+ mds->objecter->wait_for_latest_osdmap(std::ref(*new C_IO_Wrapper(
+ mds, new C_MDS_RetryRequest(mdcache, mdr))));
+ return r;
+ }
+ }
+
+ if (r < 0) {
+
+ if (r == -CEPHFS_ENOENT)
+ r = -CEPHFS_EINVAL;
+
+ respond_to_request(mdr, r);
+ return r;
+ }
+
+ // all is well
+ return 0;
+}
+
+void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ MutationImpl::LockOpVec lov;
+ string name(req->get_path2());
+ bufferlist bl = req->get_data();
+ string value (bl.c_str(), bl.length());
+ dout(10) << "handle_set_vxattr " << name
+ << " val " << value.length()
+ << " bytes on " << *cur
+ << dendl;
+
+ CInode::mempool_inode *pip = nullptr;
+ string rest;
+
+ if (!check_access(mdr, cur, MAY_SET_VXATTR)) {
+ return;
+ }
+
+ bool adjust_realm = false;
+ if (name.compare(0, 15, "ceph.dir.layout") == 0) {
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!xlock_policylock(mdr, cur, true))
+ return;
+
+ /* We need 'As' caps for the fscrypt context */
+ lov.add_xlock(&cur->authlock);
+ if (!mds->locker->acquire_locks(mdr, lov)) {
+ return;
+ }
+
+ /* encrypted directories can't have their layout changed */
+ if (!cur->get_inode()->fscrypt_auth.empty()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ file_layout_t layout;
+ if (cur->get_projected_inode()->has_layout())
+ layout = cur->get_projected_inode()->layout;
+ else if (mdr->dir_layout != file_layout_t())
+ layout = mdr->dir_layout;
+ else
+ layout = mdcache->default_file_layout;
+
+ rest = name.substr(name.find("layout"));
+ if (check_layout_vxattr(mdr, rest, value, &layout) < 0)
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ pi.inode->layout = layout;
+ mdr->no_early_reply = true;
+ pip = pi.inode.get();
+ } else if (name.compare(0, 16, "ceph.file.layout") == 0) {
+ if (!cur->is_file()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (cur->get_projected_inode()->size ||
+ cur->get_projected_inode()->truncate_seq > 1) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+ file_layout_t layout = cur->get_projected_inode()->layout;
+ rest = name.substr(name.find("layout"));
+ if (check_layout_vxattr(mdr, rest, value, &layout) < 0)
+ return;
+
+ lov.add_xlock(&cur->filelock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ /* encrypted files can't have their layout changed */
+ if (!cur->get_inode()->fscrypt_auth.empty()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ auto pi = cur->project_inode(mdr);
+ int64_t old_pool = pi.inode->layout.pool_id;
+ pi.inode->add_old_pool(old_pool);
+ pi.inode->layout = layout;
+ pip = pi.inode.get();
+ } else if (name.compare(0, 10, "ceph.quota") == 0) {
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ quota_info_t quota = cur->get_projected_inode()->quota;
+
+ rest = name.substr(name.find("quota"));
+ int r = parse_quota_vxattr(rest, value, &quota);
+ if (r < 0) {
+ respond_to_request(mdr, r);
+ return;
+ }
+
+ if (quota.is_enabled() && !cur->get_projected_srnode())
+ adjust_realm = true;
+
+ if (!xlock_policylock(mdr, cur, false, adjust_realm))
+ return;
+
+ if (cur->get_projected_inode()->quota == quota) {
+ respond_to_request(mdr, 0);
+ return;
+ }
+
+ auto pi = cur->project_inode(mdr, false, adjust_realm);
+ pi.inode->quota = quota;
+
+ if (adjust_realm)
+ pi.snapnode->created = pi.snapnode->seq = cur->find_snaprealm()->get_newest_seq();
+
+ mdr->no_early_reply = true;
+ pip = pi.inode.get();
+
+ client_t exclude_ct = mdr->get_client();
+ mdcache->broadcast_quota_to_client(cur, exclude_ct, true);
+ } else if (name == "ceph.dir.subvolume"sv) {
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ bool val;
+ try {
+ val = boost::lexical_cast<bool>(value);
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << "bad vxattr value, unable to parse bool for " << name << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ /* Verify it's not already a subvolume with lighter weight
+ * rdlock.
+ */
+ if (!mdr->more()->rdonly_checks) {
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ lov.add_rdlock(&cur->snaplock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+ const auto srnode = cur->get_projected_srnode();
+ if (val == (srnode && srnode->is_subvolume())) {
+ dout(20) << "already marked subvolume" << dendl;
+ respond_to_request(mdr, 0);
+ return;
+ }
+ mdr->more()->rdonly_checks = true;
+ }
+
+ if ((mdr->locking_state & MutationImpl::ALL_LOCKED) && !mdr->is_xlocked(&cur->snaplock)) {
+ /* drop the rdlock and acquire xlocks */
+ dout(20) << "dropping rdlocks" << dendl;
+ mds->locker->drop_locks(mdr.get());
+ if (!xlock_policylock(mdr, cur, false, true))
+ return;
+ }
+
+ /* repeat rdonly checks in case changed between rdlock -> xlock */
+ SnapRealm *realm = cur->find_snaprealm();
+ if (val) {
+ inodeno_t subvol_ino = realm->get_subvolume_ino();
+ // can't create subvolume inside another subvolume
+ if (subvol_ino && subvol_ino != cur->ino()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ }
+
+ const auto srnode = cur->get_projected_srnode();
+ if (val == (srnode && srnode->is_subvolume())) {
+ respond_to_request(mdr, 0);
+ return;
+ }
+
+ auto pi = cur->project_inode(mdr, false, true);
+ if (!srnode)
+ pi.snapnode->created = pi.snapnode->seq = realm->get_newest_seq();
+ if (val)
+ pi.snapnode->mark_subvolume();
+ else
+ pi.snapnode->clear_subvolume();
+
+ mdr->no_early_reply = true;
+ pip = pi.inode.get();
+ adjust_realm = true;
+ } else if (name == "ceph.dir.pin"sv) {
+ if (!cur->is_dir() || cur->is_root()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ mds_rank_t rank;
+ try {
+ rank = boost::lexical_cast<mds_rank_t>(value);
+ if (rank < 0) rank = MDS_RANK_NONE;
+ else if (rank >= MAX_MDS) {
+ respond_to_request(mdr, -CEPHFS_EDOM);
+ return;
+ }
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << "bad vxattr value, unable to parse int for " << name << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!xlock_policylock(mdr, cur))
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ cur->set_export_pin(rank);
+ pip = pi.inode.get();
+ } else if (name == "ceph.dir.pin.random"sv) {
+ if (!cur->is_dir() || cur->is_root()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ double val;
+ try {
+ val = boost::lexical_cast<double>(value);
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << "bad vxattr value, unable to parse float for " << name << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (val < 0.0 || 1.0 < val) {
+ respond_to_request(mdr, -CEPHFS_EDOM);
+ return;
+ } else if (mdcache->export_ephemeral_random_max < val) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!xlock_policylock(mdr, cur))
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ cur->setxattr_ephemeral_rand(val);
+ pip = pi.inode.get();
+ } else if (name == "ceph.dir.pin.distributed"sv) {
+ if (!cur->is_dir() || cur->is_root()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ bool val;
+ try {
+ val = boost::lexical_cast<bool>(value);
+ } catch (boost::bad_lexical_cast const&) {
+ dout(10) << "bad vxattr value, unable to parse bool for " << name << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!xlock_policylock(mdr, cur))
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ cur->setxattr_ephemeral_dist(val);
+ pip = pi.inode.get();
+ } else {
+ dout(10) << " unknown vxattr " << name << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ pip->change_attr++;
+ pip->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pip->rstat.rctime)
+ pip->rstat.rctime = mdr->get_op_stamp();
+ pip->version = cur->pre_dirty();
+ if (cur->is_file())
+ pip->update_backtrace();
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "set vxattr layout");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur,
+ false, false, adjust_realm));
+ return;
+}
+
+void Server::handle_remove_vxattr(MDRequestRef& mdr, CInode *cur)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ string name(req->get_path2());
+
+ dout(10) << __func__ << " " << name << " on " << *cur << dendl;
+
+ if (name == "ceph.dir.layout") {
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+ return;
+ }
+ if (cur->is_root()) {
+ dout(10) << "can't remove layout policy on the root directory" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ if (!cur->get_projected_inode()->has_layout()) {
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+ return;
+ }
+
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&cur->policylock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ auto pi = cur->project_inode(mdr);
+ pi.inode->clear_layout();
+ pi.inode->version = cur->pre_dirty();
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "remove dir layout vxattr");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ mdr->no_early_reply = true;
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+ return;
+ } else if (name == "ceph.dir.layout.pool_namespace"
+ || name == "ceph.file.layout.pool_namespace") {
+ // Namespace is the only layout field that has a meaningful
+ // null/none value (empty string, means default layout). Is equivalent
+ // to a setxattr with empty string: pass through the empty payload of
+ // the rmxattr request to do this.
+ handle_set_vxattr(mdr, cur);
+ return;
+ }
+
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+}
+
+const Server::XattrHandler Server::xattr_handlers[] = {
+ {
+ xattr_name: Server::DEFAULT_HANDLER,
+ description: "default xattr handler",
+ validate: &Server::default_xattr_validate,
+ setxattr: &Server::default_setxattr_handler,
+ removexattr: &Server::default_removexattr_handler,
+ },
+ {
+ xattr_name: "ceph.mirror.info",
+ description: "mirror info xattr handler",
+ validate: &Server::mirror_info_xattr_validate,
+ setxattr: &Server::mirror_info_setxattr_handler,
+ removexattr: &Server::mirror_info_removexattr_handler
+ },
+};
+
+const Server::XattrHandler* Server::get_xattr_or_default_handler(std::string_view xattr_name) {
+ const XattrHandler *default_xattr_handler = nullptr;
+
+ for (auto &handler : xattr_handlers) {
+ if (handler.xattr_name == Server::DEFAULT_HANDLER) {
+ ceph_assert(default_xattr_handler == nullptr);
+ default_xattr_handler = &handler;
+ }
+ if (handler.xattr_name == xattr_name) {
+ dout(20) << "handler=" << handler.description << dendl;
+ return &handler;
+ }
+ }
+
+ ceph_assert(default_xattr_handler != nullptr);
+ dout(20) << "handler=" << default_xattr_handler->description << dendl;
+ return default_xattr_handler;
+}
+
+int Server::xattr_validate(CInode *cur, const InodeStoreBase::xattr_map_const_ptr xattrs,
+ const std::string &xattr_name, int op, int flags) {
+ if (op == CEPH_MDS_OP_SETXATTR) {
+ if (xattrs) {
+ if ((flags & CEPH_XATTR_CREATE) && xattrs->count(mempool::mds_co::string(xattr_name))) {
+ dout(10) << "setxattr '" << xattr_name << "' XATTR_CREATE and CEPHFS_EEXIST on " << *cur << dendl;
+ return -CEPHFS_EEXIST;
+ }
+ }
+ if ((flags & CEPH_XATTR_REPLACE) && !(xattrs && xattrs->count(mempool::mds_co::string(xattr_name)))) {
+ dout(10) << "setxattr '" << xattr_name << "' XATTR_REPLACE and CEPHFS_ENODATA on " << *cur << dendl;
+ return -CEPHFS_ENODATA;
+ }
+
+ return 0;
+ }
+
+ if (op == CEPH_MDS_OP_RMXATTR) {
+ if (!xattrs || xattrs->count(mempool::mds_co::string(xattr_name)) == 0) {
+ dout(10) << "removexattr '" << xattr_name << "' and CEPHFS_ENODATA on " << *cur << dendl;
+ return -CEPHFS_ENODATA;
+ }
+
+ return 0;
+ }
+
+ derr << ": unhandled validation for: " << xattr_name << dendl;
+ return -CEPHFS_EINVAL;
+}
+
+void Server::xattr_set(InodeStoreBase::xattr_map_ptr xattrs, const std::string &xattr_name,
+ const bufferlist &xattr_value) {
+ size_t len = xattr_value.length();
+ bufferptr b = buffer::create(len);
+ if (len) {
+ xattr_value.begin().copy(len, b.c_str());
+ }
+ auto em = xattrs->emplace(std::piecewise_construct,
+ std::forward_as_tuple(mempool::mds_co::string(xattr_name)),
+ std::forward_as_tuple(b));
+ if (!em.second) {
+ em.first->second = b;
+ }
+}
+
+void Server::xattr_rm(InodeStoreBase::xattr_map_ptr xattrs, const std::string &xattr_name) {
+ xattrs->erase(mempool::mds_co::string(xattr_name));
+}
+
+int Server::default_xattr_validate(CInode *cur, const InodeStoreBase::xattr_map_const_ptr xattrs,
+ XattrOp *xattr_op) {
+ return xattr_validate(cur, xattrs, xattr_op->xattr_name, xattr_op->op, xattr_op->flags);
+}
+
+void Server::default_setxattr_handler(CInode *cur, InodeStoreBase::xattr_map_ptr xattrs,
+ const XattrOp &xattr_op) {
+ xattr_set(xattrs, xattr_op.xattr_name, xattr_op.xattr_value);
+}
+
+void Server::default_removexattr_handler(CInode *cur, InodeStoreBase::xattr_map_ptr xattrs,
+ const XattrOp &xattr_op) {
+ xattr_rm(xattrs, xattr_op.xattr_name);
+}
+
+// mirror info xattr handlers
+const std::string Server::MirrorXattrInfo::MIRROR_INFO_REGEX = "^cluster_id=([a-f0-9]{8}-" \
+ "[a-f0-9]{4}-[a-f0-9]{4}-" \
+ "[a-f0-9]{4}-[a-f0-9]{12})" \
+ " fs_id=(\\d+)$";
+const std::string Server::MirrorXattrInfo::CLUSTER_ID = "ceph.mirror.info.cluster_id";
+const std::string Server::MirrorXattrInfo::FS_ID = "ceph.mirror.info.fs_id";
+int Server::parse_mirror_info_xattr(const std::string &name, const std::string &value,
+ std::string &cluster_id, std::string &fs_id) {
+ dout(20) << "parsing name=" << name << ", value=" << value << dendl;
+
+ static const std::regex regex(Server::MirrorXattrInfo::MIRROR_INFO_REGEX);
+ std::smatch match;
+
+ std::regex_search(value, match, regex);
+ if (match.size() != 3) {
+ derr << "mirror info parse error" << dendl;
+ return -CEPHFS_EINVAL;
+ }
+
+ cluster_id = match[1];
+ fs_id = match[2];
+ dout(20) << " parsed cluster_id=" << cluster_id << ", fs_id=" << fs_id << dendl;
+ return 0;
+}
+
+int Server::mirror_info_xattr_validate(CInode *cur, const InodeStoreBase::xattr_map_const_ptr xattrs,
+ XattrOp *xattr_op) {
+ if (!cur->is_root()) {
+ return -CEPHFS_EINVAL;
+ }
+
+ int v1 = xattr_validate(cur, xattrs, Server::MirrorXattrInfo::CLUSTER_ID, xattr_op->op, xattr_op->flags);
+ int v2 = xattr_validate(cur, xattrs, Server::MirrorXattrInfo::FS_ID, xattr_op->op, xattr_op->flags);
+ if (v1 != v2) {
+ derr << "inconsistent mirror info state (" << v1 << "," << v2 << ")" << dendl;
+ return -CEPHFS_EINVAL;
+ }
+
+ if (v1 < 0) {
+ return v1;
+ }
+
+ if (xattr_op->op == CEPH_MDS_OP_RMXATTR) {
+ return 0;
+ }
+
+ std::string cluster_id;
+ std::string fs_id;
+ int r = parse_mirror_info_xattr(xattr_op->xattr_name, xattr_op->xattr_value.to_str(),
+ cluster_id, fs_id);
+ if (r < 0) {
+ return r;
+ }
+
+ xattr_op->xinfo = std::make_unique<MirrorXattrInfo>(cluster_id, fs_id);
+ return 0;
+}
+
+void Server::mirror_info_setxattr_handler(CInode *cur, InodeStoreBase::xattr_map_ptr xattrs,
+ const XattrOp &xattr_op) {
+ auto mirror_info = dynamic_cast<MirrorXattrInfo&>(*(xattr_op.xinfo));
+
+ bufferlist bl;
+ bl.append(mirror_info.cluster_id.c_str(), mirror_info.cluster_id.length());
+ xattr_set(xattrs, Server::MirrorXattrInfo::CLUSTER_ID, bl);
+
+ bl.clear();
+ bl.append(mirror_info.fs_id.c_str(), mirror_info.fs_id.length());
+ xattr_set(xattrs, Server::MirrorXattrInfo::FS_ID, bl);
+}
+
+void Server::mirror_info_removexattr_handler(CInode *cur, InodeStoreBase::xattr_map_ptr xattrs,
+ const XattrOp &xattr_op) {
+ xattr_rm(xattrs, Server::MirrorXattrInfo::CLUSTER_ID);
+ xattr_rm(xattrs, Server::MirrorXattrInfo::FS_ID);
+}
+
+void Server::handle_client_setxattr(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ string name(req->get_path2());
+
+ // is a ceph virtual xattr?
+ if (is_ceph_vxattr(name)) {
+ // can't use rdlock_path_pin_ref because we need to xlock snaplock/policylock
+ CInode *cur = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!cur)
+ return;
+
+ handle_set_vxattr(mdr, cur);
+ return;
+ }
+
+ if (!is_allowed_ceph_xattr(name)) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ CInode *cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur)
+ return;
+
+ if (mdr->snapid != CEPH_NOSNAP) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+
+ int flags = req->head.args.setxattr.flags;
+
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&cur->xattrlock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, cur, MAY_WRITE))
+ return;
+
+ size_t len = req->get_data().length();
+ size_t inc = len + name.length();
+
+ auto handler = Server::get_xattr_or_default_handler(name);
+ const auto& pxattrs = cur->get_projected_xattrs();
+ if (pxattrs) {
+ // check xattrs kv pairs size
+ size_t cur_xattrs_size = 0;
+ for (const auto& p : *pxattrs) {
+ if ((flags & CEPH_XATTR_REPLACE) && name.compare(p.first) == 0) {
+ continue;
+ }
+ cur_xattrs_size += p.first.length() + p.second.length();
+ }
+
+ if (((cur_xattrs_size + inc) > g_conf()->mds_max_xattr_pairs_size)) {
+ dout(10) << "xattr kv pairs size too big. cur_xattrs_size "
+ << cur_xattrs_size << ", inc " << inc << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOSPC);
+ return;
+ }
+ }
+
+ XattrOp xattr_op(CEPH_MDS_OP_SETXATTR, name, req->get_data(), flags);
+ int r = std::invoke(handler->validate, this, cur, pxattrs, &xattr_op);
+ if (r < 0) {
+ respond_to_request(mdr, r);
+ return;
+ }
+
+ dout(10) << "setxattr '" << name << "' len " << len << " on " << *cur << dendl;
+
+ // project update
+ auto pi = cur->project_inode(mdr, true);
+ pi.inode->version = cur->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+ pi.inode->xattr_version++;
+
+ if ((flags & CEPH_XATTR_REMOVE)) {
+ std::invoke(handler->removexattr, this, cur, pi.xattrs, xattr_op);
+ } else {
+ std::invoke(handler->setxattr, this, cur, pi.xattrs, xattr_op);
+ }
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "setxattr");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+}
+
+void Server::handle_client_removexattr(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ std::string name(req->get_path2());
+
+ // is a ceph virtual xattr?
+ if (is_ceph_vxattr(name)) {
+ // can't use rdlock_path_pin_ref because we need to xlock snaplock/policylock
+ CInode *cur = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!cur)
+ return;
+
+ handle_remove_vxattr(mdr, cur);
+ return;
+ }
+
+ if (!is_allowed_ceph_xattr(name)) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ CInode* cur = rdlock_path_pin_ref(mdr, true);
+ if (!cur)
+ return;
+
+ if (mdr->snapid != CEPH_NOSNAP) {
+ respond_to_request(mdr, -CEPHFS_EROFS);
+ return;
+ }
+
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&cur->xattrlock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+
+ auto handler = Server::get_xattr_or_default_handler(name);
+ bufferlist bl;
+ XattrOp xattr_op(CEPH_MDS_OP_RMXATTR, name, bl, 0);
+
+ const auto& pxattrs = cur->get_projected_xattrs();
+ int r = std::invoke(handler->validate, this, cur, pxattrs, &xattr_op);
+ if (r < 0) {
+ respond_to_request(mdr, r);
+ return;
+ }
+
+ dout(10) << "removexattr '" << name << "' on " << *cur << dendl;
+
+ // project update
+ auto pi = cur->project_inode(mdr, true);
+ pi.inode->version = cur->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+ pi.inode->xattr_version++;
+ std::invoke(handler->removexattr, this, cur, pi.xattrs, xattr_op);
+
+ // log + wait
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "removexattr");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, cur, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, cur);
+
+ journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+}
+
+void Server::handle_client_getvxattr(MDRequestRef& mdr)
+{
+ const auto& req = mdr->client_request;
+ string xattr_name{req->get_path2()};
+
+ // is a ceph virtual xattr?
+ if (!is_ceph_vxattr(xattr_name)) {
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+ return;
+ }
+
+ CInode *cur = rdlock_path_pin_ref(mdr, true, false);
+ if (!cur) {
+ return;
+ }
+
+ if (is_ceph_dir_vxattr(xattr_name)) {
+ if (!cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+ return;
+ }
+ } else if (is_ceph_file_vxattr(xattr_name)) {
+ if (cur->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENODATA);
+ return;
+ }
+ }
+
+ CachedStackStringStream css;
+ int r = 0;
+ ceph::bufferlist bl;
+ // handle these vxattrs
+ if ((xattr_name.substr(0, 15) == "ceph.dir.layout"sv) ||
+ (xattr_name.substr(0, 16) == "ceph.file.layout"sv)) {
+ std::string layout_field;
+
+ struct layout_xattr_info_t {
+ enum class InheritanceStatus : uint32_t {
+ DEFAULT = 0,
+ SET = 1,
+ INHERITED = 2
+ };
+
+ const file_layout_t layout;
+ const InheritanceStatus status;
+
+ layout_xattr_info_t(const file_layout_t& l, InheritanceStatus inh)
+ : layout(l), status(inh) { }
+
+ static std::string status_to_string(InheritanceStatus status) {
+ switch (status) {
+ case InheritanceStatus::DEFAULT: return "default"s;
+ case InheritanceStatus::SET: return "set"s;
+ case InheritanceStatus::INHERITED: return "inherited"s;
+ default: return "unknown"s;
+ }
+ }
+ };
+
+ auto is_default_layout = [&](const file_layout_t& layout) -> bool {
+ return (layout == mdcache->default_file_layout);
+ };
+ auto get_inherited_layout = [&](CInode *cur) -> layout_xattr_info_t {
+ auto orig_in = cur;
+
+ while (cur) {
+ if (cur->get_projected_inode()->has_layout()) {
+ auto& curr_layout = cur->get_projected_inode()->layout;
+ if (is_default_layout(curr_layout)) {
+ return {curr_layout, layout_xattr_info_t::InheritanceStatus::DEFAULT};
+ }
+ if (cur == orig_in) {
+ // we've found a new layout at this inode
+ return {curr_layout, layout_xattr_info_t::InheritanceStatus::SET};
+ } else {
+ return {curr_layout, layout_xattr_info_t::InheritanceStatus::INHERITED};
+ }
+ }
+
+ if (cur->is_root()) {
+ break;
+ }
+
+ cur = cur->get_projected_parent_dir()->get_inode();
+ }
+ mds->clog->error() << "no layout found at root dir!";
+ ceph_abort("no layout found at root dir! something is really messed up with layouts!");
+ };
+
+ if (xattr_name == "ceph.dir.layout.json"sv ||
+ xattr_name == "ceph.file.layout.json"sv) {
+ // fetch layout only for valid xattr_name
+ const auto lxi = get_inherited_layout(cur);
+
+ *css << "{\"stripe_unit\": " << lxi.layout.stripe_unit
+ << ", \"stripe_count\": " << lxi.layout.stripe_count
+ << ", \"object_size\": " << lxi.layout.object_size
+ << ", \"pool_name\": ";
+ mds->objecter->with_osdmap([lxi, &css](const OSDMap& o) {
+ *css << "\"";
+ if (o.have_pg_pool(lxi.layout.pool_id)) {
+ *css << o.get_pool_name(lxi.layout.pool_id);
+ }
+ *css << "\"";
+ });
+ *css << ", \"pool_id\": " << (uint64_t)lxi.layout.pool_id;
+ *css << ", \"pool_namespace\": \"" << lxi.layout.pool_ns << "\"";
+ *css << ", \"inheritance\": \"@"
+ << layout_xattr_info_t::status_to_string(lxi.status) << "\"}";
+ } else if ((xattr_name == "ceph.dir.layout.pool_name"sv) ||
+ (xattr_name == "ceph.file.layout.pool_name"sv)) {
+ // fetch layout only for valid xattr_name
+ const auto lxi = get_inherited_layout(cur);
+ mds->objecter->with_osdmap([lxi, &css](const OSDMap& o) {
+ if (o.have_pg_pool(lxi.layout.pool_id)) {
+ *css << o.get_pool_name(lxi.layout.pool_id);
+ }
+ });
+ } else if ((xattr_name == "ceph.dir.layout.pool_id"sv) ||
+ (xattr_name == "ceph.file.layout.pool_id"sv)) {
+ // fetch layout only for valid xattr_name
+ const auto lxi = get_inherited_layout(cur);
+ *css << (uint64_t)lxi.layout.pool_id;
+ } else {
+ r = -CEPHFS_ENODATA; // no such attribute
+ }
+ } else if (xattr_name.substr(0, 12) == "ceph.dir.pin"sv) {
+ if (xattr_name == "ceph.dir.pin"sv) {
+ *css << cur->get_projected_inode()->export_pin;
+ } else if (xattr_name == "ceph.dir.pin.random"sv) {
+ *css << cur->get_projected_inode()->export_ephemeral_random_pin;
+ } else if (xattr_name == "ceph.dir.pin.distributed"sv) {
+ *css << cur->get_projected_inode()->export_ephemeral_distributed_pin;
+ } else {
+ // otherwise respond as invalid request
+ // since we only handle ceph vxattrs here
+ r = -CEPHFS_ENODATA; // no such attribute
+ }
+ } else {
+ // otherwise respond as invalid request
+ // since we only handle ceph vxattrs here
+ r = -CEPHFS_ENODATA; // no such attribute
+ }
+
+ if (r == 0) {
+ ENCODE_START(1, 1, bl);
+ encode(css->strv(), bl);
+ ENCODE_FINISH(bl);
+ mdr->reply_extra_bl = bl;
+ }
+
+ respond_to_request(mdr, r);
+}
+
+// =================================================================
+// DIRECTORY and NAMESPACE OPS
+
+
+// ------------------------------------------------
+
+// MKNOD
+
+class C_MDS_mknod_finish : public ServerLogContext {
+ CDentry *dn;
+ CInode *newi;
+public:
+ C_MDS_mknod_finish(Server *s, MDRequestRef& r, CDentry *d, CInode *ni) :
+ ServerLogContext(s, r), dn(d), newi(ni) {}
+ void finish(int r) override {
+ ceph_assert(r == 0);
+
+ // crash current MDS and the replacing MDS will test the journal
+ ceph_assert(!g_conf()->mds_kill_skip_replaying_inotable);
+
+ // link the inode
+ dn->pop_projected_linkage();
+
+ // be a bit hacky with the inode version, here.. we decrement it
+ // just to keep mark_dirty() happen. (we didn't bother projecting
+ // a new version of hte inode since it's just been created)
+ newi->mark_dirty(mdr->ls);
+ newi->mark_dirty_parent(mdr->ls, true);
+
+ // mkdir?
+ if (newi->is_dir()) {
+ CDir *dir = newi->get_dirfrag(frag_t());
+ ceph_assert(dir);
+ dir->mark_dirty(mdr->ls);
+ dir->mark_new(mdr->ls);
+ }
+
+ mdr->apply();
+
+ MDRequestRef null_ref;
+ get_mds()->mdcache->send_dentry_link(dn, null_ref);
+
+ if (newi->is_file()) {
+ get_mds()->locker->share_inode_max_size(newi);
+ } else if (newi->is_dir()) {
+ // We do this now so that the linkages on the new directory are stable.
+ newi->maybe_ephemeral_rand();
+ }
+
+ // hit pop
+ get_mds()->balancer->hit_inode(newi, META_POP_IWR);
+
+ // reply
+ server->respond_to_request(mdr, 0);
+ }
+};
+
+
+void Server::handle_client_mknod(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ client_t client = mdr->get_client();
+
+ unsigned mode = req->head.args.mknod.mode;
+ if ((mode & S_IFMT) == 0)
+ mode |= S_IFREG;
+
+ mdr->disable_lock_cache();
+ CDentry *dn = rdlock_path_xlock_dentry(mdr, true, false, false, S_ISREG(mode));
+ if (!dn)
+ return;
+
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+ if (!check_access(mdr, diri, MAY_WRITE))
+ return;
+ if (!check_fragment_space(mdr, dir))
+ return;
+ if (!check_dir_max_entries(mdr, dir))
+ return;
+
+ ceph_assert(dn->get_projected_linkage()->is_null());
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ return;
+ }
+ dn->set_alternate_name(req->get_alternate_name());
+
+ // set layout
+ file_layout_t layout;
+ if (mdr->dir_layout != file_layout_t())
+ layout = mdr->dir_layout;
+ else
+ layout = mdcache->default_file_layout;
+
+ CInode *newi = prepare_new_inode(mdr, dn->get_dir(), inodeno_t(req->head.ino), mode, &layout);
+ ceph_assert(newi);
+
+ dn->push_projected_linkage(newi);
+
+ auto _inode = newi->_get_inode();
+ _inode->version = dn->pre_dirty();
+ _inode->rdev = req->head.args.mknod.rdev;
+ _inode->rstat.rfiles = 1;
+ _inode->accounted_rstat = _inode->rstat;
+ if (layout.pool_id != mdcache->default_file_layout.pool_id)
+ _inode->add_old_pool(mdcache->default_file_layout.pool_id);
+ _inode->update_backtrace();
+
+ snapid_t follows = mdcache->get_global_snaprealm()->get_newest_seq();
+ SnapRealm *realm = dn->get_dir()->inode->find_snaprealm();
+ ceph_assert(follows >= realm->get_newest_seq());
+
+ // if the client created a _regular_ file via MKNOD, it's highly likely they'll
+ // want to write to it (e.g., if they are reexporting NFS)
+ if (S_ISREG(_inode->mode)) {
+ // issue a cap on the file
+ int cmode = CEPH_FILE_MODE_RDWR;
+ Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr, realm);
+ if (cap) {
+ cap->set_wanted(0);
+
+ // put locks in excl mode
+ newi->filelock.set_state(LOCK_EXCL);
+ newi->authlock.set_state(LOCK_EXCL);
+ newi->xattrlock.set_state(LOCK_EXCL);
+
+ dout(15) << " setting a client_range too, since this is a regular file" << dendl;
+ _inode->client_ranges[client].range.first = 0;
+ _inode->client_ranges[client].range.last = _inode->layout.stripe_unit;
+ _inode->client_ranges[client].follows = follows;
+ newi->mark_clientwriteable();
+ cap->mark_clientwriteable();
+ }
+ }
+
+ ceph_assert(dn->first == follows + 1);
+ newi->first = dn->first;
+
+ dout(10) << "mknod mode " << _inode->mode << " rdev " << _inode->rdev << dendl;
+
+ // prepare finisher
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "mknod");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ journal_allocated_inos(mdr, &le->metablob);
+
+ mdcache->predirty_journal_parents(mdr, &le->metablob, newi, dn->get_dir(),
+ PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+ le->metablob.add_primary_dentry(dn, newi, true, true, true);
+
+ journal_and_reply(mdr, newi, dn, le, new C_MDS_mknod_finish(this, mdr, dn, newi));
+ mds->balancer->maybe_fragment(dn->get_dir(), false);
+}
+
+
+
+// MKDIR
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_mkdir(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ mdr->disable_lock_cache();
+ CDentry *dn = rdlock_path_xlock_dentry(mdr, true);
+ if (!dn)
+ return;
+
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+
+ // mkdir check access
+ if (!check_access(mdr, diri, MAY_WRITE))
+ return;
+
+ if (!check_fragment_space(mdr, dir))
+ return;
+ if (!check_dir_max_entries(mdr, dir))
+ return;
+
+ ceph_assert(dn->get_projected_linkage()->is_null());
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ return;
+ }
+ dn->set_alternate_name(req->get_alternate_name());
+
+ // new inode
+ unsigned mode = req->head.args.mkdir.mode;
+ mode &= ~S_IFMT;
+ mode |= S_IFDIR;
+ CInode *newi = prepare_new_inode(mdr, dir, inodeno_t(req->head.ino), mode);
+ ceph_assert(newi);
+
+ // it's a directory.
+ dn->push_projected_linkage(newi);
+
+ auto _inode = newi->_get_inode();
+ _inode->version = dn->pre_dirty();
+ _inode->rstat.rsubdirs = 1;
+ _inode->accounted_rstat = _inode->rstat;
+ _inode->update_backtrace();
+
+ snapid_t follows = mdcache->get_global_snaprealm()->get_newest_seq();
+ SnapRealm *realm = dn->get_dir()->inode->find_snaprealm();
+ ceph_assert(follows >= realm->get_newest_seq());
+
+ dout(12) << " follows " << follows << dendl;
+ ceph_assert(dn->first == follows + 1);
+ newi->first = dn->first;
+
+ // ...and that new dir is empty.
+ CDir *newdir = newi->get_or_open_dirfrag(mdcache, frag_t());
+ newdir->state_set(CDir::STATE_CREATING);
+ newdir->mark_complete();
+ newdir->_get_fnode()->version = newdir->pre_dirty();
+
+ // prepare finisher
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "mkdir");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ journal_allocated_inos(mdr, &le->metablob);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, newi, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+ le->metablob.add_primary_dentry(dn, newi, true, true);
+ le->metablob.add_new_dir(newdir); // dirty AND complete AND new
+
+ // issue a cap on the directory
+ int cmode = CEPH_FILE_MODE_RDWR;
+ Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr, realm);
+ if (cap) {
+ cap->set_wanted(0);
+
+ // put locks in excl mode
+ newi->filelock.set_state(LOCK_EXCL);
+ newi->authlock.set_state(LOCK_EXCL);
+ newi->xattrlock.set_state(LOCK_EXCL);
+ }
+
+ // make sure this inode gets into the journal
+ le->metablob.add_opened_ino(newi->ino());
+
+ journal_and_reply(mdr, newi, dn, le, new C_MDS_mknod_finish(this, mdr, dn, newi));
+
+ // We hit_dir (via hit_inode) in our finish callback, but by then we might
+ // have overshot the split size (multiple mkdir in flight), so here is
+ // an early chance to split the dir if this mkdir makes it oversized.
+ mds->balancer->maybe_fragment(dir, false);
+}
+
+
+// SYMLINK
+
+void Server::handle_client_symlink(MDRequestRef& mdr)
+{
+ const auto& req = mdr->client_request;
+
+ mdr->disable_lock_cache();
+ CDentry *dn = rdlock_path_xlock_dentry(mdr, true);
+ if (!dn)
+ return;
+
+ CDir *dir = dn->get_dir();
+ CInode *diri = dir->get_inode();
+
+ if (!check_access(mdr, diri, MAY_WRITE))
+ return;
+ if (!check_fragment_space(mdr, dir))
+ return;
+ if (!check_dir_max_entries(mdr, dir))
+ return;
+
+ ceph_assert(dn->get_projected_linkage()->is_null());
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ }
+ dn->set_alternate_name(req->get_alternate_name());
+
+ unsigned mode = S_IFLNK | 0777;
+ CInode *newi = prepare_new_inode(mdr, dir, inodeno_t(req->head.ino), mode);
+ ceph_assert(newi);
+
+ // it's a symlink
+ dn->push_projected_linkage(newi);
+
+ newi->symlink = req->get_path2();
+ auto _inode = newi->_get_inode();
+ _inode->version = dn->pre_dirty();
+ _inode->size = newi->symlink.length();
+ _inode->rstat.rbytes = _inode->size;
+ _inode->rstat.rfiles = 1;
+ _inode->accounted_rstat = _inode->rstat;
+ _inode->update_backtrace();
+
+ newi->first = dn->first;
+
+ // prepare finisher
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "symlink");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ journal_allocated_inos(mdr, &le->metablob);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, newi, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+ le->metablob.add_primary_dentry(dn, newi, true, true);
+
+ journal_and_reply(mdr, newi, dn, le, new C_MDS_mknod_finish(this, mdr, dn, newi));
+ mds->balancer->maybe_fragment(dir, false);
+
+ // flush the journal as soon as possible
+ if (g_conf()->mds_kill_skip_replaying_inotable) {
+ mdlog->flush();
+ }
+}
+
+
+
+
+
+// LINK
+
+void Server::handle_client_link(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ dout(7) << "handle_client_link " << req->get_filepath()
+ << " to " << req->get_filepath2()
+ << dendl;
+
+ mdr->disable_lock_cache();
+
+ CDentry *destdn;
+ CInode *targeti;
+
+ if (req->get_filepath2().depth() == 0) {
+ targeti = mdcache->get_inode(req->get_filepath2().get_ino());
+ if (!targeti) {
+ dout(10) << "CEPHFS_ESTALE on path2, attempting recovery" << dendl;
+ inodeno_t ino = req->get_filepath2().get_ino();
+ mdcache->find_ino_peers(ino, new C_MDS_TryFindInode(this, mdr, mdcache, ino));
+ return;
+ }
+ mdr->pin(targeti);
+
+ if (!(mdr->locking_state & MutationImpl::SNAP2_LOCKED)) {
+ CDentry *pdn = targeti->get_projected_parent_dn();
+ if (!pdn) {
+ dout(7) << "target has no parent dn, failing..." << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (!mds->locker->try_rdlock_snap_layout(pdn->get_dir()->get_inode(), mdr, 1))
+ return;
+ mdr->locking_state |= MutationImpl::SNAP2_LOCKED;
+ }
+
+ destdn = rdlock_path_xlock_dentry(mdr, false);
+ if (!destdn)
+ return;
+ } else {
+ auto ret = rdlock_two_paths_xlock_destdn(mdr, false);
+ destdn = ret.first;
+ if (!destdn)
+ return;
+
+ if (!destdn->get_projected_linkage()->is_null()) {
+ respond_to_request(mdr, -CEPHFS_EEXIST);
+ return;
+ }
+
+ targeti = ret.second->get_projected_linkage()->get_inode();
+ }
+
+ ceph_assert(destdn->get_projected_linkage()->is_null());
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ return;
+ }
+ destdn->set_alternate_name(req->get_alternate_name());
+
+ if (targeti->is_dir()) {
+ dout(7) << "target is a dir, failing..." << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ CDir *dir = destdn->get_dir();
+ dout(7) << "handle_client_link link " << destdn->get_name() << " in " << *dir << dendl;
+ dout(7) << "target is " << *targeti << dendl;
+
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&targeti->snaplock);
+ lov.add_xlock(&targeti->linklock);
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (targeti->get_projected_inode()->nlink == 0) {
+ dout(7) << "target has no link, failing..." << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return;
+ }
+
+ if ((!mdr->has_more() || mdr->more()->witnessed.empty())) {
+ if (!check_access(mdr, targeti, MAY_WRITE))
+ return;
+
+ if (!check_access(mdr, dir->get_inode(), MAY_WRITE))
+ return;
+
+ if (!check_fragment_space(mdr, dir))
+ return;
+
+ if (!check_dir_max_entries(mdr, dir))
+ return;
+ }
+
+ CInode* target_pin = targeti->get_projected_parent_dir()->inode;
+ SnapRealm *target_realm = target_pin->find_snaprealm();
+ if (target_pin != dir->inode &&
+ target_realm->get_subvolume_ino() !=
+ dir->inode->find_snaprealm()->get_subvolume_ino() &&
+ /* The inode is temporarily located in the stray dir pending reintegration */
+ !target_pin->is_stray()) {
+ dout(7) << "target is in different subvolume, failing..." << dendl;
+ respond_to_request(mdr, -CEPHFS_EXDEV);
+ return;
+ }
+
+ // go!
+ ceph_assert(g_conf()->mds_kill_link_at != 1);
+
+ // local or remote?
+ if (targeti->is_auth())
+ _link_local(mdr, destdn, targeti, target_realm);
+ else
+ _link_remote(mdr, true, destdn, targeti);
+ mds->balancer->maybe_fragment(dir, false);
+}
+
+
+class C_MDS_link_local_finish : public ServerLogContext {
+ CDentry *dn;
+ CInode *targeti;
+ version_t dnpv;
+ version_t tipv;
+ bool adjust_realm;
+public:
+ C_MDS_link_local_finish(Server *s, MDRequestRef& r, CDentry *d, CInode *ti,
+ version_t dnpv_, version_t tipv_, bool ar) :
+ ServerLogContext(s, r), dn(d), targeti(ti),
+ dnpv(dnpv_), tipv(tipv_), adjust_realm(ar) { }
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_link_local_finish(mdr, dn, targeti, dnpv, tipv, adjust_realm);
+ }
+};
+
+
+void Server::_link_local(MDRequestRef& mdr, CDentry *dn, CInode *targeti, SnapRealm *target_realm)
+{
+ dout(10) << "_link_local " << *dn << " to " << *targeti << dendl;
+
+ mdr->ls = mdlog->get_current_segment();
+
+ // predirty NEW dentry
+ version_t dnpv = dn->pre_dirty();
+ version_t tipv = targeti->pre_dirty();
+
+ // project inode update
+ auto pi = targeti->project_inode(mdr);
+ pi.inode->nlink++;
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+ pi.inode->version = tipv;
+
+ bool adjust_realm = false;
+ if (!target_realm->get_subvolume_ino() && !targeti->is_projected_snaprealm_global()) {
+ sr_t *newsnap = targeti->project_snaprealm();
+ targeti->mark_snaprealm_global(newsnap);
+ targeti->record_snaprealm_parent_dentry(newsnap, target_realm, targeti->get_projected_parent_dn(), true);
+ adjust_realm = true;
+ }
+
+ // log + wait
+ EUpdate *le = new EUpdate(mdlog, "link_local");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(mdr->reqid, mdr->client_request->get_oldest_client_tid());
+ mdcache->predirty_journal_parents(mdr, &le->metablob, targeti, dn->get_dir(), PREDIRTY_DIR, 1); // new dn
+ mdcache->predirty_journal_parents(mdr, &le->metablob, targeti, 0, PREDIRTY_PRIMARY); // targeti
+ le->metablob.add_remote_dentry(dn, true, targeti->ino(), targeti->d_type()); // new remote
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, targeti);
+
+ // do this after predirty_*, to avoid funky extra dnl arg
+ dn->push_projected_linkage(targeti->ino(), targeti->d_type());
+
+ journal_and_reply(mdr, targeti, dn, le,
+ new C_MDS_link_local_finish(this, mdr, dn, targeti, dnpv, tipv, adjust_realm));
+}
+
+void Server::_link_local_finish(MDRequestRef& mdr, CDentry *dn, CInode *targeti,
+ version_t dnpv, version_t tipv, bool adjust_realm)
+{
+ dout(10) << "_link_local_finish " << *dn << " to " << *targeti << dendl;
+
+ // link and unlock the NEW dentry
+ CDentry::linkage_t *dnl = dn->pop_projected_linkage();
+ if (!dnl->get_inode())
+ dn->link_remote(dnl, targeti);
+ dn->mark_dirty(dnpv, mdr->ls);
+
+ // target inode
+ mdr->apply();
+
+ MDRequestRef null_ref;
+ mdcache->send_dentry_link(dn, null_ref);
+
+ if (adjust_realm) {
+ int op = CEPH_SNAP_OP_SPLIT;
+ mds->mdcache->send_snap_update(targeti, 0, op);
+ mds->mdcache->do_realm_invalidate_and_update_notify(targeti, op);
+ }
+
+ // bump target popularity
+ mds->balancer->hit_inode(targeti, META_POP_IWR);
+ mds->balancer->hit_dir(dn->get_dir(), META_POP_IWR);
+
+ // reply
+ respond_to_request(mdr, 0);
+}
+
+
+// link / unlink remote
+
+class C_MDS_link_remote_finish : public ServerLogContext {
+ bool inc;
+ CDentry *dn;
+ CInode *targeti;
+ version_t dpv;
+public:
+ C_MDS_link_remote_finish(Server *s, MDRequestRef& r, bool i, CDentry *d, CInode *ti) :
+ ServerLogContext(s, r), inc(i), dn(d), targeti(ti),
+ dpv(d->get_projected_version()) {}
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_link_remote_finish(mdr, inc, dn, targeti, dpv);
+ }
+};
+
+void Server::_link_remote(MDRequestRef& mdr, bool inc, CDentry *dn, CInode *targeti)
+{
+ dout(10) << "_link_remote "
+ << (inc ? "link ":"unlink ")
+ << *dn << " to " << *targeti << dendl;
+
+ // 1. send LinkPrepare to dest (journal nlink++ prepare)
+ mds_rank_t linkauth = targeti->authority().first;
+ if (mdr->more()->witnessed.count(linkauth) == 0) {
+ if (mds->is_cluster_degraded() &&
+ !mds->mdsmap->is_clientreplay_or_active_or_stopping(linkauth)) {
+ dout(10) << " targeti auth mds." << linkauth << " is not active" << dendl;
+ if (mdr->more()->waiting_on_peer.empty())
+ mds->wait_for_active_peer(linkauth, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ dout(10) << " targeti auth must prepare nlink++/--" << dendl;
+ int op;
+ if (inc)
+ op = MMDSPeerRequest::OP_LINKPREP;
+ else
+ op = MMDSPeerRequest::OP_UNLINKPREP;
+ auto req = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, op);
+ targeti->set_object_info(req->get_object_info());
+ req->op_stamp = mdr->get_op_stamp();
+ if (auto& desti_srnode = mdr->more()->desti_srnode)
+ encode(*desti_srnode, req->desti_snapbl);
+ mds->send_message_mds(req, linkauth);
+
+ ceph_assert(mdr->more()->waiting_on_peer.count(linkauth) == 0);
+ mdr->more()->waiting_on_peer.insert(linkauth);
+ return;
+ }
+ dout(10) << " targeti auth has prepared nlink++/--" << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 2);
+
+ if (auto& desti_srnode = mdr->more()->desti_srnode) {
+ delete desti_srnode;
+ desti_srnode = NULL;
+ }
+
+ mdr->set_mds_stamp(ceph_clock_now());
+
+ // add to event
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, inc ? "link_remote":"unlink_remote");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(mdr->reqid, mdr->client_request->get_oldest_client_tid());
+ if (!mdr->more()->witnessed.empty()) {
+ dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
+ le->reqid = mdr->reqid;
+ le->had_peers = true;
+ mdcache->add_uncommitted_leader(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+ }
+
+ if (inc) {
+ dn->pre_dirty();
+ mdcache->predirty_journal_parents(mdr, &le->metablob, targeti, dn->get_dir(), PREDIRTY_DIR, 1);
+ le->metablob.add_remote_dentry(dn, true, targeti->ino(), targeti->d_type()); // new remote
+ dn->push_projected_linkage(targeti->ino(), targeti->d_type());
+ } else {
+ dn->pre_dirty();
+ mdcache->predirty_journal_parents(mdr, &le->metablob, targeti, dn->get_dir(), PREDIRTY_DIR, -1);
+ mdcache->journal_cow_dentry(mdr.get(), &le->metablob, dn);
+ le->metablob.add_null_dentry(dn, true);
+ dn->push_projected_linkage();
+ }
+
+ journal_and_reply(mdr, (inc ? targeti : nullptr), dn, le,
+ new C_MDS_link_remote_finish(this, mdr, inc, dn, targeti));
+}
+
+void Server::_link_remote_finish(MDRequestRef& mdr, bool inc,
+ CDentry *dn, CInode *targeti,
+ version_t dpv)
+{
+ dout(10) << "_link_remote_finish "
+ << (inc ? "link ":"unlink ")
+ << *dn << " to " << *targeti << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 3);
+
+ if (!mdr->more()->witnessed.empty())
+ mdcache->logged_leader_update(mdr->reqid);
+
+ if (inc) {
+ // link the new dentry
+ CDentry::linkage_t *dnl = dn->pop_projected_linkage();
+ if (!dnl->get_inode())
+ dn->link_remote(dnl, targeti);
+ dn->mark_dirty(dpv, mdr->ls);
+ } else {
+ // unlink main dentry
+ dn->get_dir()->unlink_inode(dn);
+ dn->pop_projected_linkage();
+ dn->mark_dirty(dn->get_projected_version(), mdr->ls); // dirty old dentry
+ }
+
+ mdr->apply();
+
+ MDRequestRef null_ref;
+ if (inc)
+ mdcache->send_dentry_link(dn, null_ref);
+ else
+ mdcache->send_dentry_unlink(dn, NULL, null_ref);
+
+ // bump target popularity
+ mds->balancer->hit_inode(targeti, META_POP_IWR);
+ mds->balancer->hit_dir(dn->get_dir(), META_POP_IWR);
+
+ // reply
+ respond_to_request(mdr, 0);
+
+ if (!inc)
+ // removing a new dn?
+ dn->get_dir()->try_remove_unlinked_dn(dn);
+}
+
+
+// remote linking/unlinking
+
+class C_MDS_PeerLinkPrep : public ServerLogContext {
+ CInode *targeti;
+ bool adjust_realm;
+public:
+ C_MDS_PeerLinkPrep(Server *s, MDRequestRef& r, CInode *t, bool ar) :
+ ServerLogContext(s, r), targeti(t), adjust_realm(ar) { }
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_logged_peer_link(mdr, targeti, adjust_realm);
+ }
+};
+
+class C_MDS_PeerLinkCommit : public ServerContext {
+ MDRequestRef mdr;
+ CInode *targeti;
+public:
+ C_MDS_PeerLinkCommit(Server *s, MDRequestRef& r, CInode *t) :
+ ServerContext(s), mdr(r), targeti(t) { }
+ void finish(int r) override {
+ server->_commit_peer_link(mdr, r, targeti);
+ }
+};
+
+void Server::handle_peer_link_prep(MDRequestRef& mdr)
+{
+ dout(10) << "handle_peer_link_prep " << *mdr
+ << " on " << mdr->peer_request->get_object_info()
+ << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 4);
+
+ CInode *targeti = mdcache->get_inode(mdr->peer_request->get_object_info().ino);
+ ceph_assert(targeti);
+ dout(10) << "targeti " << *targeti << dendl;
+ CDentry *dn = targeti->get_parent_dn();
+ CDentry::linkage_t *dnl = dn->get_linkage();
+ ceph_assert(dnl->is_primary());
+
+ mdr->set_op_stamp(mdr->peer_request->op_stamp);
+
+ mdr->auth_pin(targeti);
+
+ //ceph_abort(); // test hack: make sure leader can handle a peer that fails to prepare...
+ ceph_assert(g_conf()->mds_kill_link_at != 5);
+
+ // journal it
+ mdr->ls = mdlog->get_current_segment();
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_link_prep", mdr->reqid, mdr->peer_to_mds,
+ EPeerUpdate::OP_PREPARE, EPeerUpdate::LINK);
+ mdlog->start_entry(le);
+
+ auto pi = dnl->get_inode()->project_inode(mdr);
+
+ // update journaled target inode
+ bool inc;
+ bool adjust_realm = false;
+ bool realm_projected = false;
+ if (mdr->peer_request->get_op() == MMDSPeerRequest::OP_LINKPREP) {
+ inc = true;
+ pi.inode->nlink++;
+
+ CDentry *target_pdn = targeti->get_projected_parent_dn();
+ SnapRealm *target_realm = target_pdn->get_dir()->inode->find_snaprealm();
+ if (!target_realm->get_subvolume_ino() && !targeti->is_projected_snaprealm_global()) {
+ sr_t *newsnap = targeti->project_snaprealm();
+ targeti->mark_snaprealm_global(newsnap);
+ targeti->record_snaprealm_parent_dentry(newsnap, target_realm, target_pdn, true);
+ adjust_realm = true;
+ realm_projected = true;
+ }
+ } else {
+ inc = false;
+ pi.inode->nlink--;
+ if (targeti->is_projected_snaprealm_global()) {
+ ceph_assert(mdr->peer_request->desti_snapbl.length());
+ auto p = mdr->peer_request->desti_snapbl.cbegin();
+
+ sr_t *newsnap = targeti->project_snaprealm();
+ decode(*newsnap, p);
+
+ if (pi.inode->nlink == 0)
+ ceph_assert(!newsnap->is_parent_global());
+
+ realm_projected = true;
+ } else {
+ ceph_assert(mdr->peer_request->desti_snapbl.length() == 0);
+ }
+ }
+
+ link_rollback rollback;
+ rollback.reqid = mdr->reqid;
+ rollback.ino = targeti->ino();
+ rollback.old_ctime = targeti->get_inode()->ctime; // we hold versionlock xlock; no concorrent projections
+ const auto& pf = targeti->get_parent_dn()->get_dir()->get_projected_fnode();
+ rollback.old_dir_mtime = pf->fragstat.mtime;
+ rollback.old_dir_rctime = pf->rstat.rctime;
+ rollback.was_inc = inc;
+ if (realm_projected) {
+ if (targeti->snaprealm) {
+ encode(true, rollback.snapbl);
+ targeti->encode_snap_blob(rollback.snapbl);
+ } else {
+ encode(false, rollback.snapbl);
+ }
+ }
+ encode(rollback, le->rollback);
+ mdr->more()->rollback_bl = le->rollback;
+
+ pi.inode->ctime = mdr->get_op_stamp();
+ pi.inode->version = targeti->pre_dirty();
+
+ dout(10) << " projected inode " << pi.inode->ino << " v " << pi.inode->version << dendl;
+
+ // commit case
+ mdcache->predirty_journal_parents(mdr, &le->commit, dnl->get_inode(), 0, PREDIRTY_SHALLOW|PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->commit, targeti);
+ mdcache->add_uncommitted_peer(mdr->reqid, mdr->ls, mdr->peer_to_mds);
+
+ // set up commit waiter
+ mdr->more()->peer_commit = new C_MDS_PeerLinkCommit(this, mdr, targeti);
+
+ mdr->more()->peer_update_journaled = true;
+ submit_mdlog_entry(le, new C_MDS_PeerLinkPrep(this, mdr, targeti, adjust_realm),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_logged_peer_link(MDRequestRef& mdr, CInode *targeti, bool adjust_realm)
+{
+ dout(10) << "_logged_peer_link " << *mdr
+ << " " << *targeti << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 6);
+
+ // update the target
+ mdr->apply();
+
+ // hit pop
+ mds->balancer->hit_inode(targeti, META_POP_IWR);
+
+ // done.
+ mdr->reset_peer_request();
+
+ if (adjust_realm) {
+ int op = CEPH_SNAP_OP_SPLIT;
+ mds->mdcache->send_snap_update(targeti, 0, op);
+ mds->mdcache->do_realm_invalidate_and_update_notify(targeti, op);
+ }
+
+ // ack
+ if (!mdr->aborted) {
+ auto reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_LINKPREPACK);
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ } else {
+ dout(10) << " abort flag set, finishing" << dendl;
+ mdcache->request_finish(mdr);
+ }
+}
+
+
+struct C_MDS_CommittedPeer : public ServerLogContext {
+ C_MDS_CommittedPeer(Server *s, MDRequestRef& m) : ServerLogContext(s, m) {}
+ void finish(int r) override {
+ server->_committed_peer(mdr);
+ }
+};
+
+void Server::_commit_peer_link(MDRequestRef& mdr, int r, CInode *targeti)
+{
+ dout(10) << "_commit_peer_link " << *mdr
+ << " r=" << r
+ << " " << *targeti << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 7);
+
+ if (r == 0) {
+ // drop our pins, etc.
+ mdr->cleanup();
+
+ // write a commit to the journal
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_link_commit", mdr->reqid, mdr->peer_to_mds,
+ EPeerUpdate::OP_COMMIT, EPeerUpdate::LINK);
+ mdlog->start_entry(le);
+ submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
+ mdlog->flush();
+ } else {
+ do_link_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr);
+ }
+}
+
+void Server::_committed_peer(MDRequestRef& mdr)
+{
+ dout(10) << "_committed_peer " << *mdr << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 8);
+
+ bool assert_exist = mdr->more()->peer_update_journaled;
+ mdcache->finish_uncommitted_peer(mdr->reqid, assert_exist);
+ auto req = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_COMMITTED);
+ mds->send_message_mds(req, mdr->peer_to_mds);
+ mdcache->request_finish(mdr);
+}
+
+struct C_MDS_LoggedLinkRollback : public ServerLogContext {
+ MutationRef mut;
+ map<client_t,ref_t<MClientSnap>> splits;
+ C_MDS_LoggedLinkRollback(Server *s, MutationRef& m, MDRequestRef& r,
+ map<client_t,ref_t<MClientSnap>>&& _splits) :
+ ServerLogContext(s, r), mut(m), splits(std::move(_splits)) {
+ }
+ void finish(int r) override {
+ server->_link_rollback_finish(mut, mdr, splits);
+ }
+};
+
+void Server::do_link_rollback(bufferlist &rbl, mds_rank_t leader, MDRequestRef& mdr)
+{
+ link_rollback rollback;
+ auto p = rbl.cbegin();
+ decode(rollback, p);
+
+ dout(10) << "do_link_rollback on " << rollback.reqid
+ << (rollback.was_inc ? " inc":" dec")
+ << " ino " << rollback.ino
+ << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 9);
+
+ mdcache->add_rollback(rollback.reqid, leader); // need to finish this update before resolve finishes
+ ceph_assert(mdr || mds->is_resolve());
+
+ MutationRef mut(new MutationImpl(nullptr, utime_t(), rollback.reqid));
+ mut->ls = mds->mdlog->get_current_segment();
+
+ CInode *in = mdcache->get_inode(rollback.ino);
+ ceph_assert(in);
+ dout(10) << " target is " << *in << dendl;
+ ceph_assert(!in->is_projected()); // live peer request hold versionlock xlock.
+
+ auto pi = in->project_inode(mut);
+ pi.inode->version = in->pre_dirty();
+
+ // parent dir rctime
+ CDir *parent = in->get_projected_parent_dn()->get_dir();
+ auto pf = parent->project_fnode(mut);
+ pf->version = parent->pre_dirty();
+ if (pf->fragstat.mtime == pi.inode->ctime) {
+ pf->fragstat.mtime = rollback.old_dir_mtime;
+ if (pf->rstat.rctime == pi.inode->ctime)
+ pf->rstat.rctime = rollback.old_dir_rctime;
+ mut->add_updated_lock(&parent->get_inode()->filelock);
+ mut->add_updated_lock(&parent->get_inode()->nestlock);
+ }
+
+ // inode
+ pi.inode->ctime = rollback.old_ctime;
+ if (rollback.was_inc)
+ pi.inode->nlink--;
+ else
+ pi.inode->nlink++;
+
+ map<client_t,ref_t<MClientSnap>> splits;
+ if (rollback.snapbl.length() && in->snaprealm) {
+ bool hadrealm;
+ auto p = rollback.snapbl.cbegin();
+ decode(hadrealm, p);
+ if (hadrealm) {
+ if (!mds->is_resolve()) {
+ sr_t *new_srnode = new sr_t();
+ decode(*new_srnode, p);
+ in->project_snaprealm(new_srnode);
+ } else {
+ decode(in->snaprealm->srnode, p);
+ }
+ } else {
+ SnapRealm *realm = parent->get_inode()->find_snaprealm();
+ if (!mds->is_resolve())
+ mdcache->prepare_realm_merge(in->snaprealm, realm, splits);
+ in->project_snaprealm(NULL);
+ }
+ }
+
+ // journal it
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_link_rollback", rollback.reqid, leader,
+ EPeerUpdate::OP_ROLLBACK, EPeerUpdate::LINK);
+ mdlog->start_entry(le);
+ le->commit.add_dir_context(parent);
+ le->commit.add_dir(parent, true);
+ le->commit.add_primary_dentry(in->get_projected_parent_dn(), 0, true);
+
+ submit_mdlog_entry(le, new C_MDS_LoggedLinkRollback(this, mut, mdr, std::move(splits)),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_link_rollback_finish(MutationRef& mut, MDRequestRef& mdr,
+ map<client_t,ref_t<MClientSnap>>& splits)
+{
+ dout(10) << "_link_rollback_finish" << dendl;
+
+ ceph_assert(g_conf()->mds_kill_link_at != 10);
+
+ mut->apply();
+
+ if (!mds->is_resolve())
+ mdcache->send_snaps(splits);
+
+ if (mdr)
+ mdcache->request_finish(mdr);
+
+ mdcache->finish_rollback(mut->reqid, mdr);
+
+ mut->cleanup();
+}
+
+
+void Server::handle_peer_link_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &m)
+{
+ dout(10) << "handle_peer_link_prep_ack " << *mdr
+ << " " << *m << dendl;
+ mds_rank_t from = mds_rank_t(m->get_source().num());
+
+ ceph_assert(g_conf()->mds_kill_link_at != 11);
+
+ // note peer
+ mdr->more()->peers.insert(from);
+
+ // witnessed!
+ ceph_assert(mdr->more()->witnessed.count(from) == 0);
+ mdr->more()->witnessed.insert(from);
+ ceph_assert(!m->is_not_journaled());
+ mdr->more()->has_journaled_peers = true;
+
+ // remove from waiting list
+ ceph_assert(mdr->more()->waiting_on_peer.count(from));
+ mdr->more()->waiting_on_peer.erase(from);
+
+ ceph_assert(mdr->more()->waiting_on_peer.empty());
+
+ dispatch_client_request(mdr); // go again!
+}
+
+
+
+
+
+// UNLINK
+
+void Server::handle_client_unlink(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ client_t client = mdr->get_client();
+
+ // rmdir or unlink?
+ bool rmdir = (req->get_op() == CEPH_MDS_OP_RMDIR);
+
+ if (rmdir)
+ mdr->disable_lock_cache();
+ CDentry *dn = rdlock_path_xlock_dentry(mdr, false, true);
+ if (!dn)
+ return;
+
+ CDentry::linkage_t *dnl = dn->get_linkage(client, mdr);
+ ceph_assert(!dnl->is_null());
+ CInode *in = dnl->get_inode();
+
+ if (rmdir) {
+ dout(7) << "handle_client_rmdir on " << *dn << dendl;
+ } else {
+ dout(7) << "handle_client_unlink on " << *dn << dendl;
+ }
+ dout(7) << "dn links to " << *in << dendl;
+
+ // rmdir vs is_dir
+ if (in->is_dir()) {
+ if (rmdir) {
+ // do empty directory checks
+ if (_dir_is_nonempty_unlocked(mdr, in)) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+ } else {
+ dout(7) << "handle_client_unlink on dir " << *in << ", returning error" << dendl;
+ respond_to_request(mdr, -CEPHFS_EISDIR);
+ return;
+ }
+ } else {
+ if (rmdir) {
+ // unlink
+ dout(7) << "handle_client_rmdir on non-dir " << *in << ", returning error" << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+ }
+
+ CInode *diri = dn->get_dir()->get_inode();
+ if ((!mdr->has_more() || mdr->more()->witnessed.empty())) {
+ if (!check_access(mdr, diri, MAY_WRITE))
+ return;
+ }
+
+ // -- create stray dentry? --
+ CDentry *straydn = NULL;
+ if (dnl->is_primary()) {
+ straydn = prepare_stray_dentry(mdr, dnl->get_inode());
+ if (!straydn)
+ return;
+ dout(10) << " straydn is " << *straydn << dendl;
+ } else if (mdr->straydn) {
+ mdr->unpin(mdr->straydn);
+ mdr->straydn = NULL;
+ }
+
+ // lock
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+
+ lov.add_xlock(&in->linklock);
+ lov.add_xlock(&in->snaplock);
+ if (in->is_dir())
+ lov.add_rdlock(&in->filelock); // to verify it's empty
+
+ if (straydn) {
+ lov.add_wrlock(&straydn->get_dir()->inode->filelock);
+ lov.add_wrlock(&straydn->get_dir()->inode->nestlock);
+ lov.add_xlock(&straydn->lock);
+ }
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (in->is_dir() &&
+ _dir_is_nonempty(mdr, in)) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+
+ if (straydn)
+ straydn->first = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+
+ if (!mdr->more()->desti_srnode) {
+ if (in->is_projected_snaprealm_global()) {
+ sr_t *new_srnode = in->prepare_new_srnode(0);
+ in->record_snaprealm_parent_dentry(new_srnode, nullptr, dn, dnl->is_primary());
+ // dropping the last linkage or dropping the last remote linkage,
+ // detch the inode from global snaprealm
+ auto nlink = in->get_projected_inode()->nlink;
+ if (nlink == 1 ||
+ (nlink == 2 && !dnl->is_primary() &&
+ !in->get_projected_parent_dir()->inode->is_stray()))
+ in->clear_snaprealm_global(new_srnode);
+ mdr->more()->desti_srnode = new_srnode;
+ } else if (dnl->is_primary()) {
+ // prepare snaprealm blob for peer request
+ SnapRealm *realm = in->find_snaprealm();
+ snapid_t follows = realm->get_newest_seq();
+ if (in->snaprealm || follows + 1 > in->get_oldest_snap()) {
+ sr_t *new_srnode = in->prepare_new_srnode(follows);
+ in->record_snaprealm_past_parent(new_srnode, straydn->get_dir()->inode->find_snaprealm());
+ mdr->more()->desti_srnode = new_srnode;
+ }
+ }
+ }
+
+ // yay!
+ if (in->is_dir() && in->has_subtree_root_dirfrag()) {
+ // subtree root auths need to be witnesses
+ set<mds_rank_t> witnesses;
+ in->list_replicas(witnesses);
+ dout(10) << " witnesses " << witnesses << ", have " << mdr->more()->witnessed << dendl;
+
+ for (set<mds_rank_t>::iterator p = witnesses.begin();
+ p != witnesses.end();
+ ++p) {
+ if (mdr->more()->witnessed.count(*p)) {
+ dout(10) << " already witnessed by mds." << *p << dendl;
+ } else if (mdr->more()->waiting_on_peer.count(*p)) {
+ dout(10) << " already waiting on witness mds." << *p << dendl;
+ } else {
+ if (!_rmdir_prepare_witness(mdr, *p, mdr->dn[0], straydn))
+ return;
+ }
+ }
+ if (!mdr->more()->waiting_on_peer.empty())
+ return; // we're waiting for a witness.
+ }
+
+ if (!rmdir && dnl->is_primary() && mdr->dn[0].size() == 1)
+ mds->locker->create_lock_cache(mdr, diri);
+
+ // ok!
+ if (dnl->is_remote() && !dnl->get_inode()->is_auth())
+ _link_remote(mdr, false, dn, dnl->get_inode());
+ else
+ _unlink_local(mdr, dn, straydn);
+}
+
+class C_MDS_unlink_local_finish : public ServerLogContext {
+ CDentry *dn;
+ CDentry *straydn;
+ version_t dnpv; // deleted dentry
+public:
+ C_MDS_unlink_local_finish(Server *s, MDRequestRef& r, CDentry *d, CDentry *sd) :
+ ServerLogContext(s, r), dn(d), straydn(sd),
+ dnpv(d->get_projected_version()) {}
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_unlink_local_finish(mdr, dn, straydn, dnpv);
+ }
+};
+
+void Server::_unlink_local(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
+{
+ dout(10) << "_unlink_local " << *dn << dendl;
+
+ CDentry::linkage_t *dnl = dn->get_projected_linkage();
+ CInode *in = dnl->get_inode();
+
+
+ // ok, let's do it.
+ mdr->ls = mdlog->get_current_segment();
+
+ // prepare log entry
+ EUpdate *le = new EUpdate(mdlog, "unlink_local");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(mdr->reqid, mdr->client_request->get_oldest_client_tid());
+ if (!mdr->more()->witnessed.empty()) {
+ dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
+ le->reqid = mdr->reqid;
+ le->had_peers = true;
+ mdcache->add_uncommitted_leader(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+ }
+
+ if (straydn) {
+ ceph_assert(dnl->is_primary());
+ straydn->push_projected_linkage(in);
+ }
+
+ // the unlinked dentry
+ dn->pre_dirty();
+
+ auto pi = in->project_inode(mdr);
+ {
+ std::string t;
+ dn->make_path_string(t, true);
+ pi.inode->stray_prior_path = std::move(t);
+ }
+ pi.inode->version = in->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->change_attr++;
+ pi.inode->nlink--;
+ if (pi.inode->nlink == 0)
+ in->state_set(CInode::STATE_ORPHAN);
+
+ if (mdr->more()->desti_srnode) {
+ auto& desti_srnode = mdr->more()->desti_srnode;
+ in->project_snaprealm(desti_srnode);
+ desti_srnode = NULL;
+ }
+
+ if (straydn) {
+ // will manually pop projected inode
+
+ // primary link. add stray dentry.
+ mdcache->predirty_journal_parents(mdr, &le->metablob, in, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, -1);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, in, straydn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+
+ pi.inode->update_backtrace();
+ le->metablob.add_primary_dentry(straydn, in, true, true);
+ } else {
+ // remote link. update remote inode.
+ mdcache->predirty_journal_parents(mdr, &le->metablob, in, dn->get_dir(), PREDIRTY_DIR, -1);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, in, 0, PREDIRTY_PRIMARY);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, in);
+ }
+
+ mdcache->journal_cow_dentry(mdr.get(), &le->metablob, dn);
+ le->metablob.add_null_dentry(dn, true);
+
+ if (in->is_dir()) {
+ dout(10) << " noting renamed (unlinked) dir ino " << in->ino() << " in metablob" << dendl;
+ le->metablob.renamed_dirino = in->ino();
+ }
+
+ dn->push_projected_linkage();
+
+ if (straydn) {
+ ceph_assert(in->first <= straydn->first);
+ in->first = straydn->first;
+ }
+
+ if (in->is_dir()) {
+ ceph_assert(straydn);
+ mdcache->project_subtree_rename(in, dn->get_dir(), straydn->get_dir());
+ }
+
+ journal_and_reply(mdr, 0, dn, le, new C_MDS_unlink_local_finish(this, mdr, dn, straydn));
+}
+
+void Server::_unlink_local_finish(MDRequestRef& mdr,
+ CDentry *dn, CDentry *straydn,
+ version_t dnpv)
+{
+ dout(10) << "_unlink_local_finish " << *dn << dendl;
+
+ if (!mdr->more()->witnessed.empty())
+ mdcache->logged_leader_update(mdr->reqid);
+
+ CInode *strayin = NULL;
+ bool hadrealm = false;
+ if (straydn) {
+ // if there is newly created snaprealm, need to split old snaprealm's
+ // inodes_with_caps. So pop snaprealm before linkage changes.
+ strayin = dn->get_linkage()->get_inode();
+ hadrealm = strayin->snaprealm ? true : false;
+ strayin->early_pop_projected_snaprealm();
+ }
+
+ // unlink main dentry
+ dn->get_dir()->unlink_inode(dn);
+ dn->pop_projected_linkage();
+ dn->mark_dirty(dnpv, mdr->ls);
+
+ // relink as stray? (i.e. was primary link?)
+ if (straydn) {
+ dout(20) << " straydn is " << *straydn << dendl;
+ straydn->pop_projected_linkage();
+ mdcache->touch_dentry_bottom(straydn);
+ }
+
+ mdr->apply();
+
+ mdcache->send_dentry_unlink(dn, straydn, mdr);
+
+ if (straydn) {
+ // update subtree map?
+ if (strayin->is_dir())
+ mdcache->adjust_subtree_after_rename(strayin, dn->get_dir(), true);
+
+ if (strayin->snaprealm && !hadrealm)
+ mdcache->do_realm_invalidate_and_update_notify(strayin, CEPH_SNAP_OP_SPLIT, false);
+ }
+
+ // bump pop
+ mds->balancer->hit_dir(dn->get_dir(), META_POP_IWR);
+
+ // reply
+ respond_to_request(mdr, 0);
+
+ // removing a new dn?
+ dn->get_dir()->try_remove_unlinked_dn(dn);
+
+ // clean up ?
+ // respond_to_request() drops locks. So stray reintegration can race with us.
+ if (straydn && !straydn->get_projected_linkage()->is_null()) {
+ // Tip off the MDCache that this dentry is a stray that
+ // might be elegible for purge.
+ mdcache->notify_stray(straydn);
+ }
+}
+
+bool Server::_rmdir_prepare_witness(MDRequestRef& mdr, mds_rank_t who, vector<CDentry*>& trace, CDentry *straydn)
+{
+ if (mds->is_cluster_degraded() &&
+ !mds->mdsmap->is_clientreplay_or_active_or_stopping(who)) {
+ dout(10) << "_rmdir_prepare_witness mds." << who << " is not active" << dendl;
+ if (mdr->more()->waiting_on_peer.empty())
+ mds->wait_for_active_peer(who, new C_MDS_RetryRequest(mdcache, mdr));
+ return false;
+ }
+
+ dout(10) << "_rmdir_prepare_witness mds." << who << dendl;
+ auto req = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RMDIRPREP);
+ req->srcdnpath = filepath(trace.front()->get_dir()->ino());
+ for (auto dn : trace)
+ req->srcdnpath.push_dentry(dn->get_name());
+ mdcache->encode_replica_stray(straydn, who, req->straybl);
+ if (mdr->more()->desti_srnode)
+ encode(*mdr->more()->desti_srnode, req->desti_snapbl);
+
+ req->op_stamp = mdr->get_op_stamp();
+ mds->send_message_mds(req, who);
+
+ ceph_assert(mdr->more()->waiting_on_peer.count(who) == 0);
+ mdr->more()->waiting_on_peer.insert(who);
+ return true;
+}
+
+struct C_MDS_PeerRmdirPrep : public ServerLogContext {
+ CDentry *dn, *straydn;
+ C_MDS_PeerRmdirPrep(Server *s, MDRequestRef& r, CDentry *d, CDentry *st)
+ : ServerLogContext(s, r), dn(d), straydn(st) {}
+ void finish(int r) override {
+ server->_logged_peer_rmdir(mdr, dn, straydn);
+ }
+};
+
+struct C_MDS_PeerRmdirCommit : public ServerContext {
+ MDRequestRef mdr;
+ CDentry *straydn;
+ C_MDS_PeerRmdirCommit(Server *s, MDRequestRef& r, CDentry *sd)
+ : ServerContext(s), mdr(r), straydn(sd) { }
+ void finish(int r) override {
+ server->_commit_peer_rmdir(mdr, r, straydn);
+ }
+};
+
+void Server::handle_peer_rmdir_prep(MDRequestRef& mdr)
+{
+ dout(10) << "handle_peer_rmdir_prep " << *mdr
+ << " " << mdr->peer_request->srcdnpath
+ << " to " << mdr->peer_request->destdnpath
+ << dendl;
+
+ vector<CDentry*> trace;
+ filepath srcpath(mdr->peer_request->srcdnpath);
+ dout(10) << " src " << srcpath << dendl;
+ CInode *in;
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, false);
+ int r = mdcache->path_traverse(mdr, cf, srcpath,
+ MDS_TRAVERSE_DISCOVER | MDS_TRAVERSE_PATH_LOCKED,
+ &trace, &in);
+ if (r > 0) return;
+ if (r == -CEPHFS_ESTALE) {
+ mdcache->find_ino_peers(srcpath.get_ino(), new C_MDS_RetryRequest(mdcache, mdr),
+ mdr->peer_to_mds, true);
+ return;
+ }
+ ceph_assert(r == 0);
+ CDentry *dn = trace.back();
+ dout(10) << " dn " << *dn << dendl;
+ mdr->pin(dn);
+
+ ceph_assert(mdr->straydn);
+ CDentry *straydn = mdr->straydn;
+ dout(10) << " straydn " << *straydn << dendl;
+
+ mdr->set_op_stamp(mdr->peer_request->op_stamp);
+
+ rmdir_rollback rollback;
+ rollback.reqid = mdr->reqid;
+ rollback.src_dir = dn->get_dir()->dirfrag();
+ rollback.src_dname = dn->get_name();
+ rollback.dest_dir = straydn->get_dir()->dirfrag();
+ rollback.dest_dname = straydn->get_name();
+ if (mdr->peer_request->desti_snapbl.length()) {
+ if (in->snaprealm) {
+ encode(true, rollback.snapbl);
+ in->encode_snap_blob(rollback.snapbl);
+ } else {
+ encode(false, rollback.snapbl);
+ }
+ }
+ encode(rollback, mdr->more()->rollback_bl);
+ // FIXME: rollback snaprealm
+ dout(20) << " rollback is " << mdr->more()->rollback_bl.length() << " bytes" << dendl;
+
+ // set up commit waiter
+ mdr->more()->peer_commit = new C_MDS_PeerRmdirCommit(this, mdr, straydn);
+
+ straydn->push_projected_linkage(in);
+ dn->push_projected_linkage();
+
+ ceph_assert(straydn->first >= in->first);
+ in->first = straydn->first;
+
+ if (!in->has_subtree_root_dirfrag(mds->get_nodeid())) {
+ dout(10) << " no auth subtree in " << *in << ", skipping journal" << dendl;
+ _logged_peer_rmdir(mdr, dn, straydn);
+ return;
+ }
+
+ mdr->ls = mdlog->get_current_segment();
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rmdir", mdr->reqid, mdr->peer_to_mds,
+ EPeerUpdate::OP_PREPARE, EPeerUpdate::RMDIR);
+ mdlog->start_entry(le);
+ le->rollback = mdr->more()->rollback_bl;
+
+ le->commit.add_dir_context(straydn->get_dir());
+ le->commit.add_primary_dentry(straydn, in, true);
+ // peer: no need to journal original dentry
+
+ dout(10) << " noting renamed (unlinked) dir ino " << in->ino() << " in metablob" << dendl;
+ le->commit.renamed_dirino = in->ino();
+
+ mdcache->project_subtree_rename(in, dn->get_dir(), straydn->get_dir());
+ mdcache->add_uncommitted_peer(mdr->reqid, mdr->ls, mdr->peer_to_mds);
+
+ mdr->more()->peer_update_journaled = true;
+ submit_mdlog_entry(le, new C_MDS_PeerRmdirPrep(this, mdr, dn, straydn),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_logged_peer_rmdir(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
+{
+ dout(10) << "_logged_peer_rmdir " << *mdr << " on " << *dn << dendl;
+ CInode *in = dn->get_linkage()->get_inode();
+
+ bool new_realm;
+ if (mdr->peer_request->desti_snapbl.length()) {
+ new_realm = !in->snaprealm;
+ in->decode_snap_blob(mdr->peer_request->desti_snapbl);
+ ceph_assert(in->snaprealm);
+ } else {
+ new_realm = false;
+ }
+
+ // update our cache now, so we are consistent with what is in the journal
+ // when we journal a subtree map
+ dn->get_dir()->unlink_inode(dn);
+ straydn->pop_projected_linkage();
+ dn->pop_projected_linkage();
+
+ mdcache->adjust_subtree_after_rename(in, dn->get_dir(), mdr->more()->peer_update_journaled);
+
+ if (new_realm)
+ mdcache->do_realm_invalidate_and_update_notify(in, CEPH_SNAP_OP_SPLIT, false);
+
+ // done.
+ mdr->reset_peer_request();
+ mdr->straydn = 0;
+
+ if (!mdr->aborted) {
+ auto reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RMDIRPREPACK);
+ if (!mdr->more()->peer_update_journaled)
+ reply->mark_not_journaled();
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ } else {
+ dout(10) << " abort flag set, finishing" << dendl;
+ mdcache->request_finish(mdr);
+ }
+}
+
+void Server::handle_peer_rmdir_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
+{
+ dout(10) << "handle_peer_rmdir_prep_ack " << *mdr
+ << " " << *ack << dendl;
+
+ mds_rank_t from = mds_rank_t(ack->get_source().num());
+
+ mdr->more()->peers.insert(from);
+ mdr->more()->witnessed.insert(from);
+ if (!ack->is_not_journaled())
+ mdr->more()->has_journaled_peers = true;
+
+ // remove from waiting list
+ ceph_assert(mdr->more()->waiting_on_peer.count(from));
+ mdr->more()->waiting_on_peer.erase(from);
+
+ if (mdr->more()->waiting_on_peer.empty())
+ dispatch_client_request(mdr); // go again!
+ else
+ dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
+}
+
+void Server::_commit_peer_rmdir(MDRequestRef& mdr, int r, CDentry *straydn)
+{
+ dout(10) << "_commit_peer_rmdir " << *mdr << " r=" << r << dendl;
+
+ if (r == 0) {
+ if (mdr->more()->peer_update_journaled) {
+ CInode *strayin = straydn->get_projected_linkage()->get_inode();
+ if (strayin && !strayin->snaprealm)
+ mdcache->clear_dirty_bits_for_stray(strayin);
+ }
+
+ mdr->cleanup();
+
+ if (mdr->more()->peer_update_journaled) {
+ // write a commit to the journal
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rmdir_commit", mdr->reqid,
+ mdr->peer_to_mds, EPeerUpdate::OP_COMMIT,
+ EPeerUpdate::RMDIR);
+ mdlog->start_entry(le);
+ submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
+ mdlog->flush();
+ } else {
+ _committed_peer(mdr);
+ }
+ } else {
+ // abort
+ do_rmdir_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr);
+ }
+}
+
+struct C_MDS_LoggedRmdirRollback : public ServerLogContext {
+ metareqid_t reqid;
+ CDentry *dn;
+ CDentry *straydn;
+ C_MDS_LoggedRmdirRollback(Server *s, MDRequestRef& m, metareqid_t mr, CDentry *d, CDentry *st)
+ : ServerLogContext(s, m), reqid(mr), dn(d), straydn(st) {}
+ void finish(int r) override {
+ server->_rmdir_rollback_finish(mdr, reqid, dn, straydn);
+ }
+};
+
+void Server::do_rmdir_rollback(bufferlist &rbl, mds_rank_t leader, MDRequestRef& mdr)
+{
+ // unlink the other rollback methods, the rmdir rollback is only
+ // needed to record the subtree changes in the journal for inode
+ // replicas who are auth for empty dirfrags. no actual changes to
+ // the file system are taking place here, so there is no Mutation.
+
+ rmdir_rollback rollback;
+ auto p = rbl.cbegin();
+ decode(rollback, p);
+
+ dout(10) << "do_rmdir_rollback on " << rollback.reqid << dendl;
+ mdcache->add_rollback(rollback.reqid, leader); // need to finish this update before resolve finishes
+ ceph_assert(mdr || mds->is_resolve());
+
+ CDir *dir = mdcache->get_dirfrag(rollback.src_dir);
+ if (!dir)
+ dir = mdcache->get_dirfrag(rollback.src_dir.ino, rollback.src_dname);
+ ceph_assert(dir);
+ CDentry *dn = dir->lookup(rollback.src_dname);
+ ceph_assert(dn);
+ dout(10) << " dn " << *dn << dendl;
+ CDir *straydir = mdcache->get_dirfrag(rollback.dest_dir);
+ ceph_assert(straydir);
+ CDentry *straydn = straydir->lookup(rollback.dest_dname);
+ ceph_assert(straydn);
+ dout(10) << " straydn " << *straydn << dendl;
+ CInode *in = straydn->get_linkage()->get_inode();
+
+ dn->push_projected_linkage(in);
+ straydn->push_projected_linkage();
+
+ if (rollback.snapbl.length() && in->snaprealm) {
+ bool hadrealm;
+ auto p = rollback.snapbl.cbegin();
+ decode(hadrealm, p);
+ if (hadrealm) {
+ decode(in->snaprealm->srnode, p);
+ } else {
+ in->snaprealm->merge_to(dir->get_inode()->find_snaprealm());
+ }
+ }
+
+ if (mdr && !mdr->more()->peer_update_journaled) {
+ ceph_assert(!in->has_subtree_root_dirfrag(mds->get_nodeid()));
+
+ _rmdir_rollback_finish(mdr, rollback.reqid, dn, straydn);
+ return;
+ }
+
+
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rmdir_rollback", rollback.reqid, leader,
+ EPeerUpdate::OP_ROLLBACK, EPeerUpdate::RMDIR);
+ mdlog->start_entry(le);
+
+ le->commit.add_dir_context(dn->get_dir());
+ le->commit.add_primary_dentry(dn, in, true);
+ // peer: no need to journal straydn
+
+ dout(10) << " noting renamed (unlinked) dir ino " << in->ino() << " in metablob" << dendl;
+ le->commit.renamed_dirino = in->ino();
+
+ mdcache->project_subtree_rename(in, straydn->get_dir(), dn->get_dir());
+
+ submit_mdlog_entry(le,
+ new C_MDS_LoggedRmdirRollback(this, mdr,rollback.reqid,
+ dn, straydn),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_rmdir_rollback_finish(MDRequestRef& mdr, metareqid_t reqid, CDentry *dn, CDentry *straydn)
+{
+ dout(10) << "_rmdir_rollback_finish " << reqid << dendl;
+
+ straydn->get_dir()->unlink_inode(straydn);
+ dn->pop_projected_linkage();
+ straydn->pop_projected_linkage();
+
+ CInode *in = dn->get_linkage()->get_inode();
+ mdcache->adjust_subtree_after_rename(in, straydn->get_dir(),
+ !mdr || mdr->more()->peer_update_journaled);
+
+ if (mds->is_resolve()) {
+ CDir *root = mdcache->get_subtree_root(straydn->get_dir());
+ mdcache->try_trim_non_auth_subtree(root);
+ }
+
+ if (mdr)
+ mdcache->request_finish(mdr);
+
+ mdcache->finish_rollback(reqid, mdr);
+}
+
+
+/** _dir_is_nonempty[_unlocked]
+ *
+ * check if a directory is non-empty (i.e. we can rmdir it).
+ *
+ * the unlocked varient this is a fastpath check. we can't really be
+ * sure until we rdlock the filelock.
+ */
+bool Server::_dir_is_nonempty_unlocked(MDRequestRef& mdr, CInode *in)
+{
+ dout(10) << "dir_is_nonempty_unlocked " << *in << dendl;
+ ceph_assert(in->is_auth());
+
+ if (in->filelock.is_cached())
+ return false; // there can be pending async create/unlink. don't know.
+ if (in->snaprealm && in->snaprealm->srnode.snaps.size())
+ return true; // in a snapshot!
+
+ auto&& ls = in->get_dirfrags();
+ for (const auto& dir : ls) {
+ // is the frag obviously non-empty?
+ if (dir->is_auth()) {
+ if (dir->get_projected_fnode()->fragstat.size()) {
+ dout(10) << "dir_is_nonempty_unlocked dirstat has "
+ << dir->get_projected_fnode()->fragstat.size() << " items " << *dir << dendl;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Server::_dir_is_nonempty(MDRequestRef& mdr, CInode *in)
+{
+ dout(10) << "dir_is_nonempty " << *in << dendl;
+ ceph_assert(in->is_auth());
+ ceph_assert(in->filelock.can_read(mdr->get_client()));
+
+ frag_info_t dirstat;
+ version_t dirstat_version = in->get_projected_inode()->dirstat.version;
+
+ auto&& ls = in->get_dirfrags();
+ for (const auto& dir : ls) {
+ const auto& pf = dir->get_projected_fnode();
+ if (pf->fragstat.size()) {
+ dout(10) << "dir_is_nonempty dirstat has "
+ << pf->fragstat.size() << " items " << *dir << dendl;
+ return true;
+ }
+
+ if (pf->accounted_fragstat.version == dirstat_version)
+ dirstat.add(pf->accounted_fragstat);
+ else
+ dirstat.add(pf->fragstat);
+ }
+
+ return dirstat.size() != in->get_projected_inode()->dirstat.size();
+}
+
+
+// ======================================================
+
+
+class C_MDS_rename_finish : public ServerLogContext {
+ CDentry *srcdn;
+ CDentry *destdn;
+ CDentry *straydn;
+public:
+ C_MDS_rename_finish(Server *s, MDRequestRef& r,
+ CDentry *sdn, CDentry *ddn, CDentry *stdn) :
+ ServerLogContext(s, r),
+ srcdn(sdn), destdn(ddn), straydn(stdn) { }
+ void finish(int r) override {
+ ceph_assert(r == 0);
+ server->_rename_finish(mdr, srcdn, destdn, straydn);
+ }
+};
+
+
+/** handle_client_rename
+ *
+ * rename leader is the destdn auth. this is because cached inodes
+ * must remain connected. thus, any replica of srci, must also
+ * replicate destdn, and possibly straydn, so that srci (and
+ * destdn->inode) remain connected during the rename.
+ *
+ * to do this, we freeze srci, then leader (destdn auth) verifies that
+ * all other nodes have also replciated destdn and straydn. note that
+ * destdn replicas need not also replicate srci. this only works when
+ * destdn is leader.
+ *
+ * This function takes responsibility for the passed mdr.
+ */
+void Server::handle_client_rename(MDRequestRef& mdr)
+{
+ const auto& req = mdr->client_request;
+ dout(7) << "handle_client_rename " << *req << dendl;
+
+ filepath destpath = req->get_filepath();
+ filepath srcpath = req->get_filepath2();
+ if (srcpath.is_last_dot_or_dotdot() || destpath.is_last_dot_or_dotdot()) {
+ respond_to_request(mdr, -CEPHFS_EBUSY);
+ return;
+ }
+
+ if (req->get_alternate_name().size() > alternate_name_max) {
+ dout(10) << " alternate_name longer than " << alternate_name_max << dendl;
+ respond_to_request(mdr, -CEPHFS_ENAMETOOLONG);
+ return;
+ }
+
+ auto [destdn, srcdn] = rdlock_two_paths_xlock_destdn(mdr, true);
+ if (!destdn)
+ return;
+
+ dout(10) << " destdn " << *destdn << dendl;
+ CDir *destdir = destdn->get_dir();
+ ceph_assert(destdir->is_auth());
+ CDentry::linkage_t *destdnl = destdn->get_projected_linkage();
+
+ dout(10) << " srcdn " << *srcdn << dendl;
+ CDir *srcdir = srcdn->get_dir();
+ CDentry::linkage_t *srcdnl = srcdn->get_projected_linkage();
+ CInode *srci = srcdnl->get_inode();
+ dout(10) << " srci " << *srci << dendl;
+
+ // -- some sanity checks --
+ if (destdn == srcdn) {
+ dout(7) << "rename src=dest, noop" << dendl;
+ respond_to_request(mdr, 0);
+ return;
+ }
+
+ // dest a child of src?
+ // e.g. mv /usr /usr/foo
+ if (srci->is_dir() && srci->is_projected_ancestor_of(destdir->get_inode())) {
+ dout(7) << "cannot rename item to be a child of itself" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ // is this a stray migration, reintegration or merge? (sanity checks!)
+ if (mdr->reqid.name.is_mds() &&
+ !(MDS_INO_IS_STRAY(srcpath.get_ino()) &&
+ MDS_INO_IS_STRAY(destpath.get_ino())) &&
+ !(destdnl->is_remote() &&
+ destdnl->get_remote_ino() == srci->ino())) {
+ respond_to_request(mdr, -CEPHFS_EINVAL); // actually, this won't reply, but whatev.
+ return;
+ }
+
+ CInode *oldin = 0;
+ if (!destdnl->is_null()) {
+ //dout(10) << "dest dn exists " << *destdn << dendl;
+ oldin = mdcache->get_dentry_inode(destdn, mdr, true);
+ if (!oldin) return;
+ dout(10) << " oldin " << *oldin << dendl;
+
+ // non-empty dir? do trivial fast unlocked check, do another check later with read locks
+ if (oldin->is_dir() && _dir_is_nonempty_unlocked(mdr, oldin)) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+
+ // mv /some/thing /to/some/existing_other_thing
+ if (oldin->is_dir() && !srci->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_EISDIR);
+ return;
+ }
+ if (!oldin->is_dir() && srci->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+ if (srci == oldin && !srcdir->inode->is_stray()) {
+ respond_to_request(mdr, 0); // no-op. POSIX makes no sense.
+ return;
+ }
+ if (destdn->get_alternate_name() != req->get_alternate_name()) {
+ /* the dentry exists but the alternate_names do not match, fail... */
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ }
+
+ vector<CDentry*>& srctrace = mdr->dn[1];
+ vector<CDentry*>& desttrace = mdr->dn[0];
+
+ // src+dest traces _must_ share a common ancestor for locking to prevent orphans
+ if (destpath.get_ino() != srcpath.get_ino() &&
+ !(req->get_source().is_mds() &&
+ MDS_INO_IS_STRAY(srcpath.get_ino()))) { // <-- mds 'rename' out of stray dir is ok!
+ CInode *srcbase = srctrace[0]->get_dir()->get_inode();
+ CInode *destbase = desttrace[0]->get_dir()->get_inode();
+ // ok, extend srctrace toward root until it is an ancestor of desttrace.
+ while (srcbase != destbase &&
+ !srcbase->is_projected_ancestor_of(destbase)) {
+ CDentry *pdn = srcbase->get_projected_parent_dn();
+ srctrace.insert(srctrace.begin(), pdn);
+ dout(10) << "rename prepending srctrace with " << *pdn << dendl;
+ srcbase = pdn->get_dir()->get_inode();
+ }
+
+ // then, extend destpath until it shares the same parent inode as srcpath.
+ while (destbase != srcbase) {
+ CDentry *pdn = destbase->get_projected_parent_dn();
+ desttrace.insert(desttrace.begin(), pdn);
+ dout(10) << "rename prepending desttrace with " << *pdn << dendl;
+ destbase = pdn->get_dir()->get_inode();
+ }
+ dout(10) << "rename src and dest traces now share common ancestor " << *destbase << dendl;
+ }
+
+
+ bool linkmerge = srcdnl->get_inode() == destdnl->get_inode();
+ if (linkmerge)
+ dout(10) << " this is a link merge" << dendl;
+
+ // -- create stray dentry? --
+ CDentry *straydn = NULL;
+ if (destdnl->is_primary() && !linkmerge) {
+ straydn = prepare_stray_dentry(mdr, destdnl->get_inode());
+ if (!straydn)
+ return;
+ dout(10) << " straydn is " << *straydn << dendl;
+ } else if (mdr->straydn) {
+ mdr->unpin(mdr->straydn);
+ mdr->straydn = NULL;
+ }
+
+
+ // -- locks --
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+
+ // we need to update srci's ctime. xlock its least contended lock to do that...
+ lov.add_xlock(&srci->linklock);
+ lov.add_xlock(&srci->snaplock);
+
+ if (oldin) {
+ // xlock oldin (for nlink--)
+ lov.add_xlock(&oldin->linklock);
+ lov.add_xlock(&oldin->snaplock);
+ if (oldin->is_dir()) {
+ ceph_assert(srci->is_dir());
+ lov.add_rdlock(&oldin->filelock); // to verify it's empty
+
+ // adjust locking order?
+ int cmp = mdr->compare_paths();
+ if (cmp < 0 || (cmp == 0 && oldin->ino() < srci->ino()))
+ std::reverse(lov.begin(), lov.end());
+ } else {
+ ceph_assert(!srci->is_dir());
+ // adjust locking order;
+ if (srci->ino() > oldin->ino())
+ std::reverse(lov.begin(), lov.end());
+ }
+ }
+
+ // straydn?
+ if (straydn) {
+ lov.add_wrlock(&straydn->get_dir()->inode->filelock);
+ lov.add_wrlock(&straydn->get_dir()->inode->nestlock);
+ lov.add_xlock(&straydn->lock);
+ }
+
+ CInode *auth_pin_freeze = !srcdn->is_auth() && srcdnl->is_primary() ? srci : nullptr;
+ if (!mds->locker->acquire_locks(mdr, lov, auth_pin_freeze))
+ return;
+
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (linkmerge)
+ ceph_assert(srcdir->inode->is_stray() && srcdnl->is_primary() && destdnl->is_remote());
+
+ if ((!mdr->has_more() || mdr->more()->witnessed.empty())) {
+ if (!check_access(mdr, srcdir->get_inode(), MAY_WRITE))
+ return;
+
+ if (!check_access(mdr, destdn->get_dir()->get_inode(), MAY_WRITE))
+ return;
+
+ if (!linkmerge && !check_fragment_space(mdr, destdn->get_dir()))
+ return;
+
+ if (!linkmerge && !check_dir_max_entries(mdr, destdn->get_dir()))
+ return;
+
+ if (!check_access(mdr, srci, MAY_WRITE))
+ return;
+ }
+
+ // with read lock, really verify oldin is empty
+ if (oldin &&
+ oldin->is_dir() &&
+ _dir_is_nonempty(mdr, oldin)) {
+ respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+ return;
+ }
+
+ /* project_snaprealm_past_parent() will do this job
+ *
+ // moving between snaprealms?
+ if (srcdnl->is_primary() && srci->is_multiversion() && !srci->snaprealm) {
+ SnapRealm *srcrealm = srci->find_snaprealm();
+ SnapRealm *destrealm = destdn->get_dir()->inode->find_snaprealm();
+ if (srcrealm != destrealm &&
+ (srcrealm->get_newest_seq() + 1 > srcdn->first ||
+ destrealm->get_newest_seq() + 1 > srcdn->first)) {
+ dout(10) << " renaming between snaprealms, creating snaprealm for " << *srci << dendl;
+ mdcache->snaprealm_create(mdr, srci);
+ return;
+ }
+ }
+ */
+
+ SnapRealm *dest_realm = nullptr;
+ SnapRealm *src_realm = nullptr;
+ if (!linkmerge) {
+ dest_realm = destdir->inode->find_snaprealm();
+ if (srcdir->inode == destdir->inode)
+ src_realm = dest_realm;
+ else
+ src_realm = srcdir->inode->find_snaprealm();
+ if (src_realm != dest_realm &&
+ src_realm->get_subvolume_ino() != dest_realm->get_subvolume_ino()) {
+ respond_to_request(mdr, -CEPHFS_EXDEV);
+ return;
+ }
+ }
+
+ ceph_assert(g_conf()->mds_kill_rename_at != 1);
+
+ // -- open all srcdn inode frags, if any --
+ // we need these open so that auth can properly delegate from inode to dirfrags
+ // after the inode is _ours_.
+ if (srcdnl->is_primary() &&
+ !srcdn->is_auth() &&
+ srci->is_dir()) {
+ dout(10) << "srci is remote dir, setting stickydirs and opening all frags" << dendl;
+ mdr->set_stickydirs(srci);
+
+ frag_vec_t leaves;
+ srci->dirfragtree.get_leaves(leaves);
+ for (const auto& leaf : leaves) {
+ CDir *dir = srci->get_dirfrag(leaf);
+ if (!dir) {
+ dout(10) << " opening " << leaf << " under " << *srci << dendl;
+ mdcache->open_remote_dirfrag(srci, leaf, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ }
+ }
+
+ // -- prepare snaprealm ---
+
+ if (linkmerge) {
+ if (!mdr->more()->srci_srnode &&
+ srci->get_projected_inode()->nlink == 1 &&
+ srci->is_projected_snaprealm_global()) {
+ sr_t *new_srnode = srci->prepare_new_srnode(0);
+ srci->record_snaprealm_parent_dentry(new_srnode, nullptr, destdn, false);
+
+ srci->clear_snaprealm_global(new_srnode);
+ mdr->more()->srci_srnode = new_srnode;
+ }
+ } else {
+ if (oldin && !mdr->more()->desti_srnode) {
+ if (oldin->is_projected_snaprealm_global()) {
+ sr_t *new_srnode = oldin->prepare_new_srnode(0);
+ oldin->record_snaprealm_parent_dentry(new_srnode, dest_realm, destdn, destdnl->is_primary());
+ // dropping the last linkage or dropping the last remote linkage,
+ // detch the inode from global snaprealm
+ auto nlink = oldin->get_projected_inode()->nlink;
+ if (nlink == 1 ||
+ (nlink == 2 && !destdnl->is_primary() &&
+ !oldin->get_projected_parent_dir()->inode->is_stray()))
+ oldin->clear_snaprealm_global(new_srnode);
+ mdr->more()->desti_srnode = new_srnode;
+ } else if (destdnl->is_primary()) {
+ snapid_t follows = dest_realm->get_newest_seq();
+ if (oldin->snaprealm || follows + 1 > oldin->get_oldest_snap()) {
+ sr_t *new_srnode = oldin->prepare_new_srnode(follows);
+ oldin->record_snaprealm_past_parent(new_srnode, straydn->get_dir()->inode->find_snaprealm());
+ mdr->more()->desti_srnode = new_srnode;
+ }
+ }
+ }
+ if (!mdr->more()->srci_srnode) {
+ if (srci->is_projected_snaprealm_global()) {
+ sr_t *new_srnode = srci->prepare_new_srnode(0);
+ srci->record_snaprealm_parent_dentry(new_srnode, src_realm, srcdn, srcdnl->is_primary());
+ mdr->more()->srci_srnode = new_srnode;
+ } else if (srcdnl->is_primary()) {
+ snapid_t follows = src_realm->get_newest_seq();
+ if (src_realm != dest_realm &&
+ (srci->snaprealm || follows + 1 > srci->get_oldest_snap())) {
+ sr_t *new_srnode = srci->prepare_new_srnode(follows);
+ srci->record_snaprealm_past_parent(new_srnode, dest_realm);
+ mdr->more()->srci_srnode = new_srnode;
+ }
+ }
+ }
+ }
+
+ // -- prepare witnesses --
+
+ /*
+ * NOTE: we use _all_ replicas as witnesses.
+ * this probably isn't totally necessary (esp for file renames),
+ * but if/when we change that, we have to make sure rejoin is
+ * sufficiently robust to handle strong rejoins from survivors
+ * with totally wrong dentry->inode linkage.
+ * (currently, it can ignore rename effects, because the resolve
+ * stage will sort them out.)
+ */
+ set<mds_rank_t> witnesses = mdr->more()->extra_witnesses;
+ if (srcdn->is_auth())
+ srcdn->list_replicas(witnesses);
+ else
+ witnesses.insert(srcdn->authority().first);
+ if (srcdnl->is_remote() && !srci->is_auth())
+ witnesses.insert(srci->authority().first);
+ destdn->list_replicas(witnesses);
+ if (destdnl->is_remote() && !oldin->is_auth())
+ witnesses.insert(oldin->authority().first);
+ dout(10) << " witnesses " << witnesses << ", have " << mdr->more()->witnessed << dendl;
+
+ if (!witnesses.empty()) {
+ // Replicas can't see projected dentry linkages and will get confused.
+ // We have taken snaplocks on ancestor inodes. Later rename/rmdir requests
+ // can't project these inodes' linkages.
+ bool need_flush = false;
+ for (auto& dn : srctrace) {
+ if (dn->is_projected()) {
+ need_flush = true;
+ break;
+ }
+ }
+ if (!need_flush) {
+ CDentry *dn = destdn;
+ do {
+ if (dn->is_projected()) {
+ need_flush = true;
+ break;
+ }
+ CInode *diri = dn->get_dir()->get_inode();
+ dn = diri->get_projected_parent_dn();
+ } while (dn);
+ }
+ if (need_flush) {
+ mdlog->wait_for_safe(
+ new MDSInternalContextWrapper(mds,
+ new C_MDS_RetryRequest(mdcache, mdr)));
+ mdlog->flush();
+ return;
+ }
+ }
+
+ // do srcdn auth last
+ mds_rank_t last = MDS_RANK_NONE;
+ if (!srcdn->is_auth()) {
+ last = srcdn->authority().first;
+ mdr->more()->srcdn_auth_mds = last;
+ // ask auth of srci to mark srci as ambiguous auth if more than two MDS
+ // are involved in the rename operation.
+ if (srcdnl->is_primary() && !mdr->more()->is_ambiguous_auth) {
+ dout(10) << " preparing ambiguous auth for srci" << dendl;
+ ceph_assert(mdr->more()->is_remote_frozen_authpin);
+ ceph_assert(mdr->more()->rename_inode == srci);
+ _rename_prepare_witness(mdr, last, witnesses, srctrace, desttrace, straydn);
+ return;
+ }
+ }
+
+ for (set<mds_rank_t>::iterator p = witnesses.begin();
+ p != witnesses.end();
+ ++p) {
+ if (*p == last) continue; // do it last!
+ if (mdr->more()->witnessed.count(*p)) {
+ dout(10) << " already witnessed by mds." << *p << dendl;
+ } else if (mdr->more()->waiting_on_peer.count(*p)) {
+ dout(10) << " already waiting on witness mds." << *p << dendl;
+ } else {
+ if (!_rename_prepare_witness(mdr, *p, witnesses, srctrace, desttrace, straydn))
+ return;
+ }
+ }
+ if (!mdr->more()->waiting_on_peer.empty())
+ return; // we're waiting for a witness.
+
+ if (last != MDS_RANK_NONE && mdr->more()->witnessed.count(last) == 0) {
+ dout(10) << " preparing last witness (srcdn auth)" << dendl;
+ ceph_assert(mdr->more()->waiting_on_peer.count(last) == 0);
+ _rename_prepare_witness(mdr, last, witnesses, srctrace, desttrace, straydn);
+ return;
+ }
+
+ // test hack: bail after peer does prepare, so we can verify it's _live_ rollback.
+ if (!mdr->more()->peers.empty() && !srci->is_dir())
+ ceph_assert(g_conf()->mds_kill_rename_at != 3);
+ if (!mdr->more()->peers.empty() && srci->is_dir())
+ ceph_assert(g_conf()->mds_kill_rename_at != 4);
+
+ // -- declare now --
+ mdr->set_mds_stamp(ceph_clock_now());
+
+ // -- prepare journal entry --
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "rename");
+ mdlog->start_entry(le);
+ le->metablob.add_client_req(mdr->reqid, req->get_oldest_client_tid());
+ if (!mdr->more()->witnessed.empty()) {
+ dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
+
+ le->reqid = mdr->reqid;
+ le->had_peers = true;
+
+ mdcache->add_uncommitted_leader(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+ // no need to send frozen auth pin to recovring auth MDS of srci
+ mdr->more()->is_remote_frozen_authpin = false;
+ }
+
+ _rename_prepare(mdr, &le->metablob, &le->client_map, srcdn, destdn, req->get_alternate_name(), straydn);
+ if (le->client_map.length())
+ le->cmapv = mds->sessionmap.get_projected();
+
+ // -- commit locally --
+ C_MDS_rename_finish *fin = new C_MDS_rename_finish(this, mdr, srcdn, destdn, straydn);
+
+ journal_and_reply(mdr, srci, destdn, le, fin);
+ mds->balancer->maybe_fragment(destdn->get_dir(), false);
+}
+
+
+void Server::_rename_finish(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, CDentry *straydn)
+{
+ dout(10) << "_rename_finish " << *mdr << dendl;
+
+ if (!mdr->more()->witnessed.empty())
+ mdcache->logged_leader_update(mdr->reqid);
+
+ // apply
+ _rename_apply(mdr, srcdn, destdn, straydn);
+
+ mdcache->send_dentry_link(destdn, mdr);
+
+ CDentry::linkage_t *destdnl = destdn->get_linkage();
+ CInode *in = destdnl->get_inode();
+ bool need_eval = mdr->more()->cap_imports.count(in);
+
+ // test hack: test peer commit
+ if (!mdr->more()->peers.empty() && !in->is_dir())
+ ceph_assert(g_conf()->mds_kill_rename_at != 5);
+ if (!mdr->more()->peers.empty() && in->is_dir())
+ ceph_assert(g_conf()->mds_kill_rename_at != 6);
+
+ // bump popularity
+ mds->balancer->hit_dir(srcdn->get_dir(), META_POP_IWR);
+ if (destdnl->is_remote() && in->is_auth())
+ mds->balancer->hit_inode(in, META_POP_IWR);
+
+ // did we import srci? if so, explicitly ack that import that, before we unlock and reply.
+
+ ceph_assert(g_conf()->mds_kill_rename_at != 7);
+
+ // reply
+ respond_to_request(mdr, 0);
+
+ if (need_eval)
+ mds->locker->eval(in, CEPH_CAP_LOCKS, true);
+
+ // clean up?
+ // respond_to_request() drops locks. So stray reintegration can race with us.
+ if (straydn && !straydn->get_projected_linkage()->is_null()) {
+ mdcache->notify_stray(straydn);
+ }
+}
+
+
+
+// helpers
+
+bool Server::_rename_prepare_witness(MDRequestRef& mdr, mds_rank_t who, set<mds_rank_t> &witnesse,
+ vector<CDentry*>& srctrace, vector<CDentry*>& dsttrace, CDentry *straydn)
+{
+ const auto& client_req = mdr->client_request;
+ ceph_assert(client_req);
+
+ if (mds->is_cluster_degraded() &&
+ !mds->mdsmap->is_clientreplay_or_active_or_stopping(who)) {
+ dout(10) << "_rename_prepare_witness mds." << who << " is not active" << dendl;
+ if (mdr->more()->waiting_on_peer.empty())
+ mds->wait_for_active_peer(who, new C_MDS_RetryRequest(mdcache, mdr));
+ return false;
+ }
+
+ dout(10) << "_rename_prepare_witness mds." << who << dendl;
+ auto req = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREP);
+
+ req->srcdnpath = filepath(srctrace.front()->get_dir()->ino());
+ for (auto dn : srctrace)
+ req->srcdnpath.push_dentry(dn->get_name());
+ req->destdnpath = filepath(dsttrace.front()->get_dir()->ino());
+ for (auto dn : dsttrace)
+ req->destdnpath.push_dentry(dn->get_name());
+ req->alternate_name = client_req->alternate_name;
+ if (straydn)
+ mdcache->encode_replica_stray(straydn, who, req->straybl);
+
+ if (mdr->more()->srci_srnode)
+ encode(*mdr->more()->srci_srnode, req->srci_snapbl);
+ if (mdr->more()->desti_srnode)
+ encode(*mdr->more()->desti_srnode, req->desti_snapbl);
+
+ req->srcdn_auth = mdr->more()->srcdn_auth_mds;
+
+ // srcdn auth will verify our current witness list is sufficient
+ req->witnesses = witnesse;
+
+ req->op_stamp = mdr->get_op_stamp();
+ mds->send_message_mds(req, who);
+
+ ceph_assert(mdr->more()->waiting_on_peer.count(who) == 0);
+ mdr->more()->waiting_on_peer.insert(who);
+ return true;
+}
+
+version_t Server::_rename_prepare_import(MDRequestRef& mdr, CDentry *srcdn, bufferlist *client_map_bl)
+{
+ version_t oldpv = mdr->more()->inode_import_v;
+
+ CDentry::linkage_t *srcdnl = srcdn->get_linkage();
+
+ /* import node */
+ auto blp = mdr->more()->inode_import.cbegin();
+
+ // imported caps
+ map<client_t,entity_inst_t> client_map;
+ map<client_t, client_metadata_t> client_metadata_map;
+ decode(client_map, blp);
+ decode(client_metadata_map, blp);
+ prepare_force_open_sessions(client_map, client_metadata_map,
+ mdr->more()->imported_session_map);
+ encode(client_map, *client_map_bl, mds->mdsmap->get_up_features());
+ encode(client_metadata_map, *client_map_bl);
+
+ list<ScatterLock*> updated_scatterlocks;
+ mdcache->migrator->decode_import_inode(srcdn, blp, srcdn->authority().first, mdr->ls,
+ mdr->more()->cap_imports, updated_scatterlocks);
+
+ // hack: force back to !auth and clean, temporarily
+ srcdnl->get_inode()->state_clear(CInode::STATE_AUTH);
+ srcdnl->get_inode()->mark_clean();
+
+ return oldpv;
+}
+
+bool Server::_need_force_journal(CInode *diri, bool empty)
+{
+ auto&& dirs = diri->get_dirfrags();
+
+ bool force_journal = false;
+ if (empty) {
+ for (const auto& dir : dirs) {
+ if (dir->is_subtree_root() && dir->get_dir_auth().first == mds->get_nodeid()) {
+ dout(10) << " frag " << dir->get_frag() << " is auth subtree dirfrag, will force journal" << dendl;
+ force_journal = true;
+ break;
+ } else
+ dout(20) << " frag " << dir->get_frag() << " is not auth subtree dirfrag" << dendl;
+ }
+ } else {
+ // see if any children of our frags are auth subtrees.
+ std::vector<CDir*> subtrees;
+ mdcache->get_subtrees(subtrees);
+ dout(10) << " subtrees " << subtrees << " frags " << dirs << dendl;
+ for (const auto& dir : dirs) {
+ for (const auto& subtree : subtrees) {
+ if (dir->contains(subtree)) {
+ if (subtree->get_dir_auth().first == mds->get_nodeid()) {
+ dout(10) << " frag " << dir->get_frag() << " contains (maybe) auth subtree, will force journal "
+ << *subtree << dendl;
+ force_journal = true;
+ break;
+ } else
+ dout(20) << " frag " << dir->get_frag() << " contains but isn't auth for " << *subtree << dendl;
+ } else
+ dout(20) << " frag " << dir->get_frag() << " does not contain " << *subtree << dendl;
+ }
+ if (force_journal)
+ break;
+ }
+ }
+ return force_journal;
+}
+
+void Server::_rename_prepare(MDRequestRef& mdr,
+ EMetaBlob *metablob, bufferlist *client_map_bl,
+ CDentry *srcdn, CDentry *destdn, std::string_view alternate_name,
+ CDentry *straydn)
+{
+ dout(10) << "_rename_prepare " << *mdr << " " << *srcdn << " " << *destdn << dendl;
+ if (straydn)
+ dout(10) << " straydn " << *straydn << dendl;
+
+ CDentry::linkage_t *srcdnl = srcdn->get_projected_linkage();
+ CDentry::linkage_t *destdnl = destdn->get_projected_linkage();
+ CInode *srci = srcdnl->get_inode();
+ CInode *oldin = destdnl->get_inode();
+
+ // primary+remote link merge?
+ bool linkmerge = (srci == oldin);
+ if (linkmerge)
+ ceph_assert(srcdnl->is_primary() && destdnl->is_remote());
+ bool silent = srcdn->get_dir()->inode->is_stray();
+
+ bool force_journal_dest = false;
+ if (srci->is_dir() && !destdn->is_auth()) {
+ if (srci->is_auth()) {
+ // if we are auth for srci and exporting it, force journal because journal replay needs
+ // the source inode to create auth subtrees.
+ dout(10) << " we are exporting srci, will force journal destdn" << dendl;
+ force_journal_dest = true;
+ } else
+ force_journal_dest = _need_force_journal(srci, false);
+ }
+
+ bool force_journal_stray = false;
+ if (oldin && oldin->is_dir() && straydn && !straydn->is_auth())
+ force_journal_stray = _need_force_journal(oldin, true);
+
+ if (linkmerge)
+ dout(10) << " merging remote and primary links to the same inode" << dendl;
+ if (silent)
+ dout(10) << " reintegrating stray; will avoid changing nlink or dir mtime" << dendl;
+ if (force_journal_dest)
+ dout(10) << " forcing journal destdn because we (will) have auth subtrees nested beneath it" << dendl;
+ if (force_journal_stray)
+ dout(10) << " forcing journal straydn because we (will) have auth subtrees nested beneath it" << dendl;
+
+ if (srci->is_dir() && (destdn->is_auth() || force_journal_dest)) {
+ dout(10) << " noting renamed dir ino " << srci->ino() << " in metablob" << dendl;
+ metablob->renamed_dirino = srci->ino();
+ } else if (oldin && oldin->is_dir() && force_journal_stray) {
+ dout(10) << " noting rename target dir " << oldin->ino() << " in metablob" << dendl;
+ metablob->renamed_dirino = oldin->ino();
+ }
+
+ // prepare
+ CInode::mempool_inode *spi = 0; // renamed inode
+ CInode::mempool_inode *tpi = 0; // target/overwritten inode
+
+ // target inode
+ if (!linkmerge) {
+ if (destdnl->is_primary()) {
+ ceph_assert(straydn); // moving to straydn.
+ // link--, and move.
+ if (destdn->is_auth()) {
+ auto pi= oldin->project_inode(mdr); //project_snaprealm
+ pi.inode->version = straydn->pre_dirty(pi.inode->version);
+ pi.inode->update_backtrace();
+ tpi = pi.inode.get();
+ }
+ straydn->push_projected_linkage(oldin);
+ } else if (destdnl->is_remote()) {
+ // nlink-- targeti
+ if (oldin->is_auth()) {
+ auto pi = oldin->project_inode(mdr);
+ pi.inode->version = oldin->pre_dirty();
+ tpi = pi.inode.get();
+ }
+ }
+ }
+
+ // dest
+ if (destdnl->is_null()) {
+ /* handle_client_rename checks that alternate_name matches for existing destdn */
+ destdn->set_alternate_name(alternate_name);
+ }
+ if (srcdnl->is_remote()) {
+ if (!linkmerge) {
+ // destdn
+ if (destdn->is_auth())
+ mdr->more()->pvmap[destdn] = destdn->pre_dirty();
+ destdn->push_projected_linkage(srcdnl->get_remote_ino(), srcdnl->get_remote_d_type());
+ // srci
+ if (srci->is_auth()) {
+ auto pi = srci->project_inode(mdr);
+ pi.inode->version = srci->pre_dirty();
+ spi = pi.inode.get();
+ }
+ } else {
+ dout(10) << " will merge remote onto primary link" << dendl;
+ if (destdn->is_auth()) {
+ auto pi = oldin->project_inode(mdr);
+ pi.inode->version = mdr->more()->pvmap[destdn] = destdn->pre_dirty(oldin->get_version());
+ spi = pi.inode.get();
+ }
+ }
+ } else { // primary
+ if (destdn->is_auth()) {
+ version_t oldpv;
+ if (srcdn->is_auth())
+ oldpv = srci->get_projected_version();
+ else {
+ oldpv = _rename_prepare_import(mdr, srcdn, client_map_bl);
+
+ // note which dirfrags have child subtrees in the journal
+ // event, so that we can open those (as bounds) during replay.
+ if (srci->is_dir()) {
+ auto&& ls = srci->get_dirfrags();
+ for (const auto& dir : ls) {
+ if (!dir->is_auth())
+ metablob->renamed_dir_frags.push_back(dir->get_frag());
+ }
+ dout(10) << " noting renamed dir open frags " << metablob->renamed_dir_frags << dendl;
+ }
+ }
+ auto pi = srci->project_inode(mdr); // project snaprealm if srcdnl->is_primary
+ // & srcdnl->snaprealm
+ pi.inode->version = mdr->more()->pvmap[destdn] = destdn->pre_dirty(oldpv);
+ pi.inode->update_backtrace();
+ spi = pi.inode.get();
+ }
+ destdn->push_projected_linkage(srci);
+ }
+
+ // src
+ if (srcdn->is_auth())
+ mdr->more()->pvmap[srcdn] = srcdn->pre_dirty();
+ srcdn->push_projected_linkage(); // push null linkage
+
+ if (!silent) {
+ if (spi) {
+ spi->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > spi->rstat.rctime)
+ spi->rstat.rctime = mdr->get_op_stamp();
+ spi->change_attr++;
+ if (linkmerge)
+ spi->nlink--;
+ }
+ if (tpi) {
+ tpi->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > tpi->rstat.rctime)
+ tpi->rstat.rctime = mdr->get_op_stamp();
+ tpi->change_attr++;
+ {
+ std::string t;
+ destdn->make_path_string(t, true);
+ tpi->stray_prior_path = std::move(t);
+ }
+ tpi->nlink--;
+ if (tpi->nlink == 0)
+ oldin->state_set(CInode::STATE_ORPHAN);
+ }
+ }
+
+ // prepare nesting, mtime updates
+ int predirty_dir = silent ? 0:PREDIRTY_DIR;
+
+ // guarantee stray dir is processed first during journal replay. unlink the old inode,
+ // then link the source inode to destdn
+ if (destdnl->is_primary()) {
+ ceph_assert(straydn);
+ if (straydn->is_auth()) {
+ metablob->add_dir_context(straydn->get_dir());
+ metablob->add_dir(straydn->get_dir(), true);
+ }
+ }
+
+ if (!linkmerge && destdnl->is_remote() && oldin->is_auth()) {
+ CDir *oldin_dir = oldin->get_projected_parent_dir();
+ if (oldin_dir != srcdn->get_dir() && oldin_dir != destdn->get_dir())
+ mdcache->predirty_journal_parents(mdr, metablob, oldin, oldin_dir, PREDIRTY_PRIMARY);
+ }
+
+ // sub off target
+ if (destdn->is_auth() && !destdnl->is_null()) {
+ mdcache->predirty_journal_parents(mdr, metablob, oldin, destdn->get_dir(),
+ (destdnl->is_primary() ? PREDIRTY_PRIMARY:0)|predirty_dir, -1);
+ if (destdnl->is_primary()) {
+ ceph_assert(straydn);
+ mdcache->predirty_journal_parents(mdr, metablob, oldin, straydn->get_dir(),
+ PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
+ }
+ }
+
+ if (srcdnl->is_remote() && srci->is_auth()) {
+ CDir *srci_dir = srci->get_projected_parent_dir();
+ if (srci_dir != srcdn->get_dir() && srci_dir != destdn->get_dir())
+ mdcache->predirty_journal_parents(mdr, metablob, srci, srci_dir, PREDIRTY_PRIMARY);
+ }
+
+ // move srcdn
+ int predirty_primary = (srcdnl->is_primary() && srcdn->get_dir() != destdn->get_dir()) ? PREDIRTY_PRIMARY:0;
+ int flags = predirty_dir | predirty_primary;
+ if (srcdn->is_auth())
+ mdcache->predirty_journal_parents(mdr, metablob, srci, srcdn->get_dir(), PREDIRTY_SHALLOW|flags, -1);
+ if (destdn->is_auth())
+ mdcache->predirty_journal_parents(mdr, metablob, srci, destdn->get_dir(), flags, 1);
+
+ // add it all to the metablob
+ // target inode
+ if (!linkmerge) {
+ if (destdnl->is_primary()) {
+ ceph_assert(straydn);
+ if (destdn->is_auth()) {
+ // project snaprealm, too
+ if (auto& desti_srnode = mdr->more()->desti_srnode) {
+ oldin->project_snaprealm(desti_srnode);
+ if (tpi->nlink == 0)
+ ceph_assert(!desti_srnode->is_parent_global());
+ desti_srnode = NULL;
+ }
+ straydn->first = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+ metablob->add_primary_dentry(straydn, oldin, true, true);
+ } else if (force_journal_stray) {
+ dout(10) << " forced journaling straydn " << *straydn << dendl;
+ metablob->add_dir_context(straydn->get_dir());
+ metablob->add_primary_dentry(straydn, oldin, true);
+ }
+ } else if (destdnl->is_remote()) {
+ if (oldin->is_auth()) {
+ sr_t *new_srnode = NULL;
+ if (mdr->peer_request) {
+ if (mdr->peer_request->desti_snapbl.length() > 0) {
+ new_srnode = new sr_t();
+ auto p = mdr->peer_request->desti_snapbl.cbegin();
+ decode(*new_srnode, p);
+ }
+ } else if (auto& desti_srnode = mdr->more()->desti_srnode) {
+ new_srnode = desti_srnode;
+ desti_srnode = NULL;
+ }
+ if (new_srnode) {
+ oldin->project_snaprealm(new_srnode);
+ if (tpi->nlink == 0)
+ ceph_assert(!new_srnode->is_parent_global());
+ }
+ // auth for targeti
+ CDentry *oldin_pdn = oldin->get_projected_parent_dn();
+ mdcache->journal_cow_dentry(mdr.get(), metablob, oldin_pdn);
+ metablob->add_primary_dentry(oldin_pdn, oldin, true);
+ }
+ }
+ }
+
+ // dest
+ if (srcdnl->is_remote()) {
+ ceph_assert(!linkmerge);
+ if (destdn->is_auth() && !destdnl->is_null())
+ mdcache->journal_cow_dentry(mdr.get(), metablob, destdn, CEPH_NOSNAP, 0, destdnl);
+ else
+ destdn->first = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+
+ if (destdn->is_auth())
+ metablob->add_remote_dentry(destdn, true, srcdnl->get_remote_ino(), srcdnl->get_remote_d_type());
+
+ if (srci->is_auth() ) { // it's remote
+ if (mdr->peer_request) {
+ if (mdr->peer_request->srci_snapbl.length() > 0) {
+ sr_t *new_srnode = new sr_t();
+ auto p = mdr->peer_request->srci_snapbl.cbegin();
+ decode(*new_srnode, p);
+ srci->project_snaprealm(new_srnode);
+ }
+ } else if (auto& srci_srnode = mdr->more()->srci_srnode) {
+ srci->project_snaprealm(srci_srnode);
+ srci_srnode = NULL;
+ }
+
+ CDentry *srci_pdn = srci->get_projected_parent_dn();
+ mdcache->journal_cow_dentry(mdr.get(), metablob, srci_pdn);
+ metablob->add_primary_dentry(srci_pdn, srci, true);
+ }
+ } else if (srcdnl->is_primary()) {
+ // project snap parent update?
+ if (destdn->is_auth()) {
+ if (auto& srci_srnode = mdr->more()->srci_srnode) {
+ srci->project_snaprealm(srci_srnode);
+ srci_srnode = NULL;
+ }
+ }
+
+ if (destdn->is_auth() && !destdnl->is_null())
+ mdcache->journal_cow_dentry(mdr.get(), metablob, destdn, CEPH_NOSNAP, 0, destdnl);
+
+ destdn->first = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+ {
+ auto do_corruption = inject_rename_corrupt_dentry_first;
+ if (unlikely(do_corruption > 0.0)) {
+ auto r = ceph::util::generate_random_number(0.0, 1.0);
+ if (r < do_corruption) {
+ dout(0) << "corrupting dn: " << *destdn << dendl;
+ destdn->first = -10;
+ }
+ }
+ }
+
+ if (destdn->is_auth())
+ metablob->add_primary_dentry(destdn, srci, true, true);
+ else if (force_journal_dest) {
+ dout(10) << " forced journaling destdn " << *destdn << dendl;
+ metablob->add_dir_context(destdn->get_dir());
+ metablob->add_primary_dentry(destdn, srci, true);
+ if (srcdn->is_auth() && srci->is_dir()) {
+ // journal new subtrees root dirfrags
+ auto&& ls = srci->get_dirfrags();
+ for (const auto& dir : ls) {
+ if (dir->is_auth())
+ metablob->add_dir(dir, true);
+ }
+ }
+ }
+ }
+
+ // src
+ if (srcdn->is_auth()) {
+ dout(10) << " journaling srcdn " << *srcdn << dendl;
+ mdcache->journal_cow_dentry(mdr.get(), metablob, srcdn, CEPH_NOSNAP, 0, srcdnl);
+ // also journal the inode in case we need do peer rename rollback. It is Ok to add
+ // both primary and NULL dentries. Because during journal replay, null dentry is
+ // processed after primary dentry.
+ if (srcdnl->is_primary() && !srci->is_dir() && !destdn->is_auth())
+ metablob->add_primary_dentry(srcdn, srci, true);
+ metablob->add_null_dentry(srcdn, true);
+ } else
+ dout(10) << " NOT journaling srcdn " << *srcdn << dendl;
+
+ // make renamed inode first track the dn
+ if (srcdnl->is_primary() && destdn->is_auth()) {
+ ceph_assert(srci->first <= destdn->first);
+ srci->first = destdn->first;
+ }
+ // make stray inode first track the straydn
+ if (straydn && straydn->is_auth()) {
+ ceph_assert(oldin->first <= straydn->first);
+ oldin->first = straydn->first;
+ }
+
+ if (oldin && oldin->is_dir()) {
+ ceph_assert(straydn);
+ mdcache->project_subtree_rename(oldin, destdn->get_dir(), straydn->get_dir());
+ }
+ if (srci->is_dir())
+ mdcache->project_subtree_rename(srci, srcdn->get_dir(), destdn->get_dir());
+
+}
+
+
+void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, CDentry *straydn)
+{
+ dout(10) << "_rename_apply " << *mdr << " " << *srcdn << " " << *destdn << dendl;
+ dout(10) << " pvs " << mdr->more()->pvmap << dendl;
+
+ CDentry::linkage_t *srcdnl = srcdn->get_linkage();
+ CDentry::linkage_t *destdnl = destdn->get_linkage();
+
+ CInode *oldin = destdnl->get_inode();
+
+ // primary+remote link merge?
+ bool linkmerge = (srcdnl->get_inode() == oldin);
+ if (linkmerge)
+ ceph_assert(srcdnl->is_primary() && destdnl->is_remote());
+
+ bool new_in_snaprealm = false;
+ bool new_oldin_snaprealm = false;
+
+ // target inode
+ if (!linkmerge) {
+ if (destdnl->is_primary()) {
+ ceph_assert(straydn);
+ dout(10) << "straydn is " << *straydn << dendl;
+
+ // if there is newly created snaprealm, need to split old snaprealm's
+ // inodes_with_caps. So pop snaprealm before linkage changes.
+ if (destdn->is_auth()) {
+ bool hadrealm = (oldin->snaprealm ? true : false);
+ oldin->early_pop_projected_snaprealm();
+ new_oldin_snaprealm = (oldin->snaprealm && !hadrealm);
+ } else {
+ ceph_assert(mdr->peer_request);
+ if (mdr->peer_request->desti_snapbl.length()) {
+ new_oldin_snaprealm = !oldin->snaprealm;
+ oldin->decode_snap_blob(mdr->peer_request->desti_snapbl);
+ ceph_assert(oldin->snaprealm);
+ }
+ }
+
+ destdn->get_dir()->unlink_inode(destdn, false);
+
+ straydn->pop_projected_linkage();
+ if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
+ ceph_assert(!straydn->is_projected()); // no other projected
+
+ // nlink-- targeti
+ if (destdn->is_auth())
+ oldin->pop_and_dirty_projected_inode(mdr->ls, mdr);
+
+ mdcache->touch_dentry_bottom(straydn); // drop dn as quickly as possible.
+ } else if (destdnl->is_remote()) {
+ destdn->get_dir()->unlink_inode(destdn, false);
+ if (oldin->is_auth()) {
+ oldin->pop_and_dirty_projected_inode(mdr->ls, mdr);
+ } else if (mdr->peer_request) {
+ if (mdr->peer_request->desti_snapbl.length() > 0) {
+ ceph_assert(oldin->snaprealm);
+ oldin->decode_snap_blob(mdr->peer_request->desti_snapbl);
+ }
+ } else if (auto& desti_srnode = mdr->more()->desti_srnode) {
+ delete desti_srnode;
+ desti_srnode = NULL;
+ }
+ }
+ }
+
+ // unlink src before we relink it at dest
+ CInode *in = srcdnl->get_inode();
+ ceph_assert(in);
+
+ bool srcdn_was_remote = srcdnl->is_remote();
+ if (!srcdn_was_remote) {
+ // if there is newly created snaprealm, need to split old snaprealm's
+ // inodes_with_caps. So pop snaprealm before linkage changes.
+ if (destdn->is_auth()) {
+ bool hadrealm = (in->snaprealm ? true : false);
+ in->early_pop_projected_snaprealm();
+ new_in_snaprealm = (in->snaprealm && !hadrealm);
+ } else {
+ ceph_assert(mdr->peer_request);
+ if (mdr->peer_request->srci_snapbl.length()) {
+ new_in_snaprealm = !in->snaprealm;
+ in->decode_snap_blob(mdr->peer_request->srci_snapbl);
+ ceph_assert(in->snaprealm);
+ }
+ }
+ }
+
+ srcdn->get_dir()->unlink_inode(srcdn);
+
+ // dest
+ if (srcdn_was_remote) {
+ if (!linkmerge) {
+ // destdn
+ destdnl = destdn->pop_projected_linkage();
+ if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
+ ceph_assert(!destdn->is_projected()); // no other projected
+
+ destdn->link_remote(destdnl, in);
+ if (destdn->is_auth())
+ destdn->mark_dirty(mdr->more()->pvmap[destdn], mdr->ls);
+ // in
+ if (in->is_auth()) {
+ in->pop_and_dirty_projected_inode(mdr->ls, mdr);
+ } else if (mdr->peer_request) {
+ if (mdr->peer_request->srci_snapbl.length() > 0) {
+ ceph_assert(in->snaprealm);
+ in->decode_snap_blob(mdr->peer_request->srci_snapbl);
+ }
+ } else if (auto& srci_srnode = mdr->more()->srci_srnode) {
+ delete srci_srnode;
+ srci_srnode = NULL;
+ }
+ } else {
+ dout(10) << "merging remote onto primary link" << dendl;
+ oldin->pop_and_dirty_projected_inode(mdr->ls, mdr);
+ }
+ } else { // primary
+ if (linkmerge) {
+ dout(10) << "merging primary onto remote link" << dendl;
+ destdn->get_dir()->unlink_inode(destdn, false);
+ }
+ destdnl = destdn->pop_projected_linkage();
+ if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
+ ceph_assert(!destdn->is_projected()); // no other projected
+
+ // srcdn inode import?
+ if (!srcdn->is_auth() && destdn->is_auth()) {
+ ceph_assert(mdr->more()->inode_import.length() > 0);
+
+ map<client_t,Capability::Import> imported_caps;
+
+ // finish cap imports
+ finish_force_open_sessions(mdr->more()->imported_session_map);
+ if (mdr->more()->cap_imports.count(destdnl->get_inode())) {
+ mdcache->migrator->finish_import_inode_caps(destdnl->get_inode(),
+ mdr->more()->srcdn_auth_mds, true,
+ mdr->more()->imported_session_map,
+ mdr->more()->cap_imports[destdnl->get_inode()],
+ imported_caps);
+ }
+
+ mdr->more()->inode_import.clear();
+ encode(imported_caps, mdr->more()->inode_import);
+
+ /* hack: add an auth pin for each xlock we hold. These were
+ * remote xlocks previously but now they're local and
+ * we're going to try and unpin when we xlock_finish. */
+
+ for (auto i = mdr->locks.lower_bound(&destdnl->get_inode()->versionlock);
+ i != mdr->locks.end();
+ ++i) {
+ SimpleLock *lock = i->lock;
+ if (lock->get_parent() != destdnl->get_inode())
+ break;
+ if (i->is_xlock() && !lock->is_locallock())
+ mds->locker->xlock_import(lock);
+ }
+
+ // hack: fix auth bit
+ in->state_set(CInode::STATE_AUTH);
+
+ mdr->clear_ambiguous_auth();
+ }
+
+ if (destdn->is_auth())
+ in->pop_and_dirty_projected_inode(mdr->ls, mdr);
+ }
+
+ // src
+ if (srcdn->is_auth())
+ srcdn->mark_dirty(mdr->more()->pvmap[srcdn], mdr->ls);
+ srcdn->pop_projected_linkage();
+ if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
+ ceph_assert(!srcdn->is_projected()); // no other projected
+
+ // apply remaining projected inodes (nested)
+ mdr->apply();
+
+ // update subtree map?
+ if (destdnl->is_primary() && in->is_dir())
+ mdcache->adjust_subtree_after_rename(in, srcdn->get_dir(), true);
+
+ if (straydn && oldin->is_dir())
+ mdcache->adjust_subtree_after_rename(oldin, destdn->get_dir(), true);
+
+ if (new_oldin_snaprealm)
+ mdcache->do_realm_invalidate_and_update_notify(oldin, CEPH_SNAP_OP_SPLIT, false);
+ if (new_in_snaprealm)
+ mdcache->do_realm_invalidate_and_update_notify(in, CEPH_SNAP_OP_SPLIT, true);
+
+ // removing a new dn?
+ if (srcdn->is_auth())
+ srcdn->get_dir()->try_remove_unlinked_dn(srcdn);
+}
+
+
+
+// ------------
+// PEER
+
+class C_MDS_PeerRenamePrep : public ServerLogContext {
+ CDentry *srcdn, *destdn, *straydn;
+public:
+ C_MDS_PeerRenamePrep(Server *s, MDRequestRef& m, CDentry *sr, CDentry *de, CDentry *st) :
+ ServerLogContext(s, m), srcdn(sr), destdn(de), straydn(st) {}
+ void finish(int r) override {
+ server->_logged_peer_rename(mdr, srcdn, destdn, straydn);
+ }
+};
+
+class C_MDS_PeerRenameCommit : public ServerContext {
+ MDRequestRef mdr;
+ CDentry *srcdn, *destdn, *straydn;
+public:
+ C_MDS_PeerRenameCommit(Server *s, MDRequestRef& m, CDentry *sr, CDentry *de, CDentry *st) :
+ ServerContext(s), mdr(m), srcdn(sr), destdn(de), straydn(st) {}
+ void finish(int r) override {
+ server->_commit_peer_rename(mdr, r, srcdn, destdn, straydn);
+ }
+};
+
+class C_MDS_PeerRenameSessionsFlushed : public ServerContext {
+ MDRequestRef mdr;
+public:
+ C_MDS_PeerRenameSessionsFlushed(Server *s, MDRequestRef& r) :
+ ServerContext(s), mdr(r) {}
+ void finish(int r) override {
+ server->_peer_rename_sessions_flushed(mdr);
+ }
+};
+
+void Server::handle_peer_rename_prep(MDRequestRef& mdr)
+{
+ dout(10) << "handle_peer_rename_prep " << *mdr
+ << " " << mdr->peer_request->srcdnpath
+ << " to " << mdr->peer_request->destdnpath
+ << dendl;
+
+ if (mdr->peer_request->is_interrupted()) {
+ dout(10) << " peer request interrupted, sending noop reply" << dendl;
+ auto reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREPACK);
+ reply->mark_interrupted();
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ mdr->reset_peer_request();
+ return;
+ }
+
+ // discover destdn
+ filepath destpath(mdr->peer_request->destdnpath);
+ dout(10) << " dest " << destpath << dendl;
+ vector<CDentry*> trace;
+ CF_MDS_RetryRequestFactory cf(mdcache, mdr, false);
+ int r = mdcache->path_traverse(mdr, cf, destpath,
+ MDS_TRAVERSE_DISCOVER | MDS_TRAVERSE_PATH_LOCKED | MDS_TRAVERSE_WANT_DENTRY,
+ &trace);
+ if (r > 0) return;
+ if (r == -CEPHFS_ESTALE) {
+ mdcache->find_ino_peers(destpath.get_ino(), new C_MDS_RetryRequest(mdcache, mdr),
+ mdr->peer_to_mds, true);
+ return;
+ }
+ ceph_assert(r == 0); // we shouldn't get an error here!
+
+ CDentry *destdn = trace.back();
+ CDentry::linkage_t *destdnl = destdn->get_projected_linkage();
+ dout(10) << " destdn " << *destdn << dendl;
+ mdr->pin(destdn);
+
+ // discover srcdn
+ filepath srcpath(mdr->peer_request->srcdnpath);
+ dout(10) << " src " << srcpath << dendl;
+ CInode *srci = nullptr;
+ r = mdcache->path_traverse(mdr, cf, srcpath,
+ MDS_TRAVERSE_DISCOVER | MDS_TRAVERSE_PATH_LOCKED,
+ &trace, &srci);
+ if (r > 0) return;
+ ceph_assert(r == 0);
+
+ CDentry *srcdn = trace.back();
+ CDentry::linkage_t *srcdnl = srcdn->get_projected_linkage();
+ dout(10) << " srcdn " << *srcdn << dendl;
+ mdr->pin(srcdn);
+ mdr->pin(srci);
+
+ // stray?
+ bool linkmerge = srcdnl->get_inode() == destdnl->get_inode();
+ if (linkmerge)
+ ceph_assert(srcdnl->is_primary() && destdnl->is_remote());
+ CDentry *straydn = mdr->straydn;
+ if (destdnl->is_primary() && !linkmerge)
+ ceph_assert(straydn);
+
+ mdr->set_op_stamp(mdr->peer_request->op_stamp);
+ mdr->more()->srcdn_auth_mds = srcdn->authority().first;
+
+ // set up commit waiter (early, to clean up any freezing etc we do)
+ if (!mdr->more()->peer_commit)
+ mdr->more()->peer_commit = new C_MDS_PeerRenameCommit(this, mdr, srcdn, destdn, straydn);
+
+ // am i srcdn auth?
+ if (srcdn->is_auth()) {
+ set<mds_rank_t> srcdnrep;
+ srcdn->list_replicas(srcdnrep);
+
+ bool reply_witness = false;
+ if (srcdnl->is_primary() && !srcdnl->get_inode()->state_test(CInode::STATE_AMBIGUOUSAUTH)) {
+ // freeze?
+ // we need this to
+ // - avoid conflicting lock state changes
+ // - avoid concurrent updates to the inode
+ // (this could also be accomplished with the versionlock)
+ int allowance = 3; // 1 for the mdr auth_pin, 1 for the link lock, 1 for the snap lock
+ dout(10) << " freezing srci " << *srcdnl->get_inode() << " with allowance " << allowance << dendl;
+ bool frozen_inode = srcdnl->get_inode()->freeze_inode(allowance);
+
+ // unfreeze auth pin after freezing the inode to avoid queueing waiters
+ if (srcdnl->get_inode()->is_frozen_auth_pin())
+ mdr->unfreeze_auth_pin();
+
+ if (!frozen_inode) {
+ srcdnl->get_inode()->add_waiter(CInode::WAIT_FROZEN, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ /*
+ * set ambiguous auth for srci
+ * NOTE: we don't worry about ambiguous cache expire as we do
+ * with subtree migrations because all peers will pin
+ * srcdn->get_inode() for duration of this rename.
+ */
+ mdr->set_ambiguous_auth(srcdnl->get_inode());
+
+ // just mark the source inode as ambiguous auth if more than two MDS are involved.
+ // the leader will send another OP_RENAMEPREP peer request later.
+ if (mdr->peer_request->witnesses.size() > 1) {
+ dout(10) << " set srci ambiguous auth; providing srcdn replica list" << dendl;
+ reply_witness = true;
+ }
+
+ // make sure bystanders have received all lock related messages
+ for (set<mds_rank_t>::iterator p = srcdnrep.begin(); p != srcdnrep.end(); ++p) {
+ if (*p == mdr->peer_to_mds ||
+ (mds->is_cluster_degraded() &&
+ !mds->mdsmap->is_clientreplay_or_active_or_stopping(*p)))
+ continue;
+ auto notify = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMENOTIFY);
+ mds->send_message_mds(notify, *p);
+ mdr->more()->waiting_on_peer.insert(*p);
+ }
+
+ // make sure clients have received all cap related messages
+ set<client_t> export_client_set;
+ mdcache->migrator->get_export_client_set(srcdnl->get_inode(), export_client_set);
+
+ MDSGatherBuilder gather(g_ceph_context);
+ flush_client_sessions(export_client_set, gather);
+ if (gather.has_subs()) {
+ mdr->more()->waiting_on_peer.insert(MDS_RANK_NONE);
+ gather.set_finisher(new C_MDS_PeerRenameSessionsFlushed(this, mdr));
+ gather.activate();
+ }
+ }
+
+ // is witness list sufficient?
+ for (set<mds_rank_t>::iterator p = srcdnrep.begin(); p != srcdnrep.end(); ++p) {
+ if (*p == mdr->peer_to_mds ||
+ mdr->peer_request->witnesses.count(*p)) continue;
+ dout(10) << " witness list insufficient; providing srcdn replica list" << dendl;
+ reply_witness = true;
+ break;
+ }
+
+ if (reply_witness) {
+ ceph_assert(!srcdnrep.empty());
+ auto reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREPACK);
+ reply->witnesses.swap(srcdnrep);
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ mdr->reset_peer_request();
+ return;
+ }
+ dout(10) << " witness list sufficient: includes all srcdn replicas" << dendl;
+ if (!mdr->more()->waiting_on_peer.empty()) {
+ dout(10) << " still waiting for rename notify acks from "
+ << mdr->more()->waiting_on_peer << dendl;
+ return;
+ }
+ } else if (srcdnl->is_primary() && srcdn->authority() != destdn->authority()) {
+ // set ambiguous auth for srci on witnesses
+ mdr->set_ambiguous_auth(srcdnl->get_inode());
+ }
+
+ // encode everything we'd need to roll this back... basically, just the original state.
+ rename_rollback rollback;
+
+ rollback.reqid = mdr->reqid;
+
+ rollback.orig_src.dirfrag = srcdn->get_dir()->dirfrag();
+ rollback.orig_src.dirfrag_old_mtime = srcdn->get_dir()->get_projected_fnode()->fragstat.mtime;
+ rollback.orig_src.dirfrag_old_rctime = srcdn->get_dir()->get_projected_fnode()->rstat.rctime;
+ rollback.orig_src.dname = srcdn->get_name();
+ if (srcdnl->is_primary())
+ rollback.orig_src.ino = srcdnl->get_inode()->ino();
+ else {
+ ceph_assert(srcdnl->is_remote());
+ rollback.orig_src.remote_ino = srcdnl->get_remote_ino();
+ rollback.orig_src.remote_d_type = srcdnl->get_remote_d_type();
+ }
+
+ rollback.orig_dest.dirfrag = destdn->get_dir()->dirfrag();
+ rollback.orig_dest.dirfrag_old_mtime = destdn->get_dir()->get_projected_fnode()->fragstat.mtime;
+ rollback.orig_dest.dirfrag_old_rctime = destdn->get_dir()->get_projected_fnode()->rstat.rctime;
+ rollback.orig_dest.dname = destdn->get_name();
+ if (destdnl->is_primary())
+ rollback.orig_dest.ino = destdnl->get_inode()->ino();
+ else if (destdnl->is_remote()) {
+ rollback.orig_dest.remote_ino = destdnl->get_remote_ino();
+ rollback.orig_dest.remote_d_type = destdnl->get_remote_d_type();
+ }
+
+ if (straydn) {
+ rollback.stray.dirfrag = straydn->get_dir()->dirfrag();
+ rollback.stray.dirfrag_old_mtime = straydn->get_dir()->get_projected_fnode()->fragstat.mtime;
+ rollback.stray.dirfrag_old_rctime = straydn->get_dir()->get_projected_fnode()->rstat.rctime;
+ rollback.stray.dname = straydn->get_name();
+ }
+ if (mdr->peer_request->desti_snapbl.length()) {
+ CInode *oldin = destdnl->get_inode();
+ if (oldin->snaprealm) {
+ encode(true, rollback.desti_snapbl);
+ oldin->encode_snap_blob(rollback.desti_snapbl);
+ } else {
+ encode(false, rollback.desti_snapbl);
+ }
+ }
+ if (mdr->peer_request->srci_snapbl.length()) {
+ if (srci->snaprealm) {
+ encode(true, rollback.srci_snapbl);
+ srci->encode_snap_blob(rollback.srci_snapbl);
+ } else {
+ encode(false, rollback.srci_snapbl);
+ }
+ }
+ encode(rollback, mdr->more()->rollback_bl);
+ // FIXME: rollback snaprealm
+ dout(20) << " rollback is " << mdr->more()->rollback_bl.length() << " bytes" << dendl;
+
+ // journal.
+ mdr->ls = mdlog->get_current_segment();
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rename_prep", mdr->reqid, mdr->peer_to_mds,
+ EPeerUpdate::OP_PREPARE, EPeerUpdate::RENAME);
+ mdlog->start_entry(le);
+ le->rollback = mdr->more()->rollback_bl;
+
+ bufferlist blah; // inode import data... obviously not used if we're the peer
+ _rename_prepare(mdr, &le->commit, &blah, srcdn, destdn, mdr->peer_request->alternate_name, straydn);
+
+ if (le->commit.empty()) {
+ dout(10) << " empty metablob, skipping journal" << dendl;
+ mdlog->cancel_entry(le);
+ mdr->ls = NULL;
+ _logged_peer_rename(mdr, srcdn, destdn, straydn);
+ } else {
+ mdcache->add_uncommitted_peer(mdr->reqid, mdr->ls, mdr->peer_to_mds);
+ mdr->more()->peer_update_journaled = true;
+ submit_mdlog_entry(le, new C_MDS_PeerRenamePrep(this, mdr, srcdn, destdn, straydn),
+ mdr, __func__);
+ mdlog->flush();
+ }
+}
+
+void Server::_logged_peer_rename(MDRequestRef& mdr,
+ CDentry *srcdn, CDentry *destdn, CDentry *straydn)
+{
+ dout(10) << "_logged_peer_rename " << *mdr << dendl;
+
+ // prepare ack
+ ref_t<MMDSPeerRequest> reply;
+ if (!mdr->aborted) {
+ reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREPACK);
+ if (!mdr->more()->peer_update_journaled)
+ reply->mark_not_journaled();
+ }
+
+ CDentry::linkage_t *srcdnl = srcdn->get_linkage();
+ //CDentry::linkage_t *straydnl = straydn ? straydn->get_linkage() : 0;
+
+ // export srci?
+ if (srcdn->is_auth() && srcdnl->is_primary()) {
+ // set export bounds for CInode::encode_export()
+ if (reply) {
+ std::vector<CDir*> bounds;
+ if (srcdnl->get_inode()->is_dir()) {
+ srcdnl->get_inode()->get_dirfrags(bounds);
+ for (const auto& bound : bounds) {
+ bound->state_set(CDir::STATE_EXPORTBOUND);
+ }
+ }
+
+ map<client_t,entity_inst_t> exported_client_map;
+ map<client_t, client_metadata_t> exported_client_metadata_map;
+ bufferlist inodebl;
+ mdcache->migrator->encode_export_inode(srcdnl->get_inode(), inodebl,
+ exported_client_map,
+ exported_client_metadata_map);
+
+ for (const auto& bound : bounds) {
+ bound->state_clear(CDir::STATE_EXPORTBOUND);
+ }
+
+ encode(exported_client_map, reply->inode_export, mds->mdsmap->get_up_features());
+ encode(exported_client_metadata_map, reply->inode_export);
+ reply->inode_export.claim_append(inodebl);
+ reply->inode_export_v = srcdnl->get_inode()->get_version();
+ }
+
+ // remove mdr auth pin
+ mdr->auth_unpin(srcdnl->get_inode());
+ mdr->more()->is_inode_exporter = true;
+
+ if (srcdnl->get_inode()->is_dirty())
+ srcdnl->get_inode()->mark_clean();
+
+ dout(10) << " exported srci " << *srcdnl->get_inode() << dendl;
+ }
+
+ // apply
+ _rename_apply(mdr, srcdn, destdn, straydn);
+
+ CDentry::linkage_t *destdnl = destdn->get_linkage();
+
+ // bump popularity
+ mds->balancer->hit_dir(srcdn->get_dir(), META_POP_IWR);
+ if (destdnl->get_inode() && destdnl->get_inode()->is_auth())
+ mds->balancer->hit_inode(destdnl->get_inode(), META_POP_IWR);
+
+ // done.
+ mdr->reset_peer_request();
+ mdr->straydn = 0;
+
+ if (reply) {
+ mds->send_message_mds(reply, mdr->peer_to_mds);
+ } else {
+ ceph_assert(mdr->aborted);
+ dout(10) << " abort flag set, finishing" << dendl;
+ mdcache->request_finish(mdr);
+ }
+}
+
+void Server::_commit_peer_rename(MDRequestRef& mdr, int r,
+ CDentry *srcdn, CDentry *destdn, CDentry *straydn)
+{
+ dout(10) << "_commit_peer_rename " << *mdr << " r=" << r << dendl;
+
+ CInode *in = destdn->get_linkage()->get_inode();
+
+ inodeno_t migrated_stray;
+ if (srcdn->is_auth() && srcdn->get_dir()->inode->is_stray())
+ migrated_stray = in->ino();
+
+ MDSContext::vec finished;
+ if (r == 0) {
+ // unfreeze+singleauth inode
+ // hmm, do i really need to delay this?
+ if (mdr->more()->is_inode_exporter) {
+ // drop our pins
+ // we exported, clear out any xlocks that we moved to another MDS
+
+ for (auto i = mdr->locks.lower_bound(&in->versionlock);
+ i != mdr->locks.end(); ) {
+ SimpleLock *lock = i->lock;
+ if (lock->get_parent() != in)
+ break;
+ // we only care about xlocks on the exported inode
+ if (i->is_xlock() && !lock->is_locallock())
+ mds->locker->xlock_export(i++, mdr.get());
+ else
+ ++i;
+ }
+
+ map<client_t,Capability::Import> peer_imported;
+ auto bp = mdr->more()->inode_import.cbegin();
+ decode(peer_imported, bp);
+
+ dout(10) << " finishing inode export on " << *in << dendl;
+ mdcache->migrator->finish_export_inode(in, mdr->peer_to_mds, peer_imported, finished);
+ mds->queue_waiters(finished); // this includes SINGLEAUTH waiters.
+
+ // unfreeze
+ ceph_assert(in->is_frozen_inode());
+ in->unfreeze_inode(finished);
+ }
+
+ // singleauth
+ if (mdr->more()->is_ambiguous_auth) {
+ mdr->more()->rename_inode->clear_ambiguous_auth(finished);
+ mdr->more()->is_ambiguous_auth = false;
+ }
+
+ if (straydn && mdr->more()->peer_update_journaled) {
+ CInode *strayin = straydn->get_projected_linkage()->get_inode();
+ if (strayin && !strayin->snaprealm)
+ mdcache->clear_dirty_bits_for_stray(strayin);
+ }
+
+ mds->queue_waiters(finished);
+ mdr->cleanup();
+
+ if (mdr->more()->peer_update_journaled) {
+ // write a commit to the journal
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rename_commit", mdr->reqid,
+ mdr->peer_to_mds, EPeerUpdate::OP_COMMIT,
+ EPeerUpdate::RENAME);
+ mdlog->start_entry(le);
+ submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
+ mdlog->flush();
+ } else {
+ _committed_peer(mdr);
+ }
+ } else {
+
+ // abort
+ // rollback_bl may be empty if we froze the inode but had to provide an expanded
+ // witness list from the leader, and they failed before we tried prep again.
+ if (mdr->more()->rollback_bl.length()) {
+ if (mdr->more()->is_inode_exporter) {
+ dout(10) << " reversing inode export of " << *in << dendl;
+ in->abort_export();
+ }
+ if (mdcache->is_ambiguous_peer_update(mdr->reqid, mdr->peer_to_mds)) {
+ mdcache->remove_ambiguous_peer_update(mdr->reqid, mdr->peer_to_mds);
+ // rollback but preserve the peer request
+ do_rename_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr, false);
+ mdr->more()->rollback_bl.clear();
+ } else
+ do_rename_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr, true);
+ } else {
+ dout(10) << " rollback_bl empty, not rollback back rename (leader failed after getting extra witnesses?)" << dendl;
+ // singleauth
+ if (mdr->more()->is_ambiguous_auth) {
+ if (srcdn->is_auth())
+ mdr->more()->rename_inode->unfreeze_inode(finished);
+
+ mdr->more()->rename_inode->clear_ambiguous_auth(finished);
+ mdr->more()->is_ambiguous_auth = false;
+ }
+ mds->queue_waiters(finished);
+ mdcache->request_finish(mdr);
+ }
+ }
+
+ if (migrated_stray && mds->is_stopping())
+ mdcache->shutdown_export_stray_finish(migrated_stray);
+}
+
+static void _rollback_repair_dir(MutationRef& mut, CDir *dir,
+ rename_rollback::drec &r, utime_t ctime,
+ bool isdir, const nest_info_t &rstat)
+{
+ auto pf = dir->project_fnode(mut);
+ pf->version = dir->pre_dirty();
+
+ if (isdir) {
+ pf->fragstat.nsubdirs += 1;
+ } else {
+ pf->fragstat.nfiles += 1;
+ }
+ if (r.ino) {
+ pf->rstat.rbytes += rstat.rbytes;
+ pf->rstat.rfiles += rstat.rfiles;
+ pf->rstat.rsubdirs += rstat.rsubdirs;
+ pf->rstat.rsnaps += rstat.rsnaps;
+ }
+ if (pf->fragstat.mtime == ctime) {
+ pf->fragstat.mtime = r.dirfrag_old_mtime;
+ if (pf->rstat.rctime == ctime)
+ pf->rstat.rctime = r.dirfrag_old_rctime;
+ }
+ mut->add_updated_lock(&dir->get_inode()->filelock);
+ mut->add_updated_lock(&dir->get_inode()->nestlock);
+}
+
+struct C_MDS_LoggedRenameRollback : public ServerLogContext {
+ MutationRef mut;
+ CDentry *srcdn;
+ version_t srcdnpv;
+ CDentry *destdn;
+ CDentry *straydn;
+ map<client_t,ref_t<MClientSnap>> splits[2];
+ bool finish_mdr;
+ C_MDS_LoggedRenameRollback(Server *s, MutationRef& m, MDRequestRef& r,
+ CDentry *sd, version_t pv, CDentry *dd, CDentry *st,
+ map<client_t,ref_t<MClientSnap>> _splits[2], bool f) :
+ ServerLogContext(s, r), mut(m), srcdn(sd), srcdnpv(pv), destdn(dd),
+ straydn(st), finish_mdr(f) {
+ splits[0].swap(_splits[0]);
+ splits[1].swap(_splits[1]);
+ }
+ void finish(int r) override {
+ server->_rename_rollback_finish(mut, mdr, srcdn, srcdnpv,
+ destdn, straydn, splits, finish_mdr);
+ }
+};
+
+void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t leader, MDRequestRef& mdr,
+ bool finish_mdr)
+{
+ rename_rollback rollback;
+ auto p = rbl.cbegin();
+ decode(rollback, p);
+
+ dout(10) << "do_rename_rollback on " << rollback.reqid << dendl;
+ // need to finish this update before sending resolve to claim the subtree
+ mdcache->add_rollback(rollback.reqid, leader);
+
+ MutationRef mut(new MutationImpl(nullptr, utime_t(), rollback.reqid));
+ mut->ls = mds->mdlog->get_current_segment();
+
+ CDentry *srcdn = NULL;
+ CDir *srcdir = mdcache->get_dirfrag(rollback.orig_src.dirfrag);
+ if (!srcdir)
+ srcdir = mdcache->get_dirfrag(rollback.orig_src.dirfrag.ino, rollback.orig_src.dname);
+ if (srcdir) {
+ dout(10) << " srcdir " << *srcdir << dendl;
+ srcdn = srcdir->lookup(rollback.orig_src.dname);
+ if (srcdn) {
+ dout(10) << " srcdn " << *srcdn << dendl;
+ ceph_assert(srcdn->get_linkage()->is_null());
+ } else
+ dout(10) << " srcdn not found" << dendl;
+ } else
+ dout(10) << " srcdir not found" << dendl;
+
+ CDentry *destdn = NULL;
+ CDir *destdir = mdcache->get_dirfrag(rollback.orig_dest.dirfrag);
+ if (!destdir)
+ destdir = mdcache->get_dirfrag(rollback.orig_dest.dirfrag.ino, rollback.orig_dest.dname);
+ if (destdir) {
+ dout(10) << " destdir " << *destdir << dendl;
+ destdn = destdir->lookup(rollback.orig_dest.dname);
+ if (destdn)
+ dout(10) << " destdn " << *destdn << dendl;
+ else
+ dout(10) << " destdn not found" << dendl;
+ } else
+ dout(10) << " destdir not found" << dendl;
+
+ CInode *in = NULL;
+ if (rollback.orig_src.ino) {
+ in = mdcache->get_inode(rollback.orig_src.ino);
+ if (in && in->is_dir())
+ ceph_assert(srcdn && destdn);
+ } else
+ in = mdcache->get_inode(rollback.orig_src.remote_ino);
+
+ CDir *straydir = NULL;
+ CDentry *straydn = NULL;
+ if (rollback.stray.dirfrag.ino) {
+ straydir = mdcache->get_dirfrag(rollback.stray.dirfrag);
+ if (straydir) {
+ dout(10) << "straydir " << *straydir << dendl;
+ straydn = straydir->lookup(rollback.stray.dname);
+ if (straydn) {
+ dout(10) << " straydn " << *straydn << dendl;
+ ceph_assert(straydn->get_linkage()->is_primary());
+ } else
+ dout(10) << " straydn not found" << dendl;
+ } else
+ dout(10) << "straydir not found" << dendl;
+ }
+
+ CInode *target = NULL;
+ if (rollback.orig_dest.ino) {
+ target = mdcache->get_inode(rollback.orig_dest.ino);
+ if (target)
+ ceph_assert(destdn && straydn);
+ } else if (rollback.orig_dest.remote_ino)
+ target = mdcache->get_inode(rollback.orig_dest.remote_ino);
+
+ // can't use is_auth() in the resolve stage
+ mds_rank_t whoami = mds->get_nodeid();
+ // peer
+ ceph_assert(!destdn || destdn->authority().first != whoami);
+ ceph_assert(!straydn || straydn->authority().first != whoami);
+
+ bool force_journal_src = false;
+ bool force_journal_dest = false;
+ if (in && in->is_dir() && srcdn->authority().first != whoami)
+ force_journal_src = _need_force_journal(in, false);
+ if (in && target && target->is_dir())
+ force_journal_dest = _need_force_journal(in, true);
+
+ version_t srcdnpv = 0;
+ // repair src
+ if (srcdn) {
+ if (srcdn->authority().first == whoami)
+ srcdnpv = srcdn->pre_dirty();
+ if (rollback.orig_src.ino) {
+ ceph_assert(in);
+ srcdn->push_projected_linkage(in);
+ } else
+ srcdn->push_projected_linkage(rollback.orig_src.remote_ino,
+ rollback.orig_src.remote_d_type);
+ }
+
+ map<client_t,ref_t<MClientSnap>> splits[2];
+
+ const CInode::mempool_inode *pip = nullptr;
+ if (in) {
+ bool projected;
+ CDir *pdir = in->get_projected_parent_dir();
+ if (pdir->authority().first == whoami) {
+ auto pi = in->project_inode(mut);
+ pi.inode->version = in->pre_dirty();
+ if (pdir != srcdir) {
+ auto pf = pdir->project_fnode(mut);
+ pf->version = pdir->pre_dirty();
+ }
+ if (pi.inode->ctime == rollback.ctime)
+ pi.inode->ctime = rollback.orig_src.old_ctime;
+ projected = true;
+ } else {
+ if (in->get_inode()->ctime == rollback.ctime) {
+ auto _inode = CInode::allocate_inode(*in->get_inode());
+ _inode->ctime = rollback.orig_src.old_ctime;
+ in->reset_inode(_inode);
+ }
+ projected = false;
+ }
+ pip = in->get_projected_inode().get();
+
+ if (rollback.srci_snapbl.length() && in->snaprealm) {
+ bool hadrealm;
+ auto p = rollback.srci_snapbl.cbegin();
+ decode(hadrealm, p);
+ if (hadrealm) {
+ if (projected && !mds->is_resolve()) {
+ sr_t *new_srnode = new sr_t();
+ decode(*new_srnode, p);
+ in->project_snaprealm(new_srnode);
+ } else
+ decode(in->snaprealm->srnode, p);
+ } else {
+ SnapRealm *realm;
+ if (rollback.orig_src.ino) {
+ ceph_assert(srcdir);
+ realm = srcdir->get_inode()->find_snaprealm();
+ } else {
+ realm = in->snaprealm->parent;
+ }
+ if (!mds->is_resolve())
+ mdcache->prepare_realm_merge(in->snaprealm, realm, splits[0]);
+ if (projected)
+ in->project_snaprealm(NULL);
+ else
+ in->snaprealm->merge_to(realm);
+ }
+ }
+ }
+
+ // repair dest
+ if (destdn) {
+ if (rollback.orig_dest.ino && target) {
+ destdn->push_projected_linkage(target);
+ } else if (rollback.orig_dest.remote_ino) {
+ destdn->push_projected_linkage(rollback.orig_dest.remote_ino,
+ rollback.orig_dest.remote_d_type);
+ } else {
+ // the dentry will be trimmed soon, it's ok to have wrong linkage
+ if (rollback.orig_dest.ino)
+ ceph_assert(mds->is_resolve());
+ destdn->push_projected_linkage();
+ }
+ }
+
+ if (straydn)
+ straydn->push_projected_linkage();
+
+ if (target) {
+ bool projected;
+ CInode::inode_ptr ti;
+ CDir *pdir = target->get_projected_parent_dir();
+ if (pdir->authority().first == whoami) {
+ auto pi = target->project_inode(mut);
+ pi.inode->version = target->pre_dirty();
+ if (pdir != srcdir) {
+ auto pf = pdir->project_fnode(mut);
+ pf->version = pdir->pre_dirty();
+ }
+ ti = pi.inode;
+ projected = true;
+ } else {
+ ti = CInode::allocate_inode(*target->get_inode());
+ projected = false;
+ }
+
+ if (ti->ctime == rollback.ctime)
+ ti->ctime = rollback.orig_dest.old_ctime;
+ if (MDS_INO_IS_STRAY(rollback.orig_src.dirfrag.ino)) {
+ if (MDS_INO_IS_STRAY(rollback.orig_dest.dirfrag.ino))
+ ceph_assert(!rollback.orig_dest.ino && !rollback.orig_dest.remote_ino);
+ else
+ ceph_assert(rollback.orig_dest.remote_ino &&
+ rollback.orig_dest.remote_ino == rollback.orig_src.ino);
+ } else
+ ti->nlink++;
+
+ if (!projected)
+ target->reset_inode(ti);
+
+ if (rollback.desti_snapbl.length() && target->snaprealm) {
+ bool hadrealm;
+ auto p = rollback.desti_snapbl.cbegin();
+ decode(hadrealm, p);
+ if (hadrealm) {
+ if (projected && !mds->is_resolve()) {
+ sr_t *new_srnode = new sr_t();
+ decode(*new_srnode, p);
+ target->project_snaprealm(new_srnode);
+ } else
+ decode(target->snaprealm->srnode, p);
+ } else {
+ SnapRealm *realm;
+ if (rollback.orig_dest.ino) {
+ ceph_assert(destdir);
+ realm = destdir->get_inode()->find_snaprealm();
+ } else {
+ realm = target->snaprealm->parent;
+ }
+ if (!mds->is_resolve())
+ mdcache->prepare_realm_merge(target->snaprealm, realm, splits[1]);
+ if (projected)
+ target->project_snaprealm(NULL);
+ else
+ target->snaprealm->merge_to(realm);
+ }
+ }
+ }
+
+ if (srcdn && srcdn->authority().first == whoami) {
+ nest_info_t blah;
+ _rollback_repair_dir(mut, srcdir, rollback.orig_src, rollback.ctime,
+ in && in->is_dir(), pip ? pip->accounted_rstat : blah);
+ }
+
+ if (srcdn)
+ dout(0) << " srcdn back to " << *srcdn << dendl;
+ if (in)
+ dout(0) << " srci back to " << *in << dendl;
+ if (destdn)
+ dout(0) << " destdn back to " << *destdn << dendl;
+ if (target)
+ dout(0) << " desti back to " << *target << dendl;
+
+ // journal it
+ EPeerUpdate *le = new EPeerUpdate(mdlog, "peer_rename_rollback", rollback.reqid, leader,
+ EPeerUpdate::OP_ROLLBACK, EPeerUpdate::RENAME);
+ mdlog->start_entry(le);
+
+ if (srcdn && (srcdn->authority().first == whoami || force_journal_src)) {
+ le->commit.add_dir_context(srcdir);
+ if (rollback.orig_src.ino)
+ le->commit.add_primary_dentry(srcdn, 0, true);
+ else
+ le->commit.add_remote_dentry(srcdn, true);
+ }
+
+ if (!rollback.orig_src.ino && // remote linkage
+ in && in->authority().first == whoami) {
+ le->commit.add_dir_context(in->get_projected_parent_dir());
+ le->commit.add_primary_dentry(in->get_projected_parent_dn(), in, true);
+ }
+
+ if (force_journal_dest) {
+ ceph_assert(rollback.orig_dest.ino);
+ le->commit.add_dir_context(destdir);
+ le->commit.add_primary_dentry(destdn, 0, true);
+ }
+
+ // peer: no need to journal straydn
+
+ if (target && target != in && target->authority().first == whoami) {
+ ceph_assert(rollback.orig_dest.remote_ino);
+ le->commit.add_dir_context(target->get_projected_parent_dir());
+ le->commit.add_primary_dentry(target->get_projected_parent_dn(), target, true);
+ }
+
+ if (in && in->is_dir() && (srcdn->authority().first == whoami || force_journal_src)) {
+ dout(10) << " noting renamed dir ino " << in->ino() << " in metablob" << dendl;
+ le->commit.renamed_dirino = in->ino();
+ if (srcdn->authority().first == whoami) {
+ auto&& ls = in->get_dirfrags();
+ for (const auto& dir : ls) {
+ if (!dir->is_auth())
+ le->commit.renamed_dir_frags.push_back(dir->get_frag());
+ }
+ dout(10) << " noting renamed dir open frags " << le->commit.renamed_dir_frags << dendl;
+ }
+ } else if (force_journal_dest) {
+ dout(10) << " noting rename target ino " << target->ino() << " in metablob" << dendl;
+ le->commit.renamed_dirino = target->ino();
+ }
+
+ if (target && target->is_dir()) {
+ ceph_assert(destdn);
+ mdcache->project_subtree_rename(target, straydir, destdir);
+ }
+
+ if (in && in->is_dir()) {
+ ceph_assert(srcdn);
+ mdcache->project_subtree_rename(in, destdir, srcdir);
+ }
+
+ if (mdr && !mdr->more()->peer_update_journaled) {
+ ceph_assert(le->commit.empty());
+ mdlog->cancel_entry(le);
+ mut->ls = NULL;
+ _rename_rollback_finish(mut, mdr, srcdn, srcdnpv, destdn, straydn, splits, finish_mdr);
+ } else {
+ ceph_assert(!le->commit.empty());
+ if (mdr)
+ mdr->more()->peer_update_journaled = false;
+ MDSLogContextBase *fin = new C_MDS_LoggedRenameRollback(this, mut, mdr,
+ srcdn, srcdnpv, destdn, straydn,
+ splits, finish_mdr);
+ submit_mdlog_entry(le, fin, mdr, __func__);
+ mdlog->flush();
+ }
+}
+
+void Server::_rename_rollback_finish(MutationRef& mut, MDRequestRef& mdr, CDentry *srcdn,
+ version_t srcdnpv, CDentry *destdn, CDentry *straydn,
+ map<client_t,ref_t<MClientSnap>> splits[2], bool finish_mdr)
+{
+ dout(10) << "_rename_rollback_finish " << mut->reqid << dendl;
+
+ if (straydn) {
+ straydn->get_dir()->unlink_inode(straydn);
+ straydn->pop_projected_linkage();
+ }
+ if (destdn) {
+ destdn->get_dir()->unlink_inode(destdn);
+ destdn->pop_projected_linkage();
+ }
+ if (srcdn) {
+ srcdn->pop_projected_linkage();
+ if (srcdn->authority().first == mds->get_nodeid()) {
+ srcdn->mark_dirty(srcdnpv, mut->ls);
+ if (srcdn->get_linkage()->is_primary())
+ srcdn->get_linkage()->get_inode()->state_set(CInode::STATE_AUTH);
+ }
+ }
+
+ mut->apply();
+
+ if (srcdn && srcdn->get_linkage()->is_primary()) {
+ CInode *in = srcdn->get_linkage()->get_inode();
+ if (in && in->is_dir()) {
+ ceph_assert(destdn);
+ mdcache->adjust_subtree_after_rename(in, destdn->get_dir(), true);
+ }
+ }
+
+ if (destdn) {
+ CInode *oldin = destdn->get_linkage()->get_inode();
+ // update subtree map?
+ if (oldin && oldin->is_dir()) {
+ ceph_assert(straydn);
+ mdcache->adjust_subtree_after_rename(oldin, straydn->get_dir(), true);
+ }
+ }
+
+ if (mds->is_resolve()) {
+ CDir *root = NULL;
+ if (straydn)
+ root = mdcache->get_subtree_root(straydn->get_dir());
+ else if (destdn)
+ root = mdcache->get_subtree_root(destdn->get_dir());
+ if (root)
+ mdcache->try_trim_non_auth_subtree(root);
+ } else {
+ mdcache->send_snaps(splits[1]);
+ mdcache->send_snaps(splits[0]);
+ }
+
+ if (mdr) {
+ MDSContext::vec finished;
+ if (mdr->more()->is_ambiguous_auth) {
+ if (srcdn->is_auth())
+ mdr->more()->rename_inode->unfreeze_inode(finished);
+
+ mdr->more()->rename_inode->clear_ambiguous_auth(finished);
+ mdr->more()->is_ambiguous_auth = false;
+ }
+ mds->queue_waiters(finished);
+ if (finish_mdr || mdr->aborted)
+ mdcache->request_finish(mdr);
+ else
+ mdr->more()->peer_rolling_back = false;
+ }
+
+ mdcache->finish_rollback(mut->reqid, mdr);
+
+ mut->cleanup();
+}
+
+void Server::handle_peer_rename_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
+{
+ dout(10) << "handle_peer_rename_prep_ack " << *mdr
+ << " witnessed by " << ack->get_source()
+ << " " << *ack << dendl;
+ mds_rank_t from = mds_rank_t(ack->get_source().num());
+
+ // note peer
+ mdr->more()->peers.insert(from);
+ if (mdr->more()->srcdn_auth_mds == from &&
+ mdr->more()->is_remote_frozen_authpin &&
+ !mdr->more()->is_ambiguous_auth) {
+ mdr->set_ambiguous_auth(mdr->more()->rename_inode);
+ }
+
+ // witnessed? or add extra witnesses?
+ ceph_assert(mdr->more()->witnessed.count(from) == 0);
+ if (ack->is_interrupted()) {
+ dout(10) << " peer request interrupted, noop" << dendl;
+ } else if (ack->witnesses.empty()) {
+ mdr->more()->witnessed.insert(from);
+ if (!ack->is_not_journaled())
+ mdr->more()->has_journaled_peers = true;
+ } else {
+ dout(10) << " extra witnesses (srcdn replicas) are " << ack->witnesses << dendl;
+ mdr->more()->extra_witnesses = ack->witnesses;
+ mdr->more()->extra_witnesses.erase(mds->get_nodeid()); // not me!
+ }
+
+ // srci import?
+ if (ack->inode_export.length()) {
+ dout(10) << " got srci import" << dendl;
+ mdr->more()->inode_import.share(ack->inode_export);
+ mdr->more()->inode_import_v = ack->inode_export_v;
+ }
+
+ // remove from waiting list
+ ceph_assert(mdr->more()->waiting_on_peer.count(from));
+ mdr->more()->waiting_on_peer.erase(from);
+
+ if (mdr->more()->waiting_on_peer.empty())
+ dispatch_client_request(mdr); // go again!
+ else
+ dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
+}
+
+void Server::handle_peer_rename_notify_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
+{
+ dout(10) << "handle_peer_rename_notify_ack " << *mdr << " from mds."
+ << ack->get_source() << dendl;
+ ceph_assert(mdr->is_peer());
+ mds_rank_t from = mds_rank_t(ack->get_source().num());
+
+ if (mdr->more()->waiting_on_peer.count(from)) {
+ mdr->more()->waiting_on_peer.erase(from);
+
+ if (mdr->more()->waiting_on_peer.empty()) {
+ if (mdr->peer_request)
+ dispatch_peer_request(mdr);
+ } else
+ dout(10) << " still waiting for rename notify acks from "
+ << mdr->more()->waiting_on_peer << dendl;
+ }
+}
+
+void Server::_peer_rename_sessions_flushed(MDRequestRef& mdr)
+{
+ dout(10) << "_peer_rename_sessions_flushed " << *mdr << dendl;
+
+ if (mdr->more()->waiting_on_peer.count(MDS_RANK_NONE)) {
+ mdr->more()->waiting_on_peer.erase(MDS_RANK_NONE);
+
+ if (mdr->more()->waiting_on_peer.empty()) {
+ if (mdr->peer_request)
+ dispatch_peer_request(mdr);
+ } else
+ dout(10) << " still waiting for rename notify acks from "
+ << mdr->more()->waiting_on_peer << dendl;
+ }
+}
+
+// snaps
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_lssnap(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ // traverse to path
+ CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!diri)
+ return;
+
+ if (!diri->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+ dout(10) << "lssnap on " << *diri << dendl;
+
+ // lock snap
+ if (!mds->locker->try_rdlock_snap_layout(diri, mdr))
+ return;
+
+ if (!check_access(mdr, diri, MAY_READ))
+ return;
+
+ SnapRealm *realm = diri->find_snaprealm();
+ map<snapid_t,const SnapInfo*> infomap;
+ realm->get_snap_info(infomap, diri->get_oldest_snap());
+
+ unsigned max_entries = req->head.args.readdir.max_entries;
+ if (!max_entries)
+ max_entries = infomap.size();
+ int max_bytes = req->head.args.readdir.max_bytes;
+ if (!max_bytes)
+ // make sure at least one item can be encoded
+ max_bytes = (512 << 10) + g_conf()->mds_max_xattr_pairs_size;
+
+ __u64 last_snapid = 0;
+ string offset_str = req->get_path2();
+ if (!offset_str.empty())
+ last_snapid = realm->resolve_snapname(offset_str, diri->ino());
+
+ //Empty DirStat
+ bufferlist dirbl;
+ static DirStat empty;
+ CDir::encode_dirstat(dirbl, mdr->session->info, empty);
+
+ max_bytes -= dirbl.length() - sizeof(__u32) + sizeof(__u8) * 2;
+
+ __u32 num = 0;
+ bufferlist dnbl;
+ auto p = infomap.upper_bound(last_snapid);
+ for (; p != infomap.end() && num < max_entries; ++p) {
+ dout(10) << p->first << " -> " << *p->second << dendl;
+
+ // actual
+ string snap_name;
+ if (p->second->ino == diri->ino())
+ snap_name = p->second->name;
+ else
+ snap_name = p->second->get_long_name();
+
+ unsigned start_len = dnbl.length();
+ if (int(start_len + snap_name.length() + sizeof(__u32) + sizeof(LeaseStat)) > max_bytes)
+ break;
+
+ encode(snap_name, dnbl);
+ //infinite lease
+ LeaseStat e(CEPH_LEASE_VALID, -1, 0);
+ mds->locker->encode_lease(dnbl, mdr->session->info, e);
+ dout(20) << "encode_infinite_lease" << dendl;
+
+ int r = diri->encode_inodestat(dnbl, mdr->session, realm, p->first, max_bytes - (int)dnbl.length());
+ if (r < 0) {
+ bufferlist keep;
+ keep.substr_of(dnbl, 0, start_len);
+ dnbl.swap(keep);
+ break;
+ }
+ ++num;
+ }
+
+ encode(num, dirbl);
+ __u16 flags = 0;
+ if (p == infomap.end()) {
+ flags = CEPH_READDIR_FRAG_END;
+ if (last_snapid == 0)
+ flags |= CEPH_READDIR_FRAG_COMPLETE;
+ }
+ encode(flags, dirbl);
+ dirbl.claim_append(dnbl);
+
+ mdr->reply_extra_bl = dirbl;
+ mdr->tracei = diri;
+ respond_to_request(mdr, 0);
+}
+
+
+// MKSNAP
+
+struct C_MDS_mksnap_finish : public ServerLogContext {
+ CInode *diri;
+ SnapInfo info;
+ C_MDS_mksnap_finish(Server *s, MDRequestRef& r, CInode *di, SnapInfo &i) :
+ ServerLogContext(s, r), diri(di), info(i) {}
+ void finish(int r) override {
+ server->_mksnap_finish(mdr, diri, info);
+ }
+};
+
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_mksnap(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ // make sure we have as new a map as the client
+ if (req->get_mdsmap_epoch() > mds->mdsmap->get_epoch()) {
+ mds->wait_for_mdsmap(req->get_mdsmap_epoch(), new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ if (!mds->mdsmap->allows_snaps()) {
+ // you can't make snapshots until you set an option right now
+ dout(5) << "new snapshots are disabled for this fs" << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!diri)
+ return;
+
+ // dir only
+ if (!diri->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+ if (diri->is_system() && !diri->is_root()) {
+ // no snaps in system dirs (root is ok)
+ dout(5) << "is an internal system dir" << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ std::string_view snapname = req->get_filepath().last_dentry();
+
+ if (mdr->client_request->get_caller_uid() < g_conf()->mds_snap_min_uid || mdr->client_request->get_caller_uid() > g_conf()->mds_snap_max_uid) {
+ dout(20) << "mksnap " << snapname << " on " << *diri << " denied to uid " << mdr->client_request->get_caller_uid() << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ dout(10) << "mksnap " << snapname << " on " << *diri << dendl;
+
+ // lock snap
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&diri->snaplock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (CDentry *pdn = diri->get_projected_parent_dn(); pdn) {
+ if (!mds->locker->try_rdlock_snap_layout(pdn->get_dir()->get_inode(), mdr))
+ return;
+ }
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (!check_access(mdr, diri, MAY_WRITE|MAY_SNAPSHOT))
+ return;
+
+ if (inodeno_t subvol_ino = diri->find_snaprealm()->get_subvolume_ino();
+ (subvol_ino && subvol_ino != diri->ino())) {
+ dout(5) << "is a descendent of a subvolume dir" << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ // check if we can create any more snapshots
+ // we don't allow any more if we are already at or beyond the limit
+ if (diri->snaprealm &&
+ diri->snaprealm->get_snaps().size() >= max_snaps_per_dir) {
+ respond_to_request(mdr, -CEPHFS_EMLINK);
+ return;
+ }
+
+ // make sure name is unique
+ if (diri->snaprealm &&
+ diri->snaprealm->exists(snapname)) {
+ respond_to_request(mdr, -CEPHFS_EEXIST);
+ return;
+ }
+ if (snapname.length() == 0 ||
+ snapname.length() > snapshot_name_max ||
+ snapname[0] == '_') {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ // allocate a snapid
+ if (!mdr->more()->stid) {
+ // prepare an stid
+ mds->snapclient->prepare_create(diri->ino(), snapname,
+ mdr->get_mds_stamp(),
+ &mdr->more()->stid, &mdr->more()->snapidbl,
+ new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ version_t stid = mdr->more()->stid;
+ snapid_t snapid;
+ auto p = mdr->more()->snapidbl.cbegin();
+ decode(snapid, p);
+ dout(10) << " stid " << stid << " snapid " << snapid << dendl;
+
+ ceph_assert(mds->snapclient->get_cached_version() >= stid);
+
+ SnapPayload payload;
+ if (req->get_data().length()) {
+ try {
+ auto iter = req->get_data().cbegin();
+ decode(payload, iter);
+ } catch (const ceph::buffer::error &e) {
+ // backward compat -- client sends xattr bufferlist. however,
+ // that is not used anywhere -- so (log and) ignore.
+ dout(20) << ": no metadata in payload (old client?)" << dendl;
+ }
+ }
+
+ // journal
+ SnapInfo info;
+ info.ino = diri->ino();
+ info.snapid = snapid;
+ info.name = snapname;
+ info.stamp = mdr->get_op_stamp();
+ info.metadata = payload.metadata;
+
+ auto pi = diri->project_inode(mdr, false, true);
+ pi.inode->ctime = info.stamp;
+ if (info.stamp > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = info.stamp;
+ pi.inode->rstat.rsnaps++;
+ pi.inode->version = diri->pre_dirty();
+
+ // project the snaprealm
+ auto &newsnap = *pi.snapnode;
+ newsnap.created = snapid;
+ auto em = newsnap.snaps.emplace(std::piecewise_construct, std::forward_as_tuple(snapid), std::forward_as_tuple(info));
+ if (!em.second)
+ em.first->second = info;
+ newsnap.seq = snapid;
+ newsnap.last_created = snapid;
+ newsnap.last_modified = info.stamp;
+ newsnap.change_attr++;
+
+ // journal the inode changes
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "mksnap");
+ mdlog->start_entry(le);
+
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ le->metablob.add_table_transaction(TABLE_SNAP, stid);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, diri, 0, PREDIRTY_PRIMARY, false);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, diri);
+
+ // journal the snaprealm changes
+ submit_mdlog_entry(le, new C_MDS_mksnap_finish(this, mdr, diri, info),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_mksnap_finish(MDRequestRef& mdr, CInode *diri, SnapInfo &info)
+{
+ dout(10) << "_mksnap_finish " << *mdr << " " << info << dendl;
+
+ int op = (diri->snaprealm? CEPH_SNAP_OP_CREATE : CEPH_SNAP_OP_SPLIT);
+
+ mdr->apply();
+
+ mds->snapclient->commit(mdr->more()->stid, mdr->ls);
+
+ // create snap
+ dout(10) << "snaprealm now " << *diri->snaprealm << dendl;
+
+ // notify other mds
+ mdcache->send_snap_update(diri, mdr->more()->stid, op);
+
+ mdcache->do_realm_invalidate_and_update_notify(diri, op);
+
+ // yay
+ mdr->in[0] = diri;
+ mdr->snapid = info.snapid;
+ mdr->tracei = diri;
+ respond_to_request(mdr, 0);
+}
+
+
+// RMSNAP
+
+struct C_MDS_rmsnap_finish : public ServerLogContext {
+ CInode *diri;
+ snapid_t snapid;
+ C_MDS_rmsnap_finish(Server *s, MDRequestRef& r, CInode *di, snapid_t sn) :
+ ServerLogContext(s, r), diri(di), snapid(sn) {}
+ void finish(int r) override {
+ server->_rmsnap_finish(mdr, diri, snapid);
+ }
+};
+
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_rmsnap(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+
+ CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!diri)
+ return;
+
+ if (!diri->is_dir()) {
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+
+ std::string_view snapname = req->get_filepath().last_dentry();
+
+ if (mdr->client_request->get_caller_uid() < g_conf()->mds_snap_min_uid || mdr->client_request->get_caller_uid() > g_conf()->mds_snap_max_uid) {
+ dout(20) << "rmsnap " << snapname << " on " << *diri << " denied to uid " << mdr->client_request->get_caller_uid() << dendl;
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ dout(10) << "rmsnap " << snapname << " on " << *diri << dendl;
+
+ // does snap exist?
+ if (snapname.length() == 0 || snapname[0] == '_') {
+ respond_to_request(mdr, -CEPHFS_EINVAL); // can't prune a parent snap, currently.
+ return;
+ }
+ if (!diri->snaprealm || !diri->snaprealm->exists(snapname)) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return;
+ }
+ snapid_t snapid = diri->snaprealm->resolve_snapname(snapname, diri->ino());
+ dout(10) << " snapname " << snapname << " is " << snapid << dendl;
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&diri->snaplock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+ if (CDentry *pdn = diri->get_projected_parent_dn(); pdn) {
+ if (!mds->locker->try_rdlock_snap_layout(pdn->get_dir()->get_inode(), mdr))
+ return;
+ }
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (!check_access(mdr, diri, MAY_WRITE|MAY_SNAPSHOT))
+ return;
+
+ // prepare
+ if (!mdr->more()->stid) {
+ mds->snapclient->prepare_destroy(diri->ino(), snapid,
+ &mdr->more()->stid, &mdr->more()->snapidbl,
+ new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ version_t stid = mdr->more()->stid;
+ auto p = mdr->more()->snapidbl.cbegin();
+ snapid_t seq;
+ decode(seq, p);
+ dout(10) << " stid is " << stid << ", seq is " << seq << dendl;
+
+ ceph_assert(mds->snapclient->get_cached_version() >= stid);
+
+ // journal
+ auto pi = diri->project_inode(mdr, false, true);
+ pi.inode->version = diri->pre_dirty();
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->rstat.rsnaps--;
+
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "rmsnap");
+ mdlog->start_entry(le);
+
+ // project the snaprealm
+ auto &newnode = *pi.snapnode;
+ newnode.snaps.erase(snapid);
+ newnode.seq = seq;
+ newnode.last_destroyed = seq;
+ newnode.last_modified = mdr->get_op_stamp();
+ newnode.change_attr++;
+
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ le->metablob.add_table_transaction(TABLE_SNAP, stid);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, diri, 0, PREDIRTY_PRIMARY, false);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, diri);
+
+ submit_mdlog_entry(le, new C_MDS_rmsnap_finish(this, mdr, diri, snapid),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_rmsnap_finish(MDRequestRef& mdr, CInode *diri, snapid_t snapid)
+{
+ dout(10) << "_rmsnap_finish " << *mdr << " " << snapid << dendl;
+ snapid_t stid = mdr->more()->stid;
+
+ mdr->apply();
+
+ mds->snapclient->commit(stid, mdr->ls);
+
+ dout(10) << "snaprealm now " << *diri->snaprealm << dendl;
+
+ // notify other mds
+ mdcache->send_snap_update(diri, mdr->more()->stid, CEPH_SNAP_OP_DESTROY);
+
+ mdcache->do_realm_invalidate_and_update_notify(diri, CEPH_SNAP_OP_DESTROY);
+
+ // yay
+ mdr->in[0] = diri;
+ mdr->tracei = diri;
+ mdr->snapid = snapid;
+ respond_to_request(mdr, 0);
+
+ // purge snapshot data
+ diri->purge_stale_snap_data(diri->snaprealm->get_snaps());
+}
+
+struct C_MDS_renamesnap_finish : public ServerLogContext {
+ CInode *diri;
+ snapid_t snapid;
+ C_MDS_renamesnap_finish(Server *s, MDRequestRef& r, CInode *di, snapid_t sn) :
+ ServerLogContext(s, r), diri(di), snapid(sn) {}
+ void finish(int r) override {
+ server->_renamesnap_finish(mdr, diri, snapid);
+ }
+};
+
+/* This function takes responsibility for the passed mdr*/
+void Server::handle_client_renamesnap(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest> &req = mdr->client_request;
+ if (req->get_filepath().get_ino() != req->get_filepath2().get_ino()) {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+
+ CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+ if (!diri)
+ return;
+
+ if (!diri->is_dir()) { // dir only
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+
+ if (mdr->client_request->get_caller_uid() < g_conf()->mds_snap_min_uid ||
+ mdr->client_request->get_caller_uid() > g_conf()->mds_snap_max_uid) {
+ respond_to_request(mdr, -CEPHFS_EPERM);
+ return;
+ }
+
+ std::string_view dstname = req->get_filepath().last_dentry();
+ std::string_view srcname = req->get_filepath2().last_dentry();
+ dout(10) << "renamesnap " << srcname << "->" << dstname << " on " << *diri << dendl;
+
+ if (srcname.length() == 0 || srcname[0] == '_') {
+ respond_to_request(mdr, -CEPHFS_EINVAL); // can't rename a parent snap.
+ return;
+ }
+ if (!diri->snaprealm || !diri->snaprealm->exists(srcname)) {
+ respond_to_request(mdr, -CEPHFS_ENOENT);
+ return;
+ }
+ if (dstname.length() == 0 || dstname[0] == '_') {
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ return;
+ }
+ if (diri->snaprealm->exists(dstname)) {
+ respond_to_request(mdr, -CEPHFS_EEXIST);
+ return;
+ }
+
+ snapid_t snapid = diri->snaprealm->resolve_snapname(srcname, diri->ino());
+
+ dout(10) << " snapname " << srcname << " is " << snapid << dendl;
+
+ // lock snap
+ if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+ MutationImpl::LockOpVec lov;
+ lov.add_xlock(&diri->snaplock);
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+ if (CDentry *pdn = diri->get_projected_parent_dn(); pdn) {
+ if (!mds->locker->try_rdlock_snap_layout(pdn->get_dir()->get_inode(), mdr))
+ return;
+ }
+ mdr->locking_state |= MutationImpl::ALL_LOCKED;
+ }
+
+ if (!check_access(mdr, diri, MAY_WRITE|MAY_SNAPSHOT))
+ return;
+
+ // prepare
+ if (!mdr->more()->stid) {
+ mds->snapclient->prepare_update(diri->ino(), snapid, dstname, utime_t(),
+ &mdr->more()->stid,
+ new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ version_t stid = mdr->more()->stid;
+ dout(10) << " stid is " << stid << dendl;
+
+ ceph_assert(mds->snapclient->get_cached_version() >= stid);
+
+ // journal
+ auto pi = diri->project_inode(mdr, false, true);
+ pi.inode->ctime = mdr->get_op_stamp();
+ if (mdr->get_op_stamp() > pi.inode->rstat.rctime)
+ pi.inode->rstat.rctime = mdr->get_op_stamp();
+ pi.inode->version = diri->pre_dirty();
+
+ // project the snaprealm
+ auto &newsnap = *pi.snapnode;
+ auto it = newsnap.snaps.find(snapid);
+ ceph_assert(it != newsnap.snaps.end());
+ it->second.name = dstname;
+ newsnap.last_modified = mdr->get_op_stamp();
+ newsnap.change_attr++;
+
+ // journal the inode changes
+ mdr->ls = mdlog->get_current_segment();
+ EUpdate *le = new EUpdate(mdlog, "renamesnap");
+ mdlog->start_entry(le);
+
+ le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
+ le->metablob.add_table_transaction(TABLE_SNAP, stid);
+ mdcache->predirty_journal_parents(mdr, &le->metablob, diri, 0, PREDIRTY_PRIMARY, false);
+ mdcache->journal_dirty_inode(mdr.get(), &le->metablob, diri);
+
+ // journal the snaprealm changes
+ submit_mdlog_entry(le, new C_MDS_renamesnap_finish(this, mdr, diri, snapid),
+ mdr, __func__);
+ mdlog->flush();
+}
+
+void Server::_renamesnap_finish(MDRequestRef& mdr, CInode *diri, snapid_t snapid)
+{
+ dout(10) << "_renamesnap_finish " << *mdr << " " << snapid << dendl;
+
+ mdr->apply();
+
+ mds->snapclient->commit(mdr->more()->stid, mdr->ls);
+
+ dout(10) << "snaprealm now " << *diri->snaprealm << dendl;
+
+ // notify other mds
+ mdcache->send_snap_update(diri, mdr->more()->stid, CEPH_SNAP_OP_UPDATE);
+
+ mdcache->do_realm_invalidate_and_update_notify(diri, CEPH_SNAP_OP_UPDATE);
+
+ // yay
+ mdr->in[0] = diri;
+ mdr->tracei = diri;
+ mdr->snapid = snapid;
+ respond_to_request(mdr, 0);
+}
+
+void Server::handle_client_readdir_snapdiff(MDRequestRef& mdr)
+{
+ const cref_t<MClientRequest>& req = mdr->client_request;
+ Session* session = mds->get_session(req);
+ MutationImpl::LockOpVec lov;
+ CInode* diri = rdlock_path_pin_ref(mdr, false, true);
+ if (!diri) return;
+
+ // it's a directory, right?
+ if (!diri->is_dir()) {
+ // not a dir
+ dout(10) << "reply to " << *req << " snapdiff -CEPHFS_ENOTDIR" << dendl;
+ respond_to_request(mdr, -CEPHFS_ENOTDIR);
+ return;
+ }
+
+ auto num_caps = session->get_num_caps();
+ auto session_cap_acquisition = session->get_cap_acquisition();
+
+ if (num_caps > static_cast<uint64_t>(max_caps_per_client * max_caps_throttle_ratio) && session_cap_acquisition >= cap_acquisition_throttle) {
+ dout(20) << "snapdiff throttled. max_caps_per_client: " << max_caps_per_client << " num_caps: " << num_caps
+ << " session_cap_acquistion: " << session_cap_acquisition << " cap_acquisition_throttle: " << cap_acquisition_throttle << dendl;
+ if (logger)
+ logger->inc(l_mdss_cap_acquisition_throttle);
+
+ mds->timer.add_event_after(caps_throttle_retry_request_timeout, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+
+ lov.add_rdlock(&diri->filelock);
+ lov.add_rdlock(&diri->dirfragtreelock);
+
+ if (!mds->locker->acquire_locks(mdr, lov))
+ return;
+
+ if (!check_access(mdr, diri, MAY_READ))
+ return;
+
+ // which frag?
+ frag_t fg = (__u32)req->head.args.snapdiff.frag;
+ unsigned req_flags = (__u32)req->head.args.snapdiff.flags;
+ string offset_str = req->get_path2();
+
+ __u32 offset_hash = 0;
+ if (!offset_str.empty()) {
+ offset_hash = ceph_frag_value(diri->hash_dentry_name(offset_str));
+ } else {
+ offset_hash = (__u32)req->head.args.snapdiff.offset_hash;
+ }
+
+ dout(10) << " frag " << fg << " offset '" << offset_str << "'"
+ << " offset_hash " << offset_hash << " flags " << req_flags << dendl;
+
+ // does the frag exist?
+ if (diri->dirfragtree[fg.value()] != fg) {
+ frag_t newfg;
+ if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+ if (fg.contains((unsigned)offset_hash)) {
+ newfg = diri->dirfragtree[offset_hash];
+ } else {
+ // client actually wants next frag
+ newfg = diri->dirfragtree[fg.value()];
+ }
+ } else {
+ offset_str.clear();
+ newfg = diri->dirfragtree[fg.value()];
+ }
+ dout(10) << " adjust frag " << fg << " -> " << newfg << " " << diri->dirfragtree << dendl;
+ fg = newfg;
+ }
+
+ CDir* dir = try_open_auth_dirfrag(diri, fg, mdr);
+ if (!dir) return;
+
+ // ok!
+ dout(10) << __func__<< " on " << *dir << dendl;
+ ceph_assert(dir->is_auth());
+
+ if (!dir->is_complete()) {
+ if (dir->is_frozen()) {
+ dout(7) << "dir is frozen " << *dir << dendl;
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ dir->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+ return;
+ }
+ // fetch
+ dout(10) << " incomplete dir contents for snapdiff on " << *dir << ", fetching" << dendl;
+ dir->fetch(new C_MDS_RetryRequest(mdcache, mdr), true);
+ return;
+ }
+
+#ifdef MDS_VERIFY_FRAGSTAT
+ dir->verify_fragstat();
+#endif
+
+ utime_t now = ceph_clock_now();
+ mdr->set_mds_stamp(now);
+
+ mdr->snapid_diff_other = (uint64_t)req->head.args.snapdiff.snap_other;
+ if (mdr->snapid_diff_other == mdr->snapid ||
+ mdr->snapid == CEPH_NOSNAP ||
+ mdr->snapid_diff_other == CEPH_NOSNAP) {
+ dout(10) << "reply to " << *req << " snapdiff -CEPHFS_EINVAL" << dendl;
+ respond_to_request(mdr, -CEPHFS_EINVAL);
+ }
+
+ dout(10) << __func__
+ << " snap " << mdr->snapid
+ << " vs. snap " << mdr->snapid_diff_other
+ << dendl;
+
+ SnapRealm* realm = diri->find_snaprealm();
+
+ unsigned max = req->head.args.snapdiff.max_entries;
+ if (!max)
+ max = dir->get_num_any(); // whatever, something big.
+ unsigned max_bytes = req->head.args.snapdiff.max_bytes;
+ if (!max_bytes)
+ // make sure at least one item can be encoded
+ max_bytes = (512 << 10) + g_conf()->mds_max_xattr_pairs_size;
+
+ // start final blob
+ bufferlist dirbl;
+ DirStat ds;
+ ds.frag = dir->get_frag();
+ ds.auth = dir->get_dir_auth().first;
+ if (dir->is_auth() && !forward_all_requests_to_auth)
+ dir->get_dist_spec(ds.dist, mds->get_nodeid());
+
+ dir->encode_dirstat(dirbl, mdr->session->info, ds);
+
+ // count bytes available.
+ // this isn't perfect, but we should capture the main variable/unbounded size items!
+ int front_bytes = dirbl.length() + sizeof(__u32) + sizeof(__u8) * 2;
+ int bytes_left = max_bytes - front_bytes;
+ bytes_left -= get_snap_trace(session, realm).length();
+
+ _readdir_diff(
+ now,
+ mdr,
+ diri,
+ dir,
+ realm,
+ max,
+ bytes_left,
+ offset_str,
+ offset_hash,
+ req_flags,
+ dirbl);
+}
+
+
+/**
+ * Return true if server is in state RECONNECT and this
+ * client has not yet reconnected.
+ */
+bool Server::waiting_for_reconnect(client_t c) const
+{
+ return client_reconnect_gather.count(c) > 0;
+}
+
+void Server::dump_reconnect_status(Formatter *f) const
+{
+ f->open_object_section("reconnect_status");
+ f->dump_stream("client_reconnect_gather") << client_reconnect_gather;
+ f->close_section();
+}
+
+const bufferlist& Server::get_snap_trace(Session *session, SnapRealm *realm) const {
+ ceph_assert(session);
+ ceph_assert(realm);
+ if (session->info.has_feature(CEPHFS_FEATURE_NEW_SNAPREALM_INFO)) {
+ return realm->get_snap_trace_new();
+ } else {
+ return realm->get_snap_trace();
+ }
+}
+
+const bufferlist& Server::get_snap_trace(client_t client, SnapRealm *realm) const {
+ Session *session = mds->sessionmap.get_session(entity_name_t::CLIENT(client.v));
+ return get_snap_trace(session, realm);
+}
+
+void Server::_readdir_diff(
+ utime_t now,
+ MDRequestRef& mdr,
+ CInode* diri,
+ CDir* dir,
+ SnapRealm* realm,
+ unsigned max_entries,
+ int bytes_left,
+ const string& offset_str,
+ uint32_t offset_hash,
+ unsigned req_flags,
+ bufferlist& dirbl)
+{
+ // build dir contents
+ bufferlist dnbl;
+ __u32 numfiles = 0;
+
+ snapid_t snapid = mdr->snapid;
+ snapid_t snapid_prev = mdr->snapid_diff_other;
+ if (snapid < snapid_prev) {
+ std::swap(snapid, snapid_prev);
+ }
+ bool from_the_beginning = !offset_hash && offset_str.empty();
+ // skip all dns < dentry_key_t(snapid, offset_str, offset_hash)
+ dentry_key_t skip_key(snapid_prev, offset_str.c_str(), offset_hash);
+
+ bool end = build_snap_diff(
+ mdr,
+ dir,
+ bytes_left,
+ from_the_beginning ? nullptr : & skip_key,
+ snapid_prev,
+ snapid,
+ dnbl,
+ [&](CDentry* dn, CInode* in, bool exists) {
+ string name;
+ snapid_t effective_snapid;
+ const auto& dn_name = dn->get_name();
+ // provide the first snapid for removed entries and
+ // the last one for existent ones
+ effective_snapid = exists ? snapid : snapid_prev;
+ name.append(dn_name);
+ if ((int)(dnbl.length() + name.length() + sizeof(__u32) + sizeof(LeaseStat)) > bytes_left) {
+ dout(10) << " ran out of room, stopping at " << dnbl.length() << " < " << bytes_left << dendl;
+ return false;
+ }
+
+ auto diri = dir->get_inode();
+ auto hash = ceph_frag_value(diri->hash_dentry_name(dn_name));
+ unsigned start_len = dnbl.length();
+ dout(10) << "inc dn " << *dn << " as " << name
+ << std::hex << " hash 0x" << hash << std::dec
+ << dendl;
+ encode(name, dnbl);
+ mds->locker->issue_client_lease(dn, in, mdr, now, dnbl);
+
+ // inode
+ dout(10) << "inc inode " << *in << " snap " << effective_snapid << dendl;
+ int r = in->encode_inodestat(dnbl, mdr->session, realm, effective_snapid, bytes_left - (int)dnbl.length());
+ if (r < 0) {
+ // chop off dn->name, lease
+ dout(10) << " ran out of room, stopping at "
+ << start_len << " < " << bytes_left << dendl;
+ bufferlist keep;
+ keep.substr_of(dnbl, 0, start_len);
+ dnbl.swap(keep);
+ return false;
+ }
+
+ // touch dn
+ mdcache->lru.lru_touch(dn);
+ ++numfiles;
+ return true;
+ });
+
+ __u16 flags = 0;
+ if (req_flags & CEPH_READDIR_REPLY_BITFLAGS) {
+ flags |= CEPH_READDIR_HASH_ORDER | CEPH_READDIR_OFFSET_HASH;
+ }
+
+ std::swap(mdr->snapid, mdr->snapid_diff_other); // we want opponent snapid to be used for tracei
+
+ _finalize_readdir(mdr, diri, dir, from_the_beginning, end, flags, numfiles,
+ dirbl, dnbl);
+}
+
+bool Server::build_snap_diff(
+ MDRequestRef& mdr,
+ CDir* dir,
+ int bytes_left,
+ dentry_key_t* skip_key,
+ snapid_t snapid_prev,
+ snapid_t snapid,
+ const bufferlist& dnbl,
+ std::function<bool (CDentry*, CInode*, bool)> add_result_cb)
+{
+ client_t client = mdr->client_request->get_source().num();
+
+ struct EntryInfo {
+ CDentry* dn = nullptr;
+ CInode* in = nullptr;
+ utime_t mtime;
+
+ void reset() {
+ *this = EntryInfo();
+ }
+ } before;
+
+ auto insert_deleted = [&](EntryInfo& ei) {
+ dout(20) << "build_snap_diff deleted file " << ei.dn->get_name() << " "
+ << ei.dn->first << "/" << ei.dn->last << dendl;
+ int r = add_result_cb(ei.dn, ei.in, false);
+ ei.reset();
+ return r;
+ };
+
+ auto it = !skip_key ? dir->begin() : dir->lower_bound(*skip_key);
+
+ while(it != dir->end()) {
+ CDentry* dn = it->second;
+ dout(20) << __func__ << " " << it->first << "->" << *dn << dendl;
+ ++it;
+ if (dn->state_test(CDentry::STATE_PURGING))
+ continue;
+
+ bool dnp = dn->use_projected(client, mdr);
+ CDentry::linkage_t* dnl = dnp ? dn->get_projected_linkage() : dn->get_linkage();
+
+ if (dnl->is_null()) {
+ dout(20) << __func__ << " linkage is null, skipping" << dendl;
+ continue;
+ }
+
+ if (dn->last < snapid_prev || dn->first > snapid) {
+ dout(20) << __func__ << " not in range, skipping" << dendl;
+ continue;
+ }
+ if (skip_key) {
+ skip_key->snapid = dn->last;
+ if (!(*skip_key < dn->key()))
+ continue;
+ }
+
+ CInode* in = dnl->get_inode();
+ if (in && in->ino() == CEPH_INO_CEPH)
+ continue;
+
+ // remote link?
+ // better for the MDS to do the work, if we think the client will stat any of these files.
+ if (dnl->is_remote() && !in) {
+ in = mdcache->get_inode(dnl->get_remote_ino());
+ dout(20) << __func__ << " remote in: " << *in << " ino " << std::hex << dnl->get_remote_ino() << std::dec << dendl;
+ if (in) {
+ dn->link_remote(dnl, in);
+ } else if (dn->state_test(CDentry::STATE_BADREMOTEINO)) {
+ dout(10) << "skipping bad remote ino on " << *dn << dendl;
+ continue;
+ } else {
+ // touch everything i _do_ have
+ for (auto& p : *dir) {
+ if (!p.second->get_linkage()->is_null())
+ mdcache->lru.lru_touch(p.second);
+ }
+
+ // already issued caps and leases, reply immediately.
+ if (dnbl.length() > 0) {
+ mdcache->open_remote_dentry(dn, dnp, new C_MDSInternalNoop);
+ dout(10) << " open remote dentry after caps were issued, stopping at "
+ << dnbl.length() << " < " << bytes_left << dendl;
+ } else {
+ mds->locker->drop_locks(mdr.get());
+ mdr->drop_local_auth_pins();
+ mdcache->open_remote_dentry(dn, dnp, new C_MDS_RetryRequest(mdcache, mdr));
+ }
+ return false;
+ }
+ }
+ ceph_assert(in);
+
+ utime_t mtime = in->get_inode()->mtime;
+
+ if (in->is_dir()) {
+
+ // we need to maintain the order of entries (determined by their name hashes)
+ // hence need to insert the previous entry if any immediately.
+ if (before.dn) {
+ if (!insert_deleted(before)) {
+ break;
+ }
+ }
+
+ bool exists = true;
+ if (snapid_prev < dn->first && dn->last < snapid) {
+ dout(20) << __func__ << " skipping inner " << dn->get_name() << " "
+ << dn->first << "/" << dn->last << dendl;
+ continue;
+ } else if (dn->first <= snapid_prev && dn->last < snapid) {
+ // dir deleted
+ dout(20) << __func__ << " deleted dir " << dn->get_name() << " "
+ << dn->first << "/" << dn->last << dendl;
+ exists = false;
+ }
+ bool r = add_result_cb(dn, in, exists);
+ if (!r) {
+ break;
+ }
+ } else {
+ if (snapid_prev >= dn->first && snapid <= dn->last) {
+ dout(20) << __func__ << " skipping unchanged " << dn->get_name() << " "
+ << dn->first << "/" << dn->last << dendl;
+ continue;
+ } else if (snapid_prev < dn->first && snapid > dn->last) {
+ dout(20) << __func__ << " skipping inner modification " << dn->get_name() << " "
+ << dn->first << "/" << dn->last << dendl;
+ continue;
+ }
+ string_view name_before =
+ before.dn ? string_view(before.dn->get_name()) : string_view();
+ if (before.dn && dn->get_name() != name_before) {
+ if (!insert_deleted(before)) {
+ break;
+ }
+ before.reset();
+ }
+ if (snapid_prev >= dn->first && snapid_prev <= dn->last) {
+ dout(30) << __func__ << " dn_before " << dn->get_name() << " "
+ << dn->first << "/" << dn->last << dendl;
+ before = EntryInfo {dn, in, mtime};
+ continue;
+ } else {
+ if (before.dn && dn->get_name() == name_before) {
+ if (mtime == before.mtime) {
+ dout(30) << __func__ << " timestamp not changed " << dn->get_name() << " "
+ << dn->first << "/" << dn->last
+ << " " << mtime
+ << dendl;
+ before.reset();
+ continue;
+ } else {
+ dout(30) << __func__ << " timestamp changed " << dn->get_name() << " "
+ << dn->first << "/" << dn->last
+ << " " << before.mtime << " vs. " << mtime
+ << dendl;
+ before.reset();
+ }
+ }
+ dout(20) << __func__ << " new file " << dn->get_name() << " "
+ << dn->first << "/" << dn->last
+ << dendl;
+ ceph_assert(snapid >= dn->first && snapid <= dn->last);
+ }
+ if (!add_result_cb(dn, in, true)) {
+ break;
+ }
+ }
+ }
+ if (before.dn) {
+ insert_deleted(before);
+ }
+ return it == dir->end();
+}