]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/mds/Server.cc
update ceph source to reef 18.2.0
[ceph.git] / ceph / src / mds / Server.cc
index b125937a4ff048dbc0ccf4edee7d2d955274fee1..bf12cb7e2c8fa9f24be9f6eae1b330aa829351cb 100644 (file)
 #include "InoTable.h"
 #include "SnapClient.h"
 #include "Mutation.h"
+#include "MetricsHandler.h"
 #include "cephfs_features.h"
+#include "MDSContext.h"
 
 #include "msg/Messenger.h"
 
 #include "osdc/Objecter.h"
 
 #include "events/EUpdate.h"
-#include "events/ESlaveUpdate.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/perf_counters.h"
 #include "include/compat.h"
 #include "osd/OSDMap.h"
+#include "fscrypt.h"
 
 #include <errno.h>
-#include <math.h>
 
 #include <list>
-#include <iostream>
+#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;
@@ -77,6 +85,64 @@ class ServerContext : public MDSContext {
   }
 };
 
+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->release_client_request(), 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) {
+    o << "[batch front=" << *mdr << "]";
+  }
+};
+
 class ServerLogContext : public MDSLogContextBase {
 protected:
   Server *server;
@@ -105,13 +171,16 @@ void Server::create_logger()
 
   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_slave_request, "handle_slave_request",
-                      "Slave requests", "hsr", 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);
@@ -135,6 +204,8 @@ void Server::create_logger()
                    "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",
@@ -175,53 +246,77 @@ void Server::create_logger()
   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_slave_request, "dispatch_server_request",
+  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) : 
+Server::Server(MDSRank *m, MetricsHandler *metrics_handler) :
   mds(m), 
   mdcache(mds->mdcache), mdlog(mds->mdlog),
-  logger(0),
-  is_full(false),
-  reconnect_done(NULL),
-  failed_reconnects(0),
-  reconnect_evicting(false),
-  terminating_sessions(false),
-  recall_throttle(g_conf().get_val<double>("mds_recall_max_decay_rate"))
+  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 Message::const_ref &m)
+void Server::dispatch(const cref_t<Message> &m)
 {
   switch (m->get_type()) {
   case CEPH_MSG_CLIENT_RECONNECT:
-    handle_client_reconnect(MClientReconnect::msgref_cast(m));
+    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_slave_request()/handle_client_session() will wait if necessary
+  // handle_peer_request()/handle_client_session() will wait if necessary
   if (m->get_type() == CEPH_MSG_CLIENT_REQUEST && !mds->is_active()) {
-    const auto &req = MClientRequest::msgref_cast(m);
+    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_closed()) {
+      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()) {
+      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)) {
-         mdcache->add_replay_ino_alloc(inodeno_t(req->head.ino));
+         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
@@ -259,20 +354,23 @@ void Server::dispatch(const Message::const_ref &m)
 
   switch (m->get_type()) {
   case CEPH_MSG_CLIENT_SESSION:
-    handle_client_session(MClientSession::msgref_cast(m));
+    handle_client_session(ref_cast<MClientSession>(m));
     return;
   case CEPH_MSG_CLIENT_REQUEST:
-    handle_client_request(MClientRequest::msgref_cast(m));
+    handle_client_request(ref_cast<MClientRequest>(m));
+    return;
+  case CEPH_MSG_CLIENT_REPLY:
+    handle_client_reply(ref_cast<MClientReply>(m));
     return;
   case CEPH_MSG_CLIENT_RECLAIM:
-    handle_client_reclaim(MClientReclaim::msgref_cast(m));
+    handle_client_reclaim(ref_cast<MClientReclaim>(m));
     return;
-  case MSG_MDS_SLAVE_REQUEST:
-    handle_slave_request(MMDSSlaveRequest::msgref_cast(m));
+  case MSG_MDS_PEER_REQUEST:
+    handle_peer_request(ref_cast<MMDSPeerRequest>(m));
     return;
   default:
-    derr << "server unknown message " << m->get_type() << dendl;
-    ceph_abort_msg("server unknown message");  
+    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()));  
   }
 }
 
@@ -286,17 +384,22 @@ class C_MDS_session_finish : public ServerLogContext {
   uint64_t state_seq;
   bool open;
   version_t cmapv;
-  interval_set<inodeno_t> inos;
+  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_ = NULL) :
+  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, interval_set<inodeno_t>& i, version_t iv, Context *fin_ = NULL) :
-    ServerLogContext(srv), session(se), state_seq(sseq), open(s), cmapv(mv), inos(i), inotablev(iv), 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, inotablev);
+    server->_session_logged(session, state_seq, open, cmapv, inos_to_free, inotablev, inos_to_purge, ls);
     if (fin) {
       fin->complete(r);
     }
@@ -316,26 +419,26 @@ Session* Server::find_session_by_uuid(std::string_view uuid)
     if (!session) {
       session = it.second;
     } else if (!session->reclaiming_from) {
-      assert(it.second->reclaiming_from == session);
+      ceph_assert(it.second->reclaiming_from == session);
       session = it.second;
     } else {
-      assert(session->reclaiming_from == it.second);
+      ceph_assert(session->reclaiming_from == it.second);
     }
   }
   return session;
 }
 
-void Server::reclaim_session(Session *session, const MClientReclaim::const_ref &m)
+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 = MClientReclaimReply::create(0);
+  auto reply = make_message<MClientReclaimReply>(0);
   if (m->get_uuid().empty()) {
     dout(10) << __func__ << " invalid message (no uuid)" << dendl;
-    reply->set_result(-EINVAL);
+    reply->set_result(-CEPHFS_EINVAL);
     mds->send_message_client(reply, session);
     return;
   }
@@ -343,7 +446,7 @@ void Server::reclaim_session(Session *session, const MClientReclaim::const_ref &
   unsigned flags = m->get_flags();
   if (flags != CEPH_RECLAIM_RESET) { // currently only support reset
     dout(10) << __func__ << " unsupported flags" << dendl;
-    reply->set_result(-EOPNOTSUPP);
+    reply->set_result(-CEPHFS_EINVAL);
     mds->send_message_client(reply, session);
     return;
   }
@@ -353,25 +456,22 @@ void Server::reclaim_session(Session *session, const MClientReclaim::const_ref &
     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(-EPERM);
+      reply->set_result(-CEPHFS_EPERM);
       mds->send_message_client(reply, session);
     }
 
-    assert(!target->reclaiming_from);
-    assert(!session->reclaiming_from);
+    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);
-    return;
-  }
-
-  ceph_abort();
+  } else ceph_assert(0); /* no other flags are handled at this time */
 }
 
-void Server::finish_reclaim_session(Session *session, const MClientReclaimReply::ref &reply)
+void Server::finish_reclaim_session(Session *session, const ref_t<MClientReclaimReply> &reply)
 {
   Session *target = session->reclaiming_from;
   if (target) {
@@ -380,8 +480,8 @@ void Server::finish_reclaim_session(Session *session, const MClientReclaimReply:
     Context *send_reply;
     if (reply) {
       int64_t session_id = session->get_client().v;
-      send_reply = new FunctionContext([this, session_id, reply](int r) {
-           assert(mds->mds_lock.is_locked_by_me());
+      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;
@@ -394,54 +494,82 @@ void Server::finish_reclaim_session(Session *session, const MClientReclaimReply:
       send_reply = nullptr;
     }
 
-    bool blacklisted = mds->objecter->with_osdmap([target](const OSDMap &map) {
-         return map.is_blacklisted(target->info.inst.addr);
+    bool blocklisted = mds->objecter->with_osdmap([target](const OSDMap &map) {
+         return map.is_blocklisted(target->info.inst.addr);
        });
 
-    if (blacklisted || !g_conf()->mds_session_blacklist_on_evict) {
+    if (blocklisted || !g_conf()->mds_session_blocklist_on_evict) {
       kill_session(target, send_reply);
     } else {
-      std::stringstream ss;
-      mds->evict_client(target->get_client().v, false, true, ss, send_reply);
+      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 MClientReclaim::const_ref &m)
+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;
-  assert(m->get_source().is_client()); // should _not_ come from an mds!
+  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 (m->get_flags() & MClientReclaim::FLAG_FINISH) {
+  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 MClientSession::const_ref &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->get_source().is_client()); // should _not_ come from an mds!
+  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;
   }
 
@@ -466,12 +594,38 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
   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) {
-      dout(10) << "currently open|opening|stale|killing, dropping this req" << dendl;
+      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());
@@ -483,7 +637,7 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
 
     {
       auto& addr = session->info.inst.addr;
-      session->set_client_metadata(client_metadata_t(m->metadata, m->supported_features));
+      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) {
@@ -506,22 +660,29 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
         dout(2) << css->strv() << dendl;
       };
 
-      auto send_reject_message = [this, &session, &log_session_status](std::string_view err_str) {
-       auto m = MClientSession::create(CEPH_SESSION_REJECT);
+      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 blacklisted = mds->objecter->with_osdmap(
+      bool blocklisted = mds->objecter->with_osdmap(
          [&addr](const OSDMap &osd_map) -> bool {
-           return osd_map.is_blacklisted(addr);
+           return osd_map.is_blocklisted(addr);
          });
 
-      if (blacklisted) {
-       dout(10) << "rejecting blacklisted client " << addr << dendl;
-       send_reject_message("blacklisted");
+      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;
       }
@@ -530,7 +691,8 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
        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) << " 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;
       }
@@ -538,11 +700,12 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
       feature_bitset_t missing_features = required_client_features;
       missing_features -= client_metadata.features;
       if (!missing_features.empty()) {
-       stringstream ss;
-       ss << "missing required features '" << missing_features << "'";
-       send_reject_message(ss.str());
-       mds->clog->warn() << "client session lacks required features '"
-                         << missing_features << "' denied (" << session->info.inst << ")";
+       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;
       }
@@ -551,22 +714,22 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
       // 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;
-       stringstream ss;
+       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;
-         ss << "invalue root '" << claimed_root << "'";
+         *css << "invalue root '" << claimed_root << "'";
        } else if (!session->auth_caps.path_capable(claimed_root.substr(1))) {
          denied = true;
-         ss << "non-allowable root '" << claimed_root << "'";
+         *css << "non-allowable root '" << claimed_root << "'";
        }
 
        if (denied) {
          // Tell the client we're rejecting their open
-         send_reject_message(ss.str());
-         mds->clog->warn() << "client session with " << ss.str()
+         send_reject_message(css->strv());
+         mds->clog->warn() << "client session with " << css->strv()
                            << " denied (" << session->info.inst << ")";
          session->clear();
          break;
@@ -583,13 +746,14 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
        }
       }
 
-      if (session->is_closed())
-       mds->sessionmap.add_session(session);
+      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 FunctionContext([log_session_status = std::move(log_session_status)](int r){
+      auto fin = new LambdaContext([log_session_status = std::move(log_session_status)](int r){
         ceph_assert(r == 0);
         log_session_status("ACCEPTED", "");
       });
@@ -607,7 +771,7 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
        mds->locker->resume_stale_caps(session);
        mds->sessionmap.touch_session(session);
       }
-      auto reply = MClientSession::create(CEPH_SESSION_RENEWCAPS, m->get_seq());
+      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;
@@ -658,29 +822,32 @@ void Server::handle_client_session(const MClientSession::const_ref &m)
     break;
 
   default:
-    ceph_abort();
+    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) {
+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());
+  version_t seq = session->wait_for_flush(gather.new_sub());
   mds->send_message_client(
-    MClientSession::create(CEPH_SESSION_FLUSHMSG, seq), session);
+    make_message<MClientSession>(CEPH_SESSION_FLUSHMSG, seq), session);
 }
 
 void Server::flush_client_sessions(set<client_t>& client_set, MDSGatherBuilder& gather)
 {
-  for (set<client_t>::iterator p = client_set.begin(); p != client_set.end(); ++p) {
-    Session *session = mds->sessionmap.get_session(entity_name_t::CLIENT(p->v));
+  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);
+    flush_session(session, gather);
   }
 }
 
@@ -692,17 +859,34 @@ void Server::finish_flush_session(Session *session, version_t seq)
 }
 
 void Server::_session_logged(Session *session, uint64_t state_seq, bool open, version_t pv,
-                            interval_set<inodeno_t>& inos, version_t piv)
+                            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 << dendl;
+  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 (piv) {
-    ceph_assert(session->is_closing() || session->is_killing() ||
-          session->is_opening()); // re-open closing session
-    session->info.prealloc_inos.subtract(inos);
-    mds->inotable->apply_release_ids(inos);
-    ceph_assert(mds->inotable->get_version() == piv);
+    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);
@@ -716,23 +900,27 @@ void Server::_session_logged(Session *session, uint64_t state_seq, bool open, ve
     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 = MClientSession::create(CEPH_SESSION_OPEN);
-    if (session->info.has_feature(CEPHFS_FEATURE_MIMIC))
+    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 = MClientSession::create(CEPH_SESSION_FORCE_RO);
+      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);
+      mds->locker->remove_client_cap(in, cap, killing);
     }
     while (!session->leases.empty()) {
       ClientLease *r = session->leases.front();
@@ -769,16 +957,19 @@ void Server::_session_logged(Session *session, uint64_t state_seq, bool open, ve
       }
 
       // reset session
-      mds->send_message_client(MClientSession::create(CEPH_SESSION_CLOSE), 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();
-       session->get_connection()->set_priv(NULL);
+        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();
@@ -810,8 +1001,8 @@ version_t Server::prepare_force_open_sessions(map<client_t,entity_inst_t>& cm,
   mds->objecter->with_osdmap(
       [this, &cm, &cmm](const OSDMap &osd_map) {
        for (auto p = cm.begin(); p != cm.end(); ) {
-         if (osd_map.is_blacklisted(p->second.addr)) {
-           dout(10) << " ignoring blacklisted client." << p->first
+         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++);
@@ -865,14 +1056,17 @@ void Server::finish_force_open_sessions(const map<client_t,pair<Session*,uint64_
        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 = MClientSession::create(CEPH_SESSION_OPEN);
-       if (session->info.has_feature(CEPHFS_FEATURE_MIMIC))
+       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(MClientSession::create(CEPH_SESSION_FORCE_RO), session);
+         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;
@@ -933,8 +1127,16 @@ void Server::find_idle_sessions()
   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;
@@ -956,6 +1158,22 @@ void Server::find_idle_sessions()
        }
       }
 
+      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);
@@ -983,33 +1201,25 @@ void Server::find_idle_sessions()
 
     for (auto session : new_stale) {
       mds->sessionmap.set_state(session, Session::STATE_STALE);
-      mds->locker->revoke_stale_caps(session);
-      mds->locker->remove_stale_leases(session);
-      mds->send_message_client(MClientSession::create(CEPH_SESSION_STALE, session->get_push_seq()), session);
-      finish_flush_session(session, session->get_push_seq());
+      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();
 
-  // 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 kicking any clients to be safe" << dendl;
-    return;
-  }
-
-  if (mds->sessionmap.get_sessions().size() == 1 && mds->mdsmap->get_num_in_mds() == 1) {
-    dout(20) << "skipping client eviction because there is only one" << dendl;
-    return;
-  }
-
   // 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)) {
-      assert(session->is_stale());
+      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
@@ -1032,9 +1242,9 @@ void Server::find_idle_sessions()
     dout(10) << "autoclosing stale session " << session->info.inst
             << " last renewed caps " << last_cap_renew_span << "s ago" << dendl;
 
-    if (g_conf()->mds_session_blacklist_on_timeout) {
-      std::stringstream ss;
-      mds->evict_client(session->get_client().v, false, true, ss, nullptr);
+    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);
     }
@@ -1046,8 +1256,7 @@ void Server::evict_cap_revoke_non_responders() {
     return;
   }
 
-  std::list<client_t> to_evict;
-  mds->locker->get_late_revoking_clients(&to_evict, cap_revoke_eviction_timeout);
+  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"
@@ -1056,18 +1265,20 @@ void Server::evict_cap_revoke_non_responders() {
     dout(1) << __func__ << ": evicting cap revoke non-responder client id "
             << client << dendl;
 
-    std::stringstream ss;
+    CachedStackStringStream css;
     bool evicted = mds->evict_client(client.v, false,
-                                     g_conf()->mds_session_blacklist_on_evict,
-                                     ss, nullptr);
+                                     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 ConfigProxy& conf,
-                                const std::set <std::string> &changed) {
+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 "
@@ -1076,6 +1287,45 @@ void Server::handle_conf_change(const ConfigProxy& conf,
   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");
+  }
 }
 
 /*
@@ -1084,7 +1334,7 @@ void Server::handle_conf_change(const ConfigProxy& conf,
  */
 void Server::kill_session(Session *session, Context *on_safe)
 {
-  ceph_assert(mds->mds_lock.is_locked_by_me());
+  ceph_assert(ceph_mutex_is_locked_by_me(mds->mds_lock));
 
   if ((session->is_opening() ||
        session->is_open() ||
@@ -1107,76 +1357,65 @@ void Server::kill_session(Session *session, Context *on_safe)
   }
 }
 
-size_t Server::apply_blacklist(const std::set<entity_addr_t> &blacklist)
+size_t Server::apply_blocklist()
 {
-  bool prenautilus = mds->objecter->with_osdmap(
-      [&](const OSDMap& o) {
-       return o.require_osd_release < CEPH_RELEASE_NAUTILUS;
-      });
-
   std::vector<Session*> victims;
   const auto& sessions = mds->sessionmap.get_sessions();
-  for (const auto& p : sessions) {
-    if (!p.first.is_client()) {
-      // Do not apply OSDMap blacklist to MDS daemons, we find out
-      // about their death via MDSMap.
-      continue;
-    }
-
-    Session *s = p.second;
-    auto inst_addr = s->info.inst.addr;
-    // blacklist entries are always TYPE_ANY for nautilus+
-    inst_addr.set_type(entity_addr_t::TYPE_ANY);
-    if (blacklist.count(inst_addr)) {
-      victims.push_back(s);
-      continue;
-    }
-    if (prenautilus) {
-      // ...except pre-nautilus, they were TYPE_LEGACY
-      inst_addr.set_type(entity_addr_t::TYPE_LEGACY);
-      if (blacklist.count(inst_addr)) {
-       victims.push_back(s);
+  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) {
+  for (const auto& s : victims) {
     kill_session(s, nullptr);
   }
 
-  dout(10) << "apply_blacklist: killed " << victims.size() << dendl;
+  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> both;
-  both.insert(session->info.prealloc_inos);
-  both.insert(session->pending_prealloc_inos);
-  if (both.size()) {
-    mds->inotable->project_release_ids(both);
+  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;
-
-  mdlog->start_submit_entry(new ESession(session->info.inst, false, pv, both, piv),
-                           new C_MDS_session_finish(this, session, sseq, false, pv, both, piv, on_safe));
+  
+  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
-  elist<MDRequestImpl*>::iterator p =
-    session->requests.begin(member_offset(MDRequestImpl,
-                                         item_session_request));
-  while (!p.end()) {
-    MDRequestRef mdr = mdcache->request_get((*p)->reqid);
-    ++p;
+  while(!session->requests.empty()) {
+    auto mdr = MDRequestRef(*session->requests.begin());
     mdcache->request_kill(mdr);
   }
 
@@ -1193,6 +1432,7 @@ void Server::reconnect_clients(MDSContext *reconnect_done_)
   for (auto session : sessions) {
     if (session->is_open()) {
       client_reconnect_gather.insert(session->get_client());
+      session->set_reconnecting(true);
       session->last_cap_renew = now;
     }
   }
@@ -1210,14 +1450,40 @@ void Server::reconnect_clients(MDSContext *reconnect_done_)
   mds->sessionmap.dump();
 }
 
-void Server::handle_client_reconnect(const MClientReconnect::const_ref &m)
+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)
+  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;
@@ -1229,9 +1495,13 @@ void Server::handle_client_reconnect(const MClientReconnect::const_ref &m)
   dout(10) << " reconnect_start " << reconnect_start << " delay " << delay << dendl;
 
   bool deny = false;
-  if (!mds->is_reconnect() || mds->get_want_state() != CEPH_MDS_STATE_RECONNECT || reconnect_evicting) {
+  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?
-    dout(1) << " no longer in reconnect state, ignoring reconnect, sending close" << dendl;
+    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()
@@ -1250,9 +1520,9 @@ void Server::handle_client_reconnect(const MClientReconnect::const_ref &m)
       feature_bitset_t missing_features = required_client_features;
       missing_features -= session->info.client_metadata.features;
       if (!missing_features.empty()) {
-       stringstream ss;
-       ss << "missing required features '" << missing_features << "'";
-       error_str = ss.str();
+       CachedStackStringStream css;
+       *css << "missing required features '" << missing_features << "'";
+       error_str = css->strv();
       }
     }
 
@@ -1265,18 +1535,22 @@ void Server::handle_client_reconnect(const MClientReconnect::const_ref &m)
   }
 
   if (deny) {
-    auto r = MClientSession::create(CEPH_SESSION_CLOSE);
+    auto r = make_message<MClientSession>(CEPH_SESSION_CLOSE);
     mds->send_message_client(r, session);
-    if (session->is_open())
-      kill_session(session, nullptr);
+    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 = MClientSession::create(CEPH_SESSION_OPEN);
-    if (session->info.has_feature(CEPHFS_FEATURE_MIMIC))
+    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;
   }
@@ -1342,6 +1616,7 @@ void Server::handle_client_reconnect(const MClientReconnect::const_ref &m)
 
     // remove from gather set
     client_reconnect_gather.erase(from);
+    session->set_reconnecting(false);
     if (client_reconnect_gather.empty())
       reconnect_gather_finish();
   }
@@ -1378,22 +1653,7 @@ void Server::infer_supported_features(Session *session, client_metadata_t& clien
 
 void Server::update_required_client_features()
 {
-  vector<size_t> bits = CEPHFS_FEATURES_MDS_REQUIRED;
-
-  int min_compat = mds->mdsmap->get_min_compat_client();
-  if (min_compat >= CEPH_RELEASE_NAUTILUS) {
-    bits.push_back(CEPHFS_FEATURE_NAUTILUS);
-  } else if (min_compat >= CEPH_RELEASE_MIMIC)
-    bits.push_back(CEPHFS_FEATURE_MIMIC);
-  else if (min_compat >= CEPH_RELEASE_LUMINOUS)
-    bits.push_back(CEPHFS_FEATURE_LUMINOUS);
-  else if (min_compat >= CEPH_RELEASE_KRAKEN)
-    bits.push_back(CEPHFS_FEATURE_KRAKEN);
-  else if (min_compat >= CEPH_RELEASE_JEWEL)
-    bits.push_back(CEPHFS_FEATURE_JEWEL);
-
-  std::sort(bits.begin(), bits.end());
-  required_client_features = feature_bitset_t(bits);
+  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) {
@@ -1403,18 +1663,18 @@ void Server::update_required_client_features()
       feature_bitset_t missing_features = required_client_features;
       missing_features -= session->info.client_metadata.features;
       if (!missing_features.empty()) {
-       bool blacklisted = mds->objecter->with_osdmap(
+       bool blocklisted = mds->objecter->with_osdmap(
            [session](const OSDMap &osd_map) -> bool {
-             return osd_map.is_blacklisted(session->info.inst.addr);
+             return osd_map.is_blocklisted(session->info.inst.addr);
            });
-       if (blacklisted)
+       if (blocklisted)
          continue;
 
        mds->clog->warn() << "evicting session " << *session << ", missing required features '"
                          << missing_features << "'";
-       std::stringstream ss;
+       CachedStackStringStream css;
        mds->evict_client(session->get_client().v, false,
-                         g_conf()->mds_session_blacklist_on_evict, ss);
+                         g_conf()->mds_session_blocklist_on_evict, *css);
       }
     }
   }
@@ -1438,17 +1698,33 @@ void Server::reconnect_gather_finish()
 
 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)
+  if (elapse1 < g_conf()->mds_reconnect_timeout && !reject_all_reconnect)
     return;
 
   vector<Session*> remaining_sessions;
@@ -1463,16 +1739,16 @@ void Server::reconnect_tick()
   }
 
   auto elapse2 = std::chrono::duration<double>(now - reconnect_last_seen).count();
-  if (elapse2 < g_conf()->mds_reconnect_timeout / 2) {
+  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;
+          << " clients have not reconnected in time" << dendl;
 
-  // If we're doing blacklist evictions, use this to wait for them before
+  // If we're doing blocklist evictions, use this to wait for them before
   // proceeding to reconnect_gather_finish
   MDSGatherBuilder gather(g_ceph_context);
 
@@ -1494,9 +1770,12 @@ void Server::reconnect_tick()
                      << ", after waiting " << elapse1
                      << " seconds during MDS startup";
 
-    if (g_conf()->mds_session_blacklist_on_timeout) {
-      std::stringstream ss;
-      mds->evict_client(session->get_client().v, false, true, ss,
+    // 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);
@@ -1505,10 +1784,11 @@ void Server::reconnect_tick()
     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 FunctionContext(
+    gather.set_finisher(new MDSInternalContextWrapper(mds, new LambdaContext(
            [this](int r){reconnect_gather_finish();})));
     gather.activate();
     reconnect_evicting = true;
@@ -1547,27 +1827,31 @@ void Server::recover_filelocks(CInode *in, bufferlist locks, int64_t client)
 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 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=0x" << std::hex << flags
+           << " 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, max_caps_per_client](auto& s) {
+  auto f = [&caps_session, enforce_max, enforce_liveness, trim, max_caps_per_client, cache_liveness_magnitude](auto& s) {
     auto num_caps = s->caps.size();
-    if (!enforce_max || num_caps > max_caps_per_client) {
+    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));
     }
   };
@@ -1648,11 +1932,11 @@ std::pair<bool, uint64_t> Server::recall_client_state(MDSGatherBuilder* gather,
 
       dout(7) << "  recalling " << recall << " caps; session_recall_throttle = " << session_recall_throttle << "; global_recall_throttle = " << global_recall_throttle << dendl;
 
-      auto m = MClientSession::create(CEPH_SESSION_RECALL_STATE);
+      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);
+        flush_session(session, *gather);
       }
       caps_recalled += session->notify_recall_sent(newlim);
       recall_throttle.hit(recall);
@@ -1676,7 +1960,7 @@ void Server::force_clients_readonly()
     if (!session->info.inst.name.is_client() ||
        !(session->is_open() || session->is_stale()))
       continue;
-    mds->send_message_client(MClientSession::create(CEPH_SESSION_FORCE_RO), session);
+    mds->send_message_client(make_message<MClientSession>(CEPH_SESSION_FORCE_RO), session);
   }
 }
 
@@ -1698,20 +1982,23 @@ void Server::journal_and_reply(MDRequestRef& mdr, CInode *in, CDentry *dn, LogEv
     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)
+  } else if (mdr->did_early_reply) {
     mds->locker->drop_rdlocks_for_early_reply(mdr.get());
-  else
+    if (dn && dn->is_waiter_for(CDentry::WAIT_UNLINK_FINISH))
+      mdlog->flush();
+  } else {
     mdlog->flush();
+  }
 }
 
 void Server::submit_mdlog_entry(LogEvent *le, MDSLogContextBase *fin, MDRequestRef& mdr,
@@ -1731,7 +2018,12 @@ void Server::submit_mdlog_entry(LogEvent *le, MDSLogContextBase *fin, MDRequestR
 void Server::respond_to_request(MDRequestRef& mdr, int r)
 {
   if (mdr->client_request) {
-    reply_client_request(mdr, MClientReply::create(*mdr->client_request, r));
+    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)
@@ -1742,7 +2034,7 @@ void Server::respond_to_request(MDRequestRef& mdr, int r)
 }
 
 // statistics mds req op number and latency 
-void Server::perf_gather_op_latency(const MClientRequest::const_ref &req, utime_t lat)
+void Server::perf_gather_op_latency(const cref_t<MClientRequest> &req, utime_t lat)
 {
   int code = l_mdss_first;
   switch(req->get_op()) {
@@ -1776,6 +2068,9 @@ void Server::perf_gather_op_latency(const MClientRequest::const_ref &req, utime_
   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;
@@ -1830,7 +2125,9 @@ void Server::perf_gather_op_latency(const MClientRequest::const_ref &req, utime_
   case CEPH_MDS_OP_RENAMESNAP:
     code = l_mdss_req_renamesnap_latency;
     break;
-  default: ceph_abort();
+  default:
+    dout(1) << ": unknown client op" << dendl;
+    return;
   }
   logger->tinc(code, lat);   
 }
@@ -1845,8 +2142,8 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
     return;
   }
 
-  if (mdr->has_more() && mdr->more()->has_journaled_slaves) {
-    dout(10) << "early_reply - there are journaled slaves, not allowed." << dendl;
+  if (mdr->has_more() && mdr->more()->has_journaled_peers) {
+    dout(10) << "early_reply - there are journaled peers, not allowed." << dendl;
     return; 
   }
 
@@ -1855,7 +2152,7 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
     return;
   }
 
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   entity_inst_t client_inst = req->get_source_inst();
   if (client_inst.name.is_mds())
     return;
@@ -1866,7 +2163,7 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
   }
 
 
-  auto reply = MClientReply::create(*req, 0);
+  auto reply = make_message<MClientReply>(*req, 0);
   reply->set_unsafe();
 
   // mark xlocks "done", indicating that we are exposing uncommitted changes.
@@ -1886,8 +2183,7 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
     if (tracedn)
       mdr->cap_releases.erase(tracedn->get_dir()->get_inode()->vino());
 
-    set_trace_dist(mdr->session, reply, tracei, tracedn, mdr->snapid,
-                  req->get_dentry_wanted(), mdr);
+    set_trace_dist(reply, tracei, tracedn, mdr);
   }
 
   reply->set_extra_bl(mdr->reply_extra_bl);
@@ -1898,6 +2194,9 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
   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);
   }
@@ -1912,10 +2211,10 @@ void Server::early_reply(MDRequestRef& mdr, CInode *tracei, CDentry *tracedn)
  * include a trace to tracei
  * Clean up mdr
  */
-void Server::reply_client_request(MDRequestRef& mdr, const MClientReply::ref &reply)
+void Server::reply_client_request(MDRequestRef& mdr, const ref_t<MClientReply> &reply)
 {
   ceph_assert(mdr.get());
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   
   dout(7) << "reply_client_request " << reply->get_result()
           << " (" << cpp_strerror(reply->get_result())
@@ -1944,20 +2243,21 @@ void Server::reply_client_request(MDRequestRef& mdr, const MClientReply::ref &re
   apply_allocated_inos(mdr, session);
 
   // get tracei/tracedn from mdr?
-  snapid_t snapid = mdr->snapid;
   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();
-  int dentry_wanted = req->get_dentry_wanted();
 
   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);
     }
@@ -1983,9 +2283,7 @@ void Server::reply_client_request(MDRequestRef& mdr, const MClientReply::ref &re
          mdcache->try_reconnect_cap(tracei, session);
       } else {
        // include metadata in reply
-       set_trace_dist(session, reply, tracei, tracedn,
-                      snapid, dentry_wanted,
-                      mdr);
+       set_trace_dist(reply, tracei, tracedn, mdr);
       }
     }
 
@@ -1997,6 +2295,10 @@ void Server::reply_client_request(MDRequestRef& mdr, const MClientReply::ref &re
     mds->send_message_client(reply, session);
   }
 
+  if (client_inst.name.is_mds() && reply->get_op() == CEPH_MDS_OP_RENAME) {
+    mds->send_message(reply, mdr->client_request->get_connection());
+  }
+
   if (req->is_queued_for_replay() &&
       (mdr->has_completed || reply->get_result() < 0)) {
     if (reply->get_result() < 0) {
@@ -2024,10 +2326,8 @@ void Server::reply_client_request(MDRequestRef& mdr, const MClientReply::ref &re
  *
  * trace is in reverse order (i.e. root inode comes last)
  */
-void Server::set_trace_dist(Session *session, const MClientReply::ref &reply,
+void Server::set_trace_dist(const ref_t<MClientReply> &reply,
                            CInode *in, CDentry *dn,
-                           snapid_t snapid,
-                           int dentry_wanted,
                            MDRequestRef& mdr)
 {
   // skip doing this for debugging purposes?
@@ -2041,13 +2341,12 @@ void Server::set_trace_dist(Session *session, const MClientReply::ref &reply,
   // inode, dentry, dir, ..., inode
   bufferlist bl;
   mds_rank_t whoami = mds->get_nodeid();
-  client_t client = session->get_client();
+  Session *session = mdr->session;
+  snapid_t snapid = mdr->snapid;
   utime_t now = ceph_clock_now();
 
   dout(20) << "set_trace_dist snapid " << snapid << dendl;
 
-  //assert((bool)dn == (bool)dentry_wanted);  // not true for snapshot lookups
-
   // realm
   if (snapid == CEPH_NOSNAP) {
     SnapRealm *realm;
@@ -2055,7 +2354,7 @@ void Server::set_trace_dist(Session *session, const MClientReply::ref &reply,
       realm = in->find_snaprealm();
     else
       realm = dn->get_dir()->get_inode()->find_snaprealm();
-    reply->snapbl = realm->get_snap_trace();
+    reply->snapbl = get_snap_trace(session, realm);
     dout(10) << "set_trace_dist snaprealm " << *realm << " len=" << reply->snapbl.length() << dendl;
   }
 
@@ -2075,21 +2374,14 @@ void Server::set_trace_dist(Session *session, const MClientReply::ref &reply,
     DirStat ds;
     ds.frag = dir->get_frag();
     ds.auth = dir->get_dir_auth().first;
-    if (dir->is_auth())
+    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);
-    if (snapid == CEPH_NOSNAP)
-      mds->locker->issue_client_lease(dn, client, bl, now, session);
-    else {
-      //null lease
-      LeaseStat e;
-      mds->locker->encode_lease(bl, session->info, e);
-    }
-    dout(20) << "set_trace_dist added dn   " << snapid << " " << *dn << dendl;
+    mds->locker->issue_client_lease(dn, in, mdr, now, bl);
   } else
     reply->head.is_dentry = 0;
 
@@ -2104,7 +2396,7 @@ void Server::set_trace_dist(Session *session, const MClientReply::ref &reply,
   reply->set_trace(bl);
 }
 
-void Server::handle_client_request(const MClientRequest::const_ref &req)
+void Server::handle_client_request(const cref_t<MClientRequest> &req)
 {
   dout(4) << "handle_client_request " << *req << dendl;
 
@@ -2119,13 +2411,14 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
     return;
   }
 
+  bool sessionclosed_isok = replay_unsafe_with_closed_session;
   // active session?
   Session *session = 0;
-  if (req->get_source().is_client()) {
+  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() ||
+    } else if ((session->is_closed() && (!mds->is_clientreplay() || !sessionclosed_isok)) ||
               session->is_closing() ||
               session->is_killing()) {
       dout(5) << "session closed|closing|killing, dropping" << dendl;
@@ -2151,6 +2444,8 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
     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() ||
@@ -2158,7 +2453,7 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
           req->get_op() != CEPH_MDS_OP_OPEN &&
           req->get_op() != CEPH_MDS_OP_CREATE)) {
        dout(5) << "already completed " << req->get_reqid() << dendl;
-        auto reply = MClientReply::create(*req, 0);
+        auto reply = make_message<MClientReply>(*req, 0);
        if (created != inodeno_t()) {
          bufferlist extra;
          encode(created, extra);
@@ -2197,13 +2492,13 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
       if (session->get_num_completed_requests() >=
          (g_conf()->mds_max_completed_requests << session->get_num_trim_requests_warnings())) {
        session->inc_num_trim_requests_warnings();
-       stringstream ss;
-       ss << "client." << session->get_client() << " does not advance its oldest_client_tid ("
+       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() << ss.str();
-       dout(20) << __func__ << " " << ss.str() << dendl;
+       mds->clog->warn() << css->strv();
+       dout(20) << __func__ << " " << css->strv() << dendl;
       }
     }
   }
@@ -2223,7 +2518,7 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
 
   // process embedded cap releases?
   //  (only if NOT replay!)
-  if (!req->releases.empty() && req->get_source().is_client() && !req->is_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);
@@ -2235,13 +2530,45 @@ void Server::handle_client_request(const MClientRequest::const_ref &req)
   return;
 }
 
+void Server::handle_client_reply(const cref_t<MClientReply> &reply)
+{
+  dout(4) << "handle_client_reply " << *reply << dendl;
+
+  ceph_assert(reply->is_safe());
+  ceph_tid_t tid = reply->get_tid();
+
+  if (mds->internal_client_requests.count(tid) == 0) {
+    dout(1) << " no pending request on tid " << tid << dendl;
+    return;
+  }
+
+  auto &req = mds->internal_client_requests.at(tid);
+  CDentry *dn = req.get_dentry();
+
+  switch (reply->get_op()) {
+  case CEPH_MDS_OP_RENAME:
+    if (dn) {
+      dn->state_clear(CDentry::STATE_REINTEGRATING);
+
+      MDSContext::vec finished;
+      dn->take_waiting(CDentry::WAIT_REINTEGRATE_FINISH, finished);
+      mds->queue_waiters(finished);
+    }
+    break;
+  default:
+    dout(5) << " unknown client op " << reply->get_op() << dendl;
+  }
+
+  mds->internal_client_requests.erase(tid);
+}
+
 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->mdsmap->get_metadata_pool());
+      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;
@@ -2251,37 +2578,53 @@ void Server::handle_osd_map()
 void Server::dispatch_client_request(MDRequestRef& mdr)
 {
   // we shouldn't be waiting on anyone.
-  ceph_assert(!mdr->has_more() || mdr->more()->waiting_on_slave.empty());
+  ceph_assert(!mdr->has_more() || mdr->more()->waiting_on_peer.empty());
 
   if (mdr->killed) {
     dout(10) << "request " << *mdr << " was killed" << dendl;
-    return;
+    //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 MClientRequest::const_ref &req = mdr->client_request;
+  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()) {
-    if (mdcache->is_readonly()) {
-      dout(10) << " read-only FS" << dendl;
-      respond_to_request(mdr, -EROFS);
-      return;
-    }
-    if (mdr->has_more() && mdr->more()->slave_error) {
-      dout(10) << " got error from slaves" << dendl;
-      respond_to_request(mdr, mdr->more()->slave_error);
-      return;
-    }
+  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 ||
@@ -2292,12 +2635,16 @@ void Server::dispatch_client_request(MDRequestRef& mdr)
         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 slave request
+        (!mdr->has_more() || mdr->more()->witnessed.empty())) // haven't started peer request
        ) {
 
-      dout(20) << __func__ << ": full, responding ENOSPC to op " << ceph_mds_op_name(req->get_op()) << dendl;
-      respond_to_request(mdr, -ENOSPC);
-      return;
+      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;
     }
@@ -2325,6 +2672,9 @@ void Server::dispatch_client_request(MDRequestRef& mdr)
   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);
@@ -2405,41 +2755,47 @@ void Server::dispatch_client_request(MDRequestRef& mdr)
 
   default:
     dout(1) << " unknown client op " << req->get_op() << dendl;
-    respond_to_request(mdr, -EOPNOTSUPP);
+    respond_to_request(mdr, -CEPHFS_EOPNOTSUPP);
   }
 }
 
 
 // ---------------------------------------
-// SLAVE REQUESTS
+// PEER REQUESTS
 
-void Server::handle_slave_request(const MMDSSlaveRequest::const_ref &m)
+void Server::handle_peer_request(const cref_t<MMDSPeerRequest> &m)
 {
-  dout(4) << "handle_slave_request " << m->get_reqid() << " from " << m->get_source() << dendl;
+  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_slave_request);
+  if (logger) logger->inc(l_mdss_handle_peer_request);
 
   // reply?
   if (m->is_reply())
-    return handle_slave_request_reply(m);
+    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() == MMDSSlaveRequest::OP_RENAMENOTIFY) {
-    auto reply = MMDSSlaveRequest::create(m->get_reqid(), m->get_attempt(), MMDSSlaveRequest::OP_RENAMENOTIFYACK);
+  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) {
-    straydn = mdcache->add_replica_stray(m->straybl, from);
+    mdcache->decode_replica_stray(straydn, nullptr, m->straybl, from);
     ceph_assert(straydn);
     m->straybl.clear();
   }
 
-  // am i a new slave?
+  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?
@@ -2452,24 +2808,35 @@ void Server::handle_slave_request(const MMDSSlaveRequest::const_ref &m)
       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->slave_to_mds != from) {
-      dout(10) << "local request " << *mdr << " not slave to mds." << from << dendl;
+    } else if (mdr->peer_to_mds != from) {
+      dout(10) << "local request " << *mdr << " not peer to mds." << from << dendl;
       return;
     }
 
-    if (m->get_op() == MMDSSlaveRequest::OP_FINISH && m->is_abort()) {
-      mdr->aborted = true;
-      if (mdr->slave_request) {
-       // only abort on-going xlock, wrlock and auth pin
-       ceph_assert(!mdr->slave_did_prepare());
+    // 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;
@@ -2477,45 +2844,41 @@ void Server::handle_slave_request(const MMDSSlaveRequest::const_ref &m)
   }
   if (!mdr.get()) {
     // new?
-    if (m->get_op() == MMDSSlaveRequest::OP_FINISH) {
-      dout(10) << "missing slave request for " << m->get_reqid() 
+    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_slave(m->get_reqid(), m->get_attempt(), m);
+    mdr = mdcache->request_start_peer(m->get_reqid(), m->get_attempt(), m);
     mdr->set_op_stamp(m->op_stamp);
   }
-  ceph_assert(mdr->slave_request == 0);     // only one at a time, please!  
+  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->is_active() && !mds->is_stopping()) {
-    dout(3) << "not clientreplay|active yet, waiting" << dendl;
-    mds->wait_for_replay(new C_MDS_RetryMessage(mds, m));
-    return;
-  } else if (mds->is_clientreplay() && !mds->mdsmap->is_clientreplay(from) &&
-            mdr->locks.empty()) {
+  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_slave_request(m);
+  mdr->reset_peer_request(m);
   
-  dispatch_slave_request(mdr);
+  dispatch_peer_request(mdr);
 }
 
-void Server::handle_slave_request_reply(const MMDSSlaveRequest::const_ref &m)
+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_master(r, from)) {
-      dout(10) << "handle_slave_request_reply ignoring slave reply from mds."
+    if (!mdcache->have_uncommitted_leader(r, from)) {
+      dout(10) << "handle_peer_request_reply ignoring peer reply from mds."
               << from << " reqid " << r << dendl;
       return;
     }
@@ -2524,88 +2887,87 @@ void Server::handle_slave_request_reply(const MMDSSlaveRequest::const_ref &m)
     return;
   }
 
-  if (m->get_op() == MMDSSlaveRequest::OP_COMMITTED) {
+  if (m->get_op() == MMDSPeerRequest::OP_COMMITTED) {
     metareqid_t r = m->get_reqid();
-    mdcache->committed_master_slave(r, from);
+    mdcache->committed_leader_peer(r, from);
     return;
   }
 
   MDRequestRef mdr = mdcache->request_get(m->get_reqid());
   if (m->get_attempt() != mdr->attempt) {
-    dout(10) << "handle_slave_request_reply " << *mdr << " ignoring reply from other attempt "
+    dout(10) << "handle_peer_request_reply " << *mdr << " ignoring reply from other attempt "
             << m->get_attempt() << dendl;
     return;
   }
 
   switch (m->get_op()) {
-  case MMDSSlaveRequest::OP_XLOCKACK:
+  case MMDSPeerRequest::OP_XLOCKACK:
     {
-      // identify lock, master request
+      // identify lock, leader request
       SimpleLock *lock = mds->locker->get_lock(m->get_lock_type(),
                                               m->get_object_info());
-      mdr->more()->slaves.insert(from);
+      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->locks.emplace_hint(mdr->locks.end(), lock, MutationImpl::LockOp::XLOCK);
+      mdr->emplace_lock(lock, MutationImpl::LockOp::XLOCK);
       mdr->finish_locking(lock);
       lock->get_xlock(mdr, mdr->get_client());
 
-      ceph_assert(mdr->more()->waiting_on_slave.count(from));
-      mdr->more()->waiting_on_slave.erase(from);
-      ceph_assert(mdr->more()->waiting_on_slave.empty());
+      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 MMDSSlaveRequest::OP_WRLOCKACK:
+  case MMDSPeerRequest::OP_WRLOCKACK:
     {
-      // identify lock, master request
+      // identify lock, leader request
       SimpleLock *lock = mds->locker->get_lock(m->get_lock_type(),
                                               m->get_object_info());
-      mdr->more()->slaves.insert(from);
+      mdr->more()->peers.insert(from);
       dout(10) << "got remote wrlock on " << *lock << " on " << *lock->get_parent() << dendl;
-      auto it = mdr->locks.emplace_hint(mdr->locks.end(),
-                                       lock, MutationImpl::LockOp::REMOTE_WRLOCK, from);
+      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_slave.count(from));
-      mdr->more()->waiting_on_slave.erase(from);
-      ceph_assert(mdr->more()->waiting_on_slave.empty());
+      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 MMDSSlaveRequest::OP_AUTHPINACK:
-    handle_slave_auth_pin_ack(mdr, m);
+  case MMDSPeerRequest::OP_AUTHPINACK:
+    handle_peer_auth_pin_ack(mdr, m);
     break;
 
-  case MMDSSlaveRequest::OP_LINKPREPACK:
-    handle_slave_link_prep_ack(mdr, m);
+  case MMDSPeerRequest::OP_LINKPREPACK:
+    handle_peer_link_prep_ack(mdr, m);
     break;
 
-  case MMDSSlaveRequest::OP_RMDIRPREPACK:
-    handle_slave_rmdir_prep_ack(mdr, m);
+  case MMDSPeerRequest::OP_RMDIRPREPACK:
+    handle_peer_rmdir_prep_ack(mdr, m);
     break;
 
-  case MMDSSlaveRequest::OP_RENAMEPREPACK:
-    handle_slave_rename_prep_ack(mdr, m);
+  case MMDSPeerRequest::OP_RENAMEPREPACK:
+    handle_peer_rename_prep_ack(mdr, m);
     break;
 
-  case MMDSSlaveRequest::OP_RENAMENOTIFYACK:
-    handle_slave_rename_notify_ack(mdr, m);
+  case MMDSPeerRequest::OP_RENAMENOTIFYACK:
+    handle_peer_rename_notify_ack(mdr, m);
     break;
 
   default:
-    ceph_abort();
+    ceph_abort_msg("unknown op " + to_string(m->get_op()) + " requested");
   }
 }
 
-void Server::dispatch_slave_request(MDRequestRef& mdr)
+void Server::dispatch_peer_request(MDRequestRef& mdr)
 {
-  dout(7) << "dispatch_slave_request " << *mdr << " " << *mdr->slave_request << dendl;
+  dout(7) << "dispatch_peer_request " << *mdr << " " << *mdr->peer_request << dendl;
 
   if (mdr->aborted) {
     dout(7) << " abort flag set, finishing" << dendl;
@@ -2613,22 +2975,22 @@ void Server::dispatch_slave_request(MDRequestRef& mdr)
     return;
   }
 
-  if (logger) logger->inc(l_mdss_dispatch_slave_request);
+  if (logger) logger->inc(l_mdss_dispatch_peer_request);
 
-  int op = mdr->slave_request->get_op();
+  int op = mdr->peer_request->get_op();
   switch (op) {
-  case MMDSSlaveRequest::OP_XLOCK:
-  case MMDSSlaveRequest::OP_WRLOCK:
+  case MMDSPeerRequest::OP_XLOCK:
+  case MMDSPeerRequest::OP_WRLOCK:
     {
       // identify object
-      SimpleLock *lock = mds->locker->get_lock(mdr->slave_request->get_lock_type(),
-                                              mdr->slave_request->get_object_info());
+      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(); // can this happen, if we auth pinned properly.
+       ceph_abort_msg("don't have object"); // can this happen, if we auth pinned properly.
       }
-      if (op == MMDSSlaveRequest::OP_XLOCK && !lock->get_parent()->is_auth()) {
+      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 {
@@ -2643,13 +3005,13 @@ void Server::dispatch_slave_request(MDRequestRef& mdr)
 
        int replycode = 0;
        switch (op) {
-       case MMDSSlaveRequest::OP_XLOCK:
+       case MMDSPeerRequest::OP_XLOCK:
          lov.add_xlock(lock);
-         replycode = MMDSSlaveRequest::OP_XLOCKACK;
+         replycode = MMDSPeerRequest::OP_XLOCKACK;
          break;
-       case MMDSSlaveRequest::OP_WRLOCK:
+       case MMDSPeerRequest::OP_WRLOCK:
          lov.add_wrlock(lock);
-         replycode = MMDSSlaveRequest::OP_WRLOCKACK;
+         replycode = MMDSPeerRequest::OP_WRLOCKACK;
          break;
        }
        
@@ -2657,33 +3019,33 @@ void Server::dispatch_slave_request(MDRequestRef& mdr)
          return;
        
        // ack
-       auto r = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, replycode);
+       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 == MMDSSlaveRequest::OP_XLOCKACK)
+       if (replycode == MMDSPeerRequest::OP_XLOCKACK)
          lock->encode_locked_state(r->get_lock_data());
-       mds->send_message(r, mdr->slave_request->get_connection());
+       mds->send_message(r, mdr->peer_request->get_connection());
       }
 
       // done.
-      mdr->reset_slave_request();
+      mdr->reset_peer_request();
     }
     break;
 
-  case MMDSSlaveRequest::OP_UNXLOCK:
-  case MMDSSlaveRequest::OP_UNWRLOCK:
+  case MMDSPeerRequest::OP_UNXLOCK:
+  case MMDSPeerRequest::OP_UNWRLOCK:
     {  
-      SimpleLock *lock = mds->locker->get_lock(mdr->slave_request->get_lock_type(),
-                                              mdr->slave_request->get_object_info());
+      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 MMDSSlaveRequest::OP_UNXLOCK:
+      case MMDSPeerRequest::OP_UNXLOCK:
        mds->locker->xlock_finish(it, mdr.get(), &need_issue);
        break;
-      case MMDSSlaveRequest::OP_UNWRLOCK:
+      case MMDSPeerRequest::OP_UNWRLOCK:
        mds->locker->wrlock_finish(it, mdr.get(), &need_issue);
        break;
       }
@@ -2691,53 +3053,42 @@ void Server::dispatch_slave_request(MDRequestRef& mdr)
        mds->locker->issue_caps(static_cast<CInode*>(lock->get_parent()));
 
       // done.  no ack necessary.
-      mdr->reset_slave_request();
+      mdr->reset_peer_request();
     }
     break;
 
-  case MMDSSlaveRequest::OP_DROPLOCKS:
-    mds->locker->drop_locks(mdr.get());
-    mdr->reset_slave_request();
-    break;
-
-  case MMDSSlaveRequest::OP_AUTHPIN:
-    handle_slave_auth_pin(mdr);
+  case MMDSPeerRequest::OP_AUTHPIN:
+    handle_peer_auth_pin(mdr);
     break;
 
-  case MMDSSlaveRequest::OP_LINKPREP:
-  case MMDSSlaveRequest::OP_UNLINKPREP:
-    handle_slave_link_prep(mdr);
+  case MMDSPeerRequest::OP_LINKPREP:
+  case MMDSPeerRequest::OP_UNLINKPREP:
+    handle_peer_link_prep(mdr);
     break;
 
-  case MMDSSlaveRequest::OP_RMDIRPREP:
-    handle_slave_rmdir_prep(mdr);
+  case MMDSPeerRequest::OP_RMDIRPREP:
+    handle_peer_rmdir_prep(mdr);
     break;
 
-  case MMDSSlaveRequest::OP_RENAMEPREP:
-    handle_slave_rename_prep(mdr);
-    break;
-
-  case MMDSSlaveRequest::OP_FINISH:
-    // information about rename imported caps
-    if (mdr->slave_request->inode_export.length() > 0)
-      mdr->more()->inode_import = mdr->slave_request->inode_export;
-    // finish off request.
-    mdcache->request_finish(mdr);
+  case MMDSPeerRequest::OP_RENAMEPREP:
+    handle_peer_rename_prep(mdr);
     break;
 
   default: 
-    ceph_abort();
+    ceph_abort_msg("unknown op "+ to_string(op)+ " received");
   }
 }
 
-void Server::handle_slave_auth_pin(MDRequestRef& mdr)
+void Server::handle_peer_auth_pin(MDRequestRef& mdr)
 {
-  dout(10) << "handle_slave_auth_pin " << *mdr << dendl;
+  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;
@@ -2746,7 +3097,7 @@ void Server::handle_slave_auth_pin(MDRequestRef& mdr)
   }
 
   if (!fail) {
-    for (const auto &oi : mdr->slave_request->get_authpins()) {
+    for (const auto &oi : mdr->peer_request->get_authpins()) {
       MDSCacheObject *object = mdcache->get_object(oi);
       if (!object) {
        dout(10) << " don't have " << oi << dendl;
@@ -2755,55 +3106,50 @@ void Server::handle_slave_auth_pin(MDRequestRef& mdr)
       }
 
       objects.push_back(object);
-      if (oi == mdr->slave_request->get_authpin_freeze())
+      if (oi == mdr->peer_request->get_authpin_freeze())
        auth_pin_freeze = static_cast<CInode*>(object);
     }
   }
   
   // can we auth pin them?
   if (!fail) {
-    for (list<MDSCacheObject*>::iterator p = objects.begin();
-        p != objects.end();
-        ++p) {
-      if (!(*p)->is_auth()) {
-       dout(10) << " not auth for " << **p << dendl;
+    for (const auto& obj : objects) {
+      if (!obj->is_auth()) {
+       dout(10) << " not auth for " << *obj << dendl;
        fail = true;
        break;
       }
-      if (mdr->is_auth_pinned(*p))
+      if (mdr->is_auth_pinned(obj))
        continue;
-      if (!mdr->can_auth_pin(*p)) {
-       if (mdr->slave_request->is_nonblock()) {
-         dout(10) << " can't auth_pin (freezing?) " << **p << " nonblocking" << dendl;
+      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 " << **p << dendl;
-       (*p)->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
+       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(*p);
-       return;
+       mds->locker->notify_freeze_waiter(obj);
+       goto blocked;
       }
     }
   }
 
-  // auth pin!
-  if (fail) {
-    mdr->drop_local_auth_pins();  // just in case
-  } else {
+  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_slave_rename_prep() call freeze_inode() to wait for all other operations
+    /* 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_slave_rename_prep() is called.
+     * 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.
      */
@@ -2812,54 +3158,72 @@ void Server::handle_slave_auth_pin(MDRequestRef& mdr)
       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();
-       return;
+       goto blocked;
       }
     }
-    for (list<MDSCacheObject*>::iterator p = objects.begin();
-        p != objects.end();
-        ++p) {
-      dout(10) << "auth_pinning " << **p << dendl;
-      mdr->auth_pin(*p);
-    }
   }
 
-  // ack!
-  auto reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_AUTHPINACK);
-  
-  // return list of my auth_pins (if any)
-  for (const auto &p : mdr->auth_pins) {
-    MDSCacheObjectInfo info;
-    p->set_object_info(info);
-    reply->get_authpins().push_back(info);
-    if (p == (MDSCacheObject*)auth_pin_freeze)
-      auth_pin_freeze->set_object_info(reply->get_authpin_freeze());
-  }
+  reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_AUTHPINACK);
 
-  if (wouldblock)
-    reply->mark_error_wouldblock();
-  if (readonly)
-    reply->mark_error_rofs();
+  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->slave_to_mds);
+  mds->send_message_mds(reply, mdr->peer_to_mds);
   
   // clean up this request
-  mdr->reset_slave_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_slave_auth_pin_ack(MDRequestRef& mdr, const MMDSSlaveRequest::const_ref &ack)
+void Server::handle_peer_auth_pin_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
 {
-  dout(10) << "handle_slave_auth_pin_ack on " << *mdr << " " << *ack << dendl;
+  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;
-    if (!mdr->is_auth_pinned(object))
-      mdr->remote_auth_pins[object] = from;
+    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);
@@ -2868,45 +3232,42 @@ void Server::handle_slave_auth_pin_ack(MDRequestRef& mdr, const MMDSSlaveRequest
   // removed frozen auth pin ?
   if (mdr->more()->is_remote_frozen_authpin &&
       ack->get_authpin_freeze() == MDSCacheObjectInfo()) {
-    auto p = mdr->remote_auth_pins.find(mdr->more()->rename_inode);
-    ceph_assert(p != mdr->remote_auth_pins.end());
-    if (p->second == from) {
+    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?
-  auto p = mdr->remote_auth_pins.begin();
-  while (p != mdr->remote_auth_pins.end()) {
-    MDSCacheObject* object = p->first;
-    if (p->second == from && pinned.count(object) == 0) {
+  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->remote_auth_pins.erase(p++);
-    } else {
-      ++p;
+      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()->slave_error = -EROFS;
-    mdr->aborted = true;
+    mdr->more()->peer_error = -CEPHFS_EROFS;
   } else if (ack->is_error_wouldblock()) {
-    mdr->more()->slave_error = -EWOULDBLOCK;
-    mdr->aborted = true;
+    mdr->more()->peer_error = -CEPHFS_EWOULDBLOCK;
   }
-  
-  // note slave
-  mdr->more()->slaves.insert(from);
-
-  // clear from waiting list
-  ceph_assert(mdr->more()->waiting_on_slave.count(from));
-  mdr->more()->waiting_on_slave.erase(from);
 
   // go again?
-  if (mdr->more()->waiting_on_slave.empty())
+  if (mdr->more()->waiting_on_peer.empty())
     mdcache->dispatch_request(mdr);
   else 
-    dout(10) << "still waiting on slaves " << mdr->more()->waiting_on_slave << dendl;
+    dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
 }
 
 
@@ -2943,121 +3304,73 @@ bool Server::check_access(MDRequestRef& mdr, CInode *in, unsigned mask)
  * check whether fragment has reached maximum size
  *
  */
-bool Server::check_fragment_space(MDRequestRef &mdr, CDir *in)
+bool Server::check_fragment_space(MDRequestRef &mdr, CDir *dir)
 {
-  const auto size = in->get_frag_size();
-  if (size >= g_conf()->mds_bal_fragment_size_max) {
-    dout(10) << "fragment " << *in << " size exceeds " << g_conf()->mds_bal_fragment_size_max << " (ENOSPC)" << dendl;
-    respond_to_request(mdr, -ENOSPC);
+  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;
 }
 
-
-/** validate_dentry_dir
+/**
+ * check whether entries in a dir reached maximum size
  *
- * verify that the dir exists and would own the dname.
- * do not check if the dentry exists.
  */
-CDir *Server::validate_dentry_dir(MDRequestRef& mdr, CInode *diri, std::string_view dname)
+bool Server::check_dir_max_entries(MDRequestRef &mdr, CDir *in)
 {
-  // make sure parent is a dir?
-  if (!diri->is_dir()) {
-    dout(7) << "validate_dentry_dir: not a dir" << dendl;
-    respond_to_request(mdr, -ENOTDIR);
-    return NULL;
-  }
-
-  // which dirfrag?
-  frag_t fg = diri->pick_dirfrag(dname);
-  CDir *dir = try_open_auth_dirfrag(diri, fg, mdr);
-  if (!dir)
-    return 0;
-
-  // frozen?
-  if (dir->is_frozen()) {
-    dout(7) << "dir is frozen " << *dir << dendl;
-    dir->add_waiter(CDir::WAIT_UNFREEZE, new C_MDS_RetryRequest(mdcache, mdr));
-    return NULL;
+  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 dir;
+  return true;
 }
 
 
-/** prepare_null_dentry
- * prepare a null (or existing) dentry in given dir. 
- * wait for any dn lock.
- */
-CDentry* Server::prepare_null_dentry(MDRequestRef& mdr, CDir *dir, std::string_view dname, bool okexist)
-{
-  dout(10) << "prepare_null_dentry " << dname << " in " << *dir << dendl;
-  ceph_assert(dir->is_auth());
-
-  client_t client = mdr->get_client();
-
-  // does it already exist?
-  CDentry *dn = dir->lookup(dname);
-  if (dn) {
-    /*
-    if (dn->lock.is_xlocked_by_other(mdr)) {
-      dout(10) << "waiting on xlocked dentry " << *dn << dendl;
-      dn->lock.add_waiter(SimpleLock::WAIT_RD, new C_MDS_RetryRequest(mdcache, mdr));
-      return 0;
-    }
-    */
-    if (!dn->get_linkage(client, mdr)->is_null()) {
-      // name already exists
-      dout(10) << "dentry " << dname << " exists in " << *dir << dendl;
-      if (!okexist) {
-        respond_to_request(mdr, -EEXIST);
-        return 0;
-      }
-    } else {
-      snapid_t next_snap = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
-      dn->first = std::max(dn->first, next_snap);
-    }
-    return dn;
-  }
-
-  // make sure dir is complete
-  if (!dir->is_complete() && (!dir->has_bloom() || dir->is_in_bloom(dname))) {
-    dout(7) << " incomplete dir contents for " << *dir << ", fetching" << dendl;
-    dir->fetch(new C_MDS_RetryRequest(mdcache, mdr));
-    return 0;
-  }
-  
-  // create
-  dn = dir->add_null_dentry(dname, mdcache->get_global_snaprealm()->get_newest_seq() + 1);
-  dn->mark_new();
-  dout(10) << "prepare_null_dentry added " << *dn << dendl;
-  return dn;
-}
-
 CDentry* Server::prepare_stray_dentry(MDRequestRef& mdr, CInode *in)
 {
+  string straydname;
+  in->name_stray_dentry(straydname);
+
   CDentry *straydn = mdr->straydn;
   if (straydn) {
-    string straydname;
-    in->name_stray_dentry(straydname);
-    if (straydn->get_name() == straydname)
-      return straydn;
-
-    ceph_assert(!mdr->done_locking);
-    mdr->unpin(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 NULL;
+    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 = mdcache->get_or_create_stray_dentry(in);
+  straydn->state_set(CDentry::STATE_STRAY);
   mdr->straydn = straydn;
   mdr->pin(straydn);
+
   return straydn;
 }
 
@@ -3066,41 +3379,56 @@ CDentry* Server::prepare_stray_dentry(MDRequestRef& mdr, CInode *in)
  * 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,
-                                 file_layout_t *layout)
+                                 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_opening();
+  bool allow_prealloc_inos = mdr->session->is_open();
 
-  // assign ino
-  if (allow_prealloc_inos &&
-      mdr->session->info.prealloc_inos.size()) {
-    mdr->used_prealloc_ino = 
-      in->inode.ino = mdr->session->take_ino(useino);  // prealloc -> used
-    mds->sessionmap.mark_projected(mdr->session);
+  inodeno_t _useino = useino;
 
-    dout(10) << "prepare_new_inode used_prealloc " << mdr->used_prealloc_ino
-            << " (" << mdr->session->info.prealloc_inos
-            << ", " << mdr->session->info.prealloc_inos.size() << " left)"
-            << dendl;
-  } else {
-    mdr->alloc_ino = 
-      in->inode.ino = mds->inotable->project_alloc_id();
-    dout(10) << "prepare_new_inode alloc " << mdr->alloc_ino << dendl;
-  }
+  // 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 != in->inode.ino) {
-    dout(0) << "WARNING: client specified " << useino << " and i allocated " << in->inode.ino << dendl;
+  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 " << in->inode.ino;
+       << " 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();
@@ -3111,63 +3439,67 @@ CInode* Server::prepare_new_inode(MDRequestRef& mdr, CDir *dir, inodeno_t useino
     dout(10) << "prepare_new_inode prealloc " << mdr->prealloc_inos << dendl;
   }
 
-  in->inode.version = 1;
-  in->inode.xattr_version = 1;
-  in->inode.nlink = 1;   // FIXME
+  _inode->version = 1;
+  _inode->xattr_version = 1;
+  _inode->nlink = 1;   // FIXME
 
-  in->inode.mode = mode;
+  _inode->mode = mode;
 
-  memset(&in->inode.dir_layout, 0, sizeof(in->inode.dir_layout));
-  if (in->inode.is_dir()) {
-    in->inode.dir_layout.dl_dir_hash = g_conf()->mds_default_dir_hash;
+  // 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) {
-    in->inode.layout = *layout;
+    _inode->layout = *layout;
   } else {
-    in->inode.layout = mdcache->default_file_layout;
+    _inode->layout = mdcache->default_file_layout;
   }
 
-  in->inode.truncate_size = -1ull;  // not truncated, yet!
-  in->inode.truncate_seq = 1; /* starting with 1, 0 is kept for no-truncation logic */
+  _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" << diri->inode.mode << " new mode 0" << mode << dec << dendl;
+  dout(10) << oct << " dir mode 0" << pip->mode << " new mode 0" << mode << dec << dendl;
 
-  if (diri->inode.mode & S_ISGID) {
+  if (pip->mode & S_ISGID) {
     dout(10) << " dir is sticky" << dendl;
-    in->inode.gid = diri->inode.gid;
+    _inode->gid = pip->gid;
     if (S_ISDIR(mode)) {
-      dout(10) << " new dir also sticky" << dendl;      
-      in->inode.mode |= S_ISGID;
+      dout(10) << " new dir also sticky" << dendl;
+      _inode->mode |= S_ISGID;
     }
-  } else 
-    in->inode.gid = mdr->client_request->get_caller_gid();
+  } else {
+    _inode->gid = mdr->client_request->get_caller_gid();
+  }
 
-  in->inode.uid = mdr->client_request->get_caller_uid();
+  _inode->uid = mdr->client_request->get_caller_uid();
 
-  in->inode.btime = in->inode.ctime = in->inode.mtime = in->inode.atime =
+  _inode->btime = _inode->ctime = _inode->mtime = _inode->atime =
     mdr->get_op_stamp();
 
-  in->inode.change_attr = 0;
+  _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;
 
-  const MClientRequest::const_ref &req = mdr->client_request;
   if (req->get_data().length()) {
     auto p = req->get_data().cbegin();
 
     // xattrs on new inode?
-    CInode::mempool_xattr_map xattrs;
-    decode(xattrs, p);
-    for (const auto &p : xattrs) {
-      dout(10) << "prepare_new_inode setting xattr " << p.first << dendl;
-      auto em = in->xattrs.emplace(std::piecewise_construct, std::forward_as_tuple(p.first), std::forward_as_tuple(p.second));
-      if (!em.second)
-        em.first->second = p.second;
-    }
+    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))
-    in->inode.inline_data.version = CEPH_INLINE_NONE;
+    _inode->inline_data.version = CEPH_INLINE_NONE;
 
   mdcache->add_inode(in);  // add
   dout(10) << "prepare_new_inode " << *in << dendl;
@@ -3199,133 +3531,131 @@ void Server::apply_allocated_inos(MDRequestRef& mdr, Session *session)
   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.used_inos.erase(mdr->used_prealloc_ino);
+    session->info.prealloc_inos.erase(mdr->used_prealloc_ino);
     mds->sessionmap.mark_dirty(session);
   }
 }
 
-class C_MDS_TryFindInode : public ServerContext {
+struct C_MDS_TryOpenInode : public ServerContext {
   MDRequestRef mdr;
-public:
-  C_MDS_TryFindInode(Server *s, MDRequestRef& r) : ServerContext(s), mdr(r) {}
+  inodeno_t ino;
+  C_MDS_TryOpenInode(Server *s, MDRequestRef& r, inodeno_t i) :
+    ServerContext(s), mdr(r), ino(i) {}
   void finish(int r) override {
-    if (r == -ESTALE) // :( find_ino_peers failed
-      server->respond_to_request(mdr, r);
-    else
-      server->dispatch_client_request(mdr);
-  }
-};
-
-class CF_MDS_MDRContextFactory : public MDSContextFactory {
-public:
-  CF_MDS_MDRContextFactory(MDCache *cache, MDRequestRef &mdr) : cache(cache), mdr(mdr) {}
-  MDSContext *build() {
-    return new C_MDS_RetryRequest(cache, mdr);
+    server->_try_open_ino(mdr, r, ino);
   }
-private:
-  MDCache *cache;
-  MDRequestRef mdr;
 };
 
-CDir *Server::traverse_to_auth_dir(MDRequestRef& mdr, vector<CDentry*> &trace, filepath refpath)
+void Server::_try_open_ino(MDRequestRef& mdr, int r, inodeno_t ino)
 {
-  // figure parent dir vs dname
-  if (refpath.depth() == 0) {
-    dout(7) << "can't do that to root" << dendl;
-    respond_to_request(mdr, -EINVAL);
-    return 0;
-  }
-  string dname = refpath.last_dentry();
-  refpath.pop_dentry();
-  
-  dout(10) << "traverse_to_auth_dir dirpath " << refpath << " dname " << dname << dendl;
+  dout(10) << "_try_open_ino " << mdr.get() << " ino " << ino << " r=" << r << dendl;
 
-  // traverse to parent dir
-  CInode *diri;
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, refpath, &trace, &diri, MDS_TRAVERSE_FORWARD);
-  if (r > 0) return 0; // delayed
-  if (r < 0) {
-    if (r == -ESTALE) {
-      dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
-      mdcache->find_ino_peers(refpath.get_ino(), new C_MDS_TryFindInode(this, mdr));
-      return 0;
-    }
-    respond_to_request(mdr, r);
-    return 0;
+  // `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;
   }
 
-  // is it an auth dir?
-  CDir *dir = validate_dentry_dir(mdr, diri, dname);
-  if (!dir)
-    return 0; // forwarded or waiting for freeze
-
-  dout(10) << "traverse_to_auth_dir " << *dir << dendl;
-  return dir;
+  // 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, int n,
-                                   MutationImpl::LockOpVec& lov,
+CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr,
                                    bool want_auth,
-                                   bool no_want_auth, /* for readdir, who doesn't want auth _even_if_ it's
-                                                         a snapped dir */
-                                   file_layout_t **layout,
-                                   bool no_lookup)    // true if we cannot return a null dentry lease
+                                   bool no_want_auth)
 {
-  const filepath& refpath = n ? mdr->get_filepath2() : mdr->get_filepath();
+  const filepath& refpath = mdr->get_filepath();
   dout(10) << "rdlock_path_pin_ref " << *mdr << " " << refpath << dendl;
 
-  if (mdr->done_locking)
-    return mdr->in[n];
+  if (mdr->locking_state & MutationImpl::PATH_LOCKED)
+    return mdr->in[0];
 
   // traverse
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, refpath, &mdr->dn[n], &mdr->in[n], MDS_TRAVERSE_FORWARD);
+  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 NULL; // delayed
+    return nullptr; // delayed
   if (r < 0) {  // error
-    if (r == -ENOENT && n == 0 && !mdr->dn[n].empty()) {
-      if (!no_lookup) {
-        mdr->tracedn = mdr->dn[n].back();
-      }
+    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 == -ESTALE) {
-      dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
-      MDSContext *c = new C_MDS_TryFindInode(this, mdr);
-      mdcache->find_ino_peers(refpath.get_ino(), c);
+    } 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 0;
+    return nullptr;
   }
-  CInode *ref = mdr->in[n];
+  CInode *ref = mdr->in[0];
   dout(10) << "ref is " << *ref << dendl;
 
-  // fw to inode auth?
-  if (mdr->snapid != CEPH_NOSNAP && !no_want_auth)
-    want_auth = true;
-
   if (want_auth) {
-    if (ref->is_ambiguous_auth()) {
-      dout(10) << "waiting for single auth on " << *ref << dendl;
-      ref->add_waiter(CInode::WAIT_SINGLEAUTH, new C_MDS_RetryRequest(mdcache, mdr));
-      return 0;
-    }
-    if (!ref->is_auth()) {
-      dout(10) << "fw to auth for " << *ref << dendl;
-      mdcache->request_forward(mdr, ref->authority().first);
-      return 0;
-    }
-
     // 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.
@@ -3333,34 +3663,14 @@ CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr, int n,
     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, new C_MDS_RetryRequest(mdcache, mdr));
-      /* If we have any auth pins, this will deadlock.
-       * But the only way to get here if we've already got auth pins
-       * is because we're on an inode with snapshots that got updated
-       * between dispatches of this request. So we're going to drop
-       * our locks and our auth pins and reacquire them later.
-       *
-       * This is safe since we're only in this function when working on
-       * a single MDS request; otherwise we'd be in
-       * rdlock_path_xlock_dentry.
-       */
-      mds->locker->drop_locks(mdr.get(), NULL);
-      mdr->drop_local_auth_pins();
-      if (!mdr->remote_auth_pins.empty())
+      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);
   }
 
-  for (int i=0; i<(int)mdr->dn[n].size(); i++) 
-    lov.add_rdlock(&mdr->dn[n][i]->lock);
-  if (layout)
-    mds->locker->include_snap_rdlocks_wlayout(ref, lov, layout);
-  else
-    mds->locker->include_snap_rdlocks(ref, lov);
-
   // set and pin ref
   mdr->pin(ref);
   return ref;
@@ -3369,97 +3679,270 @@ CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr, int n,
 
 /** rdlock_path_xlock_dentry
  * traverse path to the directory that could/would contain dentry.
- * make sure i am auth for that dentry, forward as necessary.
- * create null dentry in place (or use existing if okexist).
+ * 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, int n,
-                                         MutationImpl::LockOpVec& lov,
-                                         bool okexist, bool mustexist, bool alwaysxlock,
-                                         file_layout_t **layout)
+CDentry* Server::rdlock_path_xlock_dentry(MDRequestRef& mdr,
+                                         bool create, bool okexist, bool authexist,
+                                         bool want_layout)
 {
-  const filepath& refpath = n ? mdr->get_filepath2() : mdr->get_filepath();
-
+  const filepath& refpath = mdr->get_filepath();
   dout(10) << "rdlock_path_xlock_dentry " << *mdr << " " << refpath << dendl;
 
-  client_t client = mdr->get_client();
+  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 (mdr->done_locking)
-    return mdr->dn[n].back();
+  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;
+  }
 
-  CDir *dir = traverse_to_auth_dir(mdr, mdr->dn[n], refpath);
-  if (!dir) return 0;
+  // 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()) {
-      respond_to_request(mdr, -EROFS);
-      return 0;
+    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, -ENOENT);
-    return 0;
+    respond_to_request(mdr, -CEPHFS_ENOENT);
+    return nullptr;
   }
 
-  // make a null dentry?
-  std::string_view dname = refpath.last_dentry();
-  CDentry *dn;
-  if (mustexist) {
-    dn = dir->lookup(dname);
-
-    // make sure dir is complete
-    if (!dn && !dir->is_complete() &&
-        (!dir->has_bloom() || dir->is_in_bloom(dname))) {
-      dout(7) << " incomplete dir contents for " << *dir << ", fetching" << dendl;
-      dir->fetch(new C_MDS_RetryRequest(mdcache, mdr));
-      return 0;
+  CDentry::linkage_t *dnl = dn->get_projected_linkage();
+  if (dnl->is_null()) {
+    if (!create && okexist) {
+      respond_to_request(mdr, -CEPHFS_ENOENT);
+      return nullptr;
     }
 
-    // readable?
-    if (dn && !dn->lock.can_read(client) && dn->lock.get_xlock_by() != mdr) {
-      dout(10) << "waiting on xlocked dentry " << *dn << dendl;
-      dn->lock.add_waiter(SimpleLock::WAIT_RD, new C_MDS_RetryRequest(mdcache, mdr));
-      return 0;
-    }
-      
-    // exists?
-    if (!dn || dn->get_linkage(client, mdr)->is_null()) {
-      dout(7) << "dentry " << dname << " dne in " << *dir << dendl;
-      respond_to_request(mdr, -ENOENT);
-      return 0;
-    }    
+    snapid_t next_snap = mdcache->get_global_snaprealm()->get_newest_seq() + 1;
+    dn->first = std::max(dn->first, next_snap);
   } else {
-    dn = prepare_null_dentry(mdr, dir, dname, okexist);
-    if (!dn) 
-      return 0;
+    if (!okexist) {
+      respond_to_request(mdr, -CEPHFS_EEXIST);
+      return nullptr;
+    }
+    mdr->in[0] = dnl->get_inode();
   }
 
-  mdr->dn[n].push_back(dn);
-  CDentry::linkage_t *dnl = dn->get_linkage(client, mdr);
-  mdr->in[n] = dnl->get_inode();
-
-  // -- lock --
-  // NOTE: rename takes the same set of locks for srcdn
-  for (int i=0; i<(int)mdr->dn[n].size(); i++) 
-    lov.add_rdlock(&mdr->dn[n][i]->lock);
-  if (alwaysxlock || dnl->is_null())
-    lov.add_xlock(&dn->lock);                 // new dn, xlock
-  else
-    lov.add_rdlock(&dn->lock);  // existing dn, rdlock
-  lov.add_wrlock(&dn->get_dir()->inode->filelock); // also, wrlock on dir mtime
-  lov.add_wrlock(&dn->get_dir()->inode->nestlock); // also, wrlock on dir mtime
-  if (layout)
-    mds->locker->include_snap_rdlocks_wlayout(dn->get_dir()->inode, lov, layout);
-  else
-    mds->locker->include_snap_rdlocks(dn->get_dir()->inode, lov);
-
   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
@@ -3473,33 +3956,34 @@ CDir* Server::try_open_auth_dirfrag(CInode *diri, frag_t fg, MDRequestRef& mdr)
 {
   CDir *dir = diri->get_dirfrag(fg);
 
-  // not open and inode not mine?
-  if (!dir && !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 0;
-  }
+  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 (!dir && 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 0;
-  }
+    // 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?
-  if (!dir) 
+    // invent?
     dir = diri->get_or_open_dirfrag(mdcache, fg);
-  // 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 0;
   }
 
   return dir;
@@ -3511,12 +3995,12 @@ CDir* Server::try_open_auth_dirfrag(CInode *diri, frag_t fg, MDRequestRef& mdr)
 
 void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
@@ -3525,10 +4009,44 @@ void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
   if (mask & CEPH_STAT_RSTAT)
     want_auth = true; // set want_auth for CEPH_STAT_RSTAT mask
 
-  MutationImpl::LockOpVec lov;
-  CInode *ref = rdlock_path_pin_ref(mdr, 0, lov, want_auth, false, NULL,
-                                   !is_lookup);
-  if (!ref) return;
+  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);
+       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);
+       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
@@ -3545,6 +4063,8 @@ void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
              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))
@@ -3563,7 +4083,7 @@ void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
               ref->filelock.get_num_wrlocks() > 0 ||
               !ref->filelock.can_read(mdr->get_client())) {
       lov.add_rdlock(&ref->filelock);
-      mdr->done_locking = false;
+      mdr->locking_state &= ~MutationImpl::ALL_LOCKED;
     }
   }
 
@@ -3580,7 +4100,7 @@ void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
   // value for them.  (currently this matters for xattrs and inline data)
   mdr->getattr_caps = mask;
 
-  mds->balancer->hit_inode(ref, META_POP_IRD, req->get_source().num());
+  mds->balancer->hit_inode(ref, META_POP_IRD);
 
   // reply
   dout(10) << "reply to stat on " << *req << dendl;
@@ -3604,15 +4124,28 @@ struct C_MDS_LookupIno2 : public ServerContext {
 void Server::handle_client_lookup_ino(MDRequestRef& mdr,
                                      bool want_parent, bool want_dentry)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, -ESTALE);
+    respond_to_request(mdr, -CEPHFS_ESTALE);
     return;
   }
   if (!in) {
@@ -3620,11 +4153,6 @@ void Server::handle_client_lookup_ino(MDRequestRef& mdr,
     return;
   }
 
-  if (mdr && in->snaprealm && !in->snaprealm->have_past_parents_open() &&
-      !in->snaprealm->open_parents(new C_MDS_RetryRequest(mdcache, mdr))) {
-    return;
-  }
-
   // check for nothing (not read or write); this still applies the
   // path check.
   if (!check_access(mdr, in, 0))
@@ -3645,6 +4173,7 @@ void Server::handle_client_lookup_ino(MDRequestRef& mdr,
     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);
@@ -3667,11 +4196,11 @@ void Server::handle_client_lookup_ino(MDRequestRef& mdr,
 
   if (want_parent) {
     if (in->is_base()) {
-      respond_to_request(mdr, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
     if (!diri || diri->is_stray()) {
-      respond_to_request(mdr, -ESTALE);
+      respond_to_request(mdr, -CEPHFS_ESTALE);
       return;
     }
     dout(10) << "reply to lookup_parent " << *in << dendl;
@@ -3681,7 +4210,7 @@ void Server::handle_client_lookup_ino(MDRequestRef& mdr,
     if (want_dentry) {
       inodeno_t dirino = req->get_filepath2().get_ino();
       if (!diri || (dirino != inodeno_t() && diri->ino() != dirino)) {
-       respond_to_request(mdr, -ENOENT);
+       respond_to_request(mdr, -CEPHFS_ENOENT);
        return;
       }
       dout(10) << "reply to lookup_name " << *in << dendl;
@@ -3697,7 +4226,7 @@ void Server::handle_client_lookup_ino(MDRequestRef& mdr,
 
 void Server::_lookup_snap_ino(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
 
   vinodeno_t vino;
   vino.ino = req->get_filepath().get_ino();
@@ -3714,7 +4243,7 @@ void Server::_lookup_snap_ino(MDRequestRef& mdr)
       if (in->state_test(CInode::STATE_PURGING) ||
          !in->has_snap_data(vino.snapid)) {
        if (in->is_dir() || !parent_ino) {
-         respond_to_request(mdr, -ESTALE);
+         respond_to_request(mdr, -CEPHFS_ESTALE);
          return;
        }
        in = NULL;
@@ -3734,12 +4263,12 @@ void Server::_lookup_snap_ino(MDRequestRef& mdr)
   if (parent_ino) {
     diri = mdcache->get_inode(parent_ino);
     if (!diri) {
-      mdcache->open_ino(parent_ino, mds->mdsmap->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr));
+      mdcache->open_ino(parent_ino, mds->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr));
       return;
     }
 
     if (!diri->is_dir()) {
-      respond_to_request(mdr, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
 
@@ -3764,9 +4293,9 @@ void Server::_lookup_snap_ino(MDRequestRef& mdr)
       return;
     }
 
-    respond_to_request(mdr, -ESTALE);
+    respond_to_request(mdr, -CEPHFS_ESTALE);
   } else {
-    mdcache->open_ino(vino.ino, mds->mdsmap->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr), false);
+    mdcache->open_ino(vino.ino, mds->get_metadata_pool(), new C_MDS_LookupIno2(this, mdr), false);
   }
 }
 
@@ -3786,8 +4315,8 @@ void Server::_lookup_ino_2(MDRequestRef& mdr, int r)
   }
 
   // give up
-  if (r == -ENOENT || r == -ENODATA)
-    r = -ESTALE;
+  if (r == -CEPHFS_ENOENT || r == -CEPHFS_ENODATA)
+    r = -CEPHFS_ESTALE;
   respond_to_request(mdr, r);
 }
 
@@ -3795,13 +4324,13 @@ void Server::_lookup_ino_2(MDRequestRef& mdr, int r)
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_open(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
   
@@ -3810,28 +4339,27 @@ void Server::handle_client_open(MDRequestRef& mdr)
 
   if ((cmode & CEPH_FILE_MODE_WR) && mdcache->is_readonly()) {
     dout(7) << "read-only FS" << dendl;
-    respond_to_request(mdr, -EROFS);
+    respond_to_request(mdr, -CEPHFS_EROFS);
     return;
   }
   
-  MutationImpl::LockOpVec lov;
-  CInode *cur = rdlock_path_pin_ref(mdr, 0, lov, need_auth);
+  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->done_locking = false;
-    CInode *cur = rdlock_path_pin_ref(mdr, 0, lov, true);
+    mdr->locking_state &= ~(MutationImpl::PATH_LOCKED | MutationImpl::ALL_LOCKED);
+    CInode *cur = rdlock_path_pin_ref(mdr, true);
     if (!cur)
       return;
   }
 
-  if (!cur->inode.is_file()) {
+  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->inode.is_symlink() && !(flags & CEPH_O_NOFOLLOW))
+    if (cur->is_symlink() && !(flags & CEPH_O_NOFOLLOW))
       flags &= ~CEPH_O_TRUNC;
   }
 
@@ -3843,26 +4371,26 @@ void Server::handle_client_open(MDRequestRef& mdr)
   // regular file?
   /*if (!cur->inode.is_file() && !cur->inode.is_dir()) {
     dout(7) << "not a file or dir " << *cur << dendl;
-    respond_to_request(mdr, -ENXIO);                 // FIXME what error do we want?
+    respond_to_request(mdr, -CEPHFS_ENXIO);                 // FIXME what error do we want?
     return;
     }*/
-  if ((flags & CEPH_O_DIRECTORY) && !cur->inode.is_dir() && !cur->inode.is_symlink()) {
+  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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
-  if ((flags & CEPH_O_TRUNC) && !cur->inode.is_file()) {
+  if ((flags & CEPH_O_TRUNC) && !cur->is_file()) {
     dout(7) << "specified O_TRUNC on !(file|symlink) " << *cur << dendl;
-    // we should return -EISDIR for directory, return -EINVAL for other non-regular
-    respond_to_request(mdr, cur->inode.is_dir() ? -EISDIR : -EINVAL);
+    // 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->inode.inline_data.version != CEPH_INLINE_NONE &&
+  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, -EPERM);
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
   
@@ -3870,10 +4398,12 @@ void Server::handle_client_open(MDRequestRef& mdr)
   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, -EROFS);
+    respond_to_request(mdr, -CEPHFS_EROFS);
     return;
   }
 
+  MutationImpl::LockOpVec lov;
+
   unsigned mask = req->head.args.open.mask;
   if (mask) {
     Capability *cap = cur->get_client_cap(mdr->get_client());
@@ -3901,7 +4431,7 @@ void Server::handle_client_open(MDRequestRef& mdr)
       return;
 
     // wait for pending truncate?
-    const auto pi = cur->get_projected_inode();
+    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;
@@ -3937,7 +4467,7 @@ void Server::handle_client_open(MDRequestRef& mdr)
   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->session, 0, req->is_replay());
+      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()
@@ -3969,8 +4499,7 @@ void Server::handle_client_open(MDRequestRef& mdr)
   if (cmode & CEPH_FILE_MODE_WR)
     mds->balancer->hit_inode(cur, META_POP_IWR);
   else
-    mds->balancer->hit_inode(cur, META_POP_IRD,
-                            mdr->client_request->get_source().num());
+    mds->balancer->hit_inode(cur, META_POP_IRD);
 
   CDentry *dn = 0;
   if (req->get_dentry_wanted()) {
@@ -3992,11 +4521,13 @@ public:
   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->inode.version--;   // a bit hacky, see C_MDS_mknod_finish
-    newi->mark_dirty(newi->inode.version+1, mdr->ls);
+    newi->mark_dirty(mdr->ls);
     newi->mark_dirty_parent(mdr->ls, true);
 
     mdr->apply();
@@ -4017,56 +4548,55 @@ public:
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_openc(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, -EINVAL);
+    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;
 
-  if (!excl) {
-    CF_MDS_MDRContextFactory cf(mdcache, mdr);
-    int r = mdcache->path_traverse(mdr, cf, req->get_filepath(),
-                                  &mdr->dn[0], NULL, MDS_TRAVERSE_FORWARD);
-    if (r > 0) return;
-    if (r == 0) {
-      // it existed.
-      handle_client_open(mdr);
-      return;
-    }
-    if (r < 0 && r != -ENOENT) {
-      if (r == -ESTALE) {
-       dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
-       MDSContext *c = new C_MDS_TryFindInode(this, mdr);
-       mdcache->find_ino_peers(req->get_filepath().get_ino(), c);
-      } else {
-       dout(10) << "FAIL on error " << r << dendl;
-       respond_to_request(mdr, r);
-      }
-      return;
-    }
-  }
-
-  MutationImpl::LockOpVec lov;
-  file_layout_t *dir_layout = nullptr;
-  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, lov,
-                                         !excl, false, false, &dir_layout);
-  if (!dn) return;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  if (is_unlink_pending(dn)) {
+    wait_for_pending_unlink(dn, mdr);
     return;
   }
-  // set layout
-  file_layout_t layout;
-  if (dir_layout)
-    layout = *dir_layout;
-  else
+
+  CDentry::linkage_t *dnl = dn->get_projected_linkage();
+  if (!excl && !dnl->is_null()) {
+    // it existed.
+    ceph_assert(mdr.get()->is_rdlocked(&dn->lock));
+
+    MutationImpl::LockOpVec lov;
+    lov.add_rdlock(&dnl->get_inode()->snaplock);
+    if (!mds->locker->acquire_locks(mdr, lov))
+      return;
+
+    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
@@ -4101,70 +4631,61 @@ void Server::handle_client_openc(MDRequestRef& mdr)
 
   if (!layout.is_valid()) {
     dout(10) << " invalid initial file layout" << dendl;
-    respond_to_request(mdr, -EINVAL);
+    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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
   // created null dn.
   CDir *dir = dn->get_dir();
   CInode *diri = dir->get_inode();
-  lov.add_rdlock(&diri->authlock);
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
-
   if (!check_access(mdr, diri, access))
     return;
-
   if (!check_fragment_space(mdr, dir))
     return;
-
-  CDentry::linkage_t *dnl = dn->get_projected_linkage();
-
-  if (!dnl->is_null()) {
-    // it existed.  
-    ceph_assert(req->head.args.open.flags & CEPH_O_EXCL);
-    dout(10) << "O_EXCL, target exists, failing with -EEXIST" << dendl;
-    mdr->tracei = dnl->get_inode();
-    mdr->tracedn = dn;
-    respond_to_request(mdr, -EEXIST);
+  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 *in = prepare_new_inode(mdr, dn->get_dir(), inodeno_t(req->head.ino),
-                                req->head.args.open.mode | S_IFREG, &layout);
-  ceph_assert(in);
+  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(in);
+  dn->push_projected_linkage(newi);
 
-  in->inode.version = dn->pre_dirty();
+  auto _inode = newi->_get_inode();
+  _inode->version = dn->pre_dirty();
   if (layout.pool_id != mdcache->default_file_layout.pool_id)
-    in->inode.add_old_pool(mdcache->default_file_layout.pool_id);
-  in->inode.update_backtrace();
-  in->inode.rstat.rfiles = 1;
+    _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);
-  in->first = dn->first;
+  newi->first = dn->first;
 
   // do the open
-  Capability *cap = mds->locker->issue_new_caps(in, cmode, mdr->session, realm, req->is_replay());
-  in->authlock.set_state(LOCK_EXCL);
-  in->xattrlock.set_state(LOCK_EXCL);
+  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)) {
-    in->inode.client_ranges[client].range.first = 0;
-    in->inode.client_ranges[client].range.last = in->inode.get_layout_size_increment();
-    in->inode.client_ranges[client].follows = follows;
+    _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();
   }
   
@@ -4174,21 +4695,35 @@ void Server::handle_client_openc(MDRequestRef& mdr)
   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, in, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
-  le->metablob.add_primary_dentry(dn, in, true, true, true);
+  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(in->ino());
+  le->metablob.add_opened_ino(newi->ino());
+
+  C_MDS_openc_finish *fin = new C_MDS_openc_finish(this, mdr, dn, newi);
 
-  C_MDS_openc_finish *fin = new C_MDS_openc_finish(this, mdr, dn, in);
+  if (mdr->session->info.has_feature(CEPHFS_FEATURE_DELEG_INO)) {
+    openc_response_t   ocresp;
 
-  if (mdr->client_request->get_connection()->has_feature(CEPH_FEATURE_REPLY_CREATE_INODE)) {
+    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(in->inode.ino, mdr->reply_extra_bl);
+    encode(newi->ino(), mdr->reply_extra_bl);
   }
 
-  journal_and_reply(mdr, in, dn, le, fin);
+  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
@@ -4200,20 +4735,34 @@ void Server::handle_client_openc(MDRequestRef& mdr)
 
 void Server::handle_client_readdir(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, 0, lov, false, true);
+  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 -ENOTDIR" << dendl;
-    respond_to_request(mdr, -ENOTDIR);
+    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);
+
+      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);
 
@@ -4301,7 +4850,7 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
   DirStat ds;
   ds.frag = dir->get_frag();
   ds.auth = dir->get_dir_auth().first;
-  if (dir->is_auth())
+  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);
@@ -4310,7 +4859,7 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
   //  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 -= realm->get_snap_trace().length();
+  bytes_left -= get_snap_trace(session, realm).length();
 
   // build dir contents
   bufferlist dnbl;
@@ -4330,8 +4879,11 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
     bool dnp = dn->use_projected(client, mdr);
     CDentry::linkage_t *dnl = dnp ? dn->get_projected_linkage() : dn->get_linkage();
 
-    if (dnl->is_null())
+    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;
@@ -4391,7 +4943,7 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
     // dentry
     dout(12) << "including    dn " << *dn << dendl;
     encode(dn->get_name(), dnbl);
-    mds->locker->issue_client_lease(dn, client, dnbl, now, mdr->session);
+    mds->locker->issue_client_lease(dn, in, mdr, now, dnbl);
 
     // inode
     dout(12) << "including inode " << *in << dendl;
@@ -4411,6 +4963,8 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
     mdcache->lru.lru_touch(dn);
   }
   
+  session->touch_readdir_cap(numfiles);
+
   __u16 flags = 0;
   if (end) {
     flags = CEPH_READDIR_FRAG_END;
@@ -4436,7 +4990,7 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
   mdr->reply_extra_bl = dirbl;
 
   // bump popularity.  NOTE: this doesn't quite capture it.
-  mds->balancer->hit_dir(dir, META_POP_IRD, -1, numfiles);
+  mds->balancer->hit_dir(dir, META_POP_READDIR, numfiles);
   
   // reply
   mdr->tracei = diri;
@@ -4454,31 +5008,31 @@ void Server::handle_client_readdir(MDRequestRef& mdr)
  */
 class C_MDS_inode_update_finish : public ServerLogContext {
   CInode *in;
-  bool truncating_smaller, changed_ranges, new_realm;
+  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 nr=false) :
+                           bool sm=false, bool cr=false, bool ar=false) :
     ServerLogContext(s, r), in(i),
-    truncating_smaller(sm), changed_ranges(cr), new_realm(nr) { }
+    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
-    in->pop_and_dirty_projected_inode(mdr->ls);
     mdr->apply();
 
     MDSRank *mds = get_mds();
 
     // notify any clients
-    if (truncating_smaller && in->inode.is_truncating()) {
+    if (truncating_smaller && in->get_inode()->is_truncating()) {
       mds->locker->issue_truncate(in);
       mds->mdcache->truncate_inode(in, mdr->ls);
     }
 
-    if (new_realm) {
-      int op = CEPH_SNAP_OP_SPLIT;
-      mds->mdcache->send_snap_update(in, 0, op);
-      mds->mdcache->do_realm_invalidate_and_update_notify(in, op);
+    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);
@@ -4492,11 +5046,11 @@ public:
 
 void Server::handle_client_file_setlock(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, 0, lov, true);
+  CInode *cur = rdlock_path_pin_ref(mdr, true);
   if (!cur)
     return;
 
@@ -4543,7 +5097,7 @@ void Server::handle_client_file_setlock(MDRequestRef& mdr)
   default:
     dout(10) << "got unknown lock type " << set_lock.type
             << ", dropping request!" << dendl;
-    respond_to_request(mdr, -EOPNOTSUPP);
+    respond_to_request(mdr, -CEPHFS_EOPNOTSUPP);
     return;
   }
 
@@ -4569,14 +5123,14 @@ void Server::handle_client_file_setlock(MDRequestRef& mdr)
     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, -EINTR);
+      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, -EDEADLK);
+       respond_to_request(mdr, -CEPHFS_EDEADLK);
       } else if (!will_wait) {
-       respond_to_request(mdr, -EWOULDBLOCK);
+       respond_to_request(mdr, -CEPHFS_EWOULDBLOCK);
       } else {
        dout(10) << " added to waiting list" << dendl;
        ceph_assert(lock_state->is_waiting(set_lock));
@@ -4595,11 +5149,11 @@ void Server::handle_client_file_setlock(MDRequestRef& mdr)
 
 void Server::handle_client_file_readlock(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, 0, lov, true);
+  CInode *cur = rdlock_path_pin_ref(mdr, true);
   if (!cur)
     return;
 
@@ -4634,7 +5188,7 @@ void Server::handle_client_file_readlock(MDRequestRef& mdr)
 
   default:
     dout(10) << "got unknown lock type " << checking_lock.type << dendl;
-    respond_to_request(mdr, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
   lock_state->look_for_lock(checking_lock);
@@ -4648,27 +5202,41 @@ void Server::handle_client_file_readlock(MDRequestRef& mdr)
 
 void Server::handle_client_setattr(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   MutationImpl::LockOpVec lov;
-  CInode *cur = rdlock_path_pin_ref(mdr, 0, lov, true);
+  CInode *cur = rdlock_path_pin_ref(mdr, true);
   if (!cur) return;
 
   if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+    respond_to_request(mdr, -CEPHFS_EROFS);
     return;
   }
   if (cur->ino() < MDS_INO_SYSTEM_BASE && !cur->is_base()) {
-    respond_to_request(mdr, -EPERM);
+    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))
+  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))
+  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);
@@ -4676,30 +5244,38 @@ void Server::handle_client_setattr(MDRequestRef& mdr)
   if (!mds->locker->acquire_locks(mdr, lov))
     return;
 
-  if ((mask & CEPH_SETATTR_UID) && (cur->inode.uid != req->head.args.setattr.uid))
+  if ((mask & CEPH_SETATTR_UID) && (cur->get_inode()->uid != req->head.args.setattr.uid))
     access_mask |= MAY_CHOWN;
 
-  if ((mask & CEPH_SETATTR_GID) && (cur->inode.gid != req->head.args.setattr.gid))
+  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?
-  auto pip = cur->get_projected_inode();
+  const auto& pip = cur->get_projected_inode();
 
   uint64_t old_size = std::max<uint64_t>(pip->size, req->head.args.setattr.old_size);
 
-  // ENOSPC on growing file while full, but allow shrinks
+  // 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 ENOSPC to setattr with larger size" << dendl;
-    respond_to_request(mdr, -ENOSPC);
+    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) {
-    truncating_smaller = req->head.args.setattr.size < old_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;
@@ -4708,6 +5284,32 @@ void Server::handle_client_setattr(MDRequestRef& mdr)
       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;
@@ -4717,55 +5319,67 @@ void Server::handle_client_setattr(MDRequestRef& mdr)
   EUpdate *le = new EUpdate(mdlog, "setattr");
   mdlog->start_entry(le);
 
-  auto &pi = cur->project_inode();
+  auto pi = cur->project_inode(mdr);
 
   if (mask & CEPH_SETATTR_UID)
-    pi.inode.uid = req->head.args.setattr.uid;
+    pi.inode->uid = req->head.args.setattr.uid;
   if (mask & CEPH_SETATTR_GID)
-    pi.inode.gid = req->head.args.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)) &&
-           S_ISREG(pi.inode.mode) &&
-            (pi.inode.mode & (S_IXUSR|S_IXGRP|S_IXOTH))) {
-    pi.inode.mode &= ~(S_ISUID|S_ISGID);
+    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;
+    pi.inode->mtime = req->head.args.setattr.mtime;
   if (mask & CEPH_SETATTR_ATIME)
-    pi.inode.atime = req->head.args.setattr.atime;
+    pi.inode->atime = req->head.args.setattr.atime;
   if (mask & CEPH_SETATTR_BTIME)
-    pi.inode.btime = req->head.args.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.
+    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);
+      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->size = req->head.args.setattr.size;
+      pi.inode->rstat.rbytes = pi.inode->size;
     }
-    pi.inode.mtime = mdr->get_op_stamp();
+    pi.inode->mtime = mdr->get_op_stamp();
 
     // adjust client's max_size?
-    CInode::mempool_inode::client_range_map new_ranges;
-    bool max_increased = false;
-    mds->locker->calc_new_client_ranges(cur, pi.inode.size, true, &new_ranges, &max_increased);
-    if (pi.inode.client_ranges != new_ranges) {
-      dout(10) << " client_ranges " << pi.inode.client_ranges << " -> " << new_ranges << dendl;
-      pi.inode.client_ranges = new_ranges;
+    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;
     }
   }
 
-  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++;
+  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());
@@ -4791,32 +5405,33 @@ void Server::do_open_truncate(MDRequestRef& mdr, int cmode)
   dout(10) << "do_open_truncate " << *in << dendl;
 
   SnapRealm *realm = in->find_snaprealm();
-  Capability *cap = mds->locker->issue_new_caps(in, cmode, mdr->session, realm, mdr->client_request->is_replay());
+  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();
-  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);
+  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);
+    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();
+    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();
   }
   
@@ -4848,22 +5463,21 @@ void Server::do_open_truncate(MDRequestRef& mdr, int cmode)
 /* This function cleans up the passed mdr */
 void Server::handle_client_setlayout(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
-  MutationImpl::LockOpVec lov;
-  CInode *cur = rdlock_path_pin_ref(mdr, 0, lov, true);
+  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, -EROFS);
+    respond_to_request(mdr, -CEPHFS_EROFS);
     return;
   }
   if (!cur->is_file()) {
-    respond_to_request(mdr, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
   if (cur->get_projected_inode()->size ||
       cur->get_projected_inode()->truncate_seq > 1) {
-    respond_to_request(mdr, -ENOTEMPTY);
+    respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
     return;
   }
 
@@ -4897,15 +5511,16 @@ void Server::handle_client_setlayout(MDRequestRef& mdr)
 
   if (!layout.is_valid()) {
     dout(10) << "bad layout" << dendl;
-    respond_to_request(mdr, -EINVAL);
+    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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
+  MutationImpl::LockOpVec lov;
   lov.add_xlock(&cur->filelock);
   if (!mds->locker->acquire_locks(mdr, lov))
     return;
@@ -4914,15 +5529,15 @@ void Server::handle_client_setlayout(MDRequestRef& mdr)
     return;
 
   // project update
-  auto &pi = cur->project_inode();
-  pi.inode.layout = layout;
+  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++;
+  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();
@@ -4935,35 +5550,72 @@ void Server::handle_client_setlayout(MDRequestRef& mdr)
   journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
 }
 
-void Server::handle_client_setdirlayout(MDRequestRef& mdr)
+bool Server::xlock_policylock(MDRequestRef& mdr, CInode *in, bool want_layout, bool xlock_snaplock)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  if (mdr->locking_state & MutationImpl::ALL_LOCKED)
+    return true;
+
   MutationImpl::LockOpVec lov;
-  file_layout_t *dir_layout = nullptr;
-  CInode *cur = rdlock_path_pin_ref(mdr, 0, lov, true, false, &dir_layout);
-  if (!cur) return;
+  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 (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
-    return;
+  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, -ENOTDIR);
+    respond_to_request(mdr, -CEPHFS_ENOTDIR);
     return;
   }
 
-  lov.add_xlock(&cur->policylock);
-  if (!mds->locker->acquire_locks(mdr, lov))
+  if (!xlock_policylock(mdr, cur, true))
     return;
 
   // validate layout
-  const auto old_pi = cur->get_projected_inode();
+  const auto& old_pi = cur->get_projected_inode();
   file_layout_t layout;
   if (old_pi->has_layout())
     layout = old_pi->layout;
-  else if (dir_layout)
-    layout = *dir_layout;
+  else if (mdr->dir_layout != file_layout_t())
+    layout = mdr->dir_layout;
   else
     layout = mdcache->default_file_layout;
 
@@ -4993,21 +5645,21 @@ void Server::handle_client_setdirlayout(MDRequestRef& mdr)
 
   if (!layout.is_valid()) {
     dout(10) << "bad layout" << dendl;
-    respond_to_request(mdr, -EINVAL);
+    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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
   if (!check_access(mdr, cur, access))
     return;
 
-  auto &pi = cur->project_inode();
-  pi.inode.layout = layout;
-  pi.inode.version = cur->pre_dirty();
+  auto pi = cur->project_inode(mdr);
+  pi.inode->layout = layout;
+  pi.inode->version = cur->pre_dirty();
 
   // log + wait
   mdr->ls = mdlog->get_current_segment();
@@ -5022,11 +5674,85 @@ void Server::handle_client_setdirlayout(MDRequestRef& mdr)
 }
 
 // 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;
+    }
+  };
 
-int Server::parse_layout_vxattr(string name, string value, const OSDMap& osdmap,
-                               file_layout_t *layout, bool validate)
+  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)
 {
-  dout(20) << "parse_layout_vxattr name " << name << " value '" << value << "'" << dendl;
   try {
     if (name == "layout") {
       string::iterator begin = value.begin();
@@ -5034,17 +5760,17 @@ int Server::parse_layout_vxattr(string name, string value, const OSDMap& osdmap,
       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 -EINVAL;
+       return -CEPHFS_EINVAL;
       }
       string left(begin, end);
-      dout(10) << " parsed " << m << " left '" << left << "'" << dendl;
+      dout(10) << __func__ << ": parsed " << m << " left '" << left << "'" << dendl;
       if (begin != end)
-       return -EINVAL;
+       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("layout.") + q->first, q->second,
-                                    osdmap, layout, false);
+       int r = parse_layout_vxattr_string(string("layout.") + q->first, q->second,
+                                          osdmap, layout);
        if (r < 0)
          return r;
       }
@@ -5060,29 +5786,55 @@ int Server::parse_layout_vxattr(string name, string value, const OSDMap& osdmap,
       } catch (boost::bad_lexical_cast const&) {
        int64_t pool = osdmap.lookup_pg_pool_name(value);
        if (pool < 0) {
-         dout(10) << " unknown pool " << value << dendl;
-         return -ENOENT;
+         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) << " unknown layout vxattr " << name << dendl;
-      return -EINVAL;
+      dout(10) << __func__ << ": unknown layout vxattr " << name << dendl;
+      return -CEPHFS_ENODATA; // no such attribute
     }
   } catch (boost::bad_lexical_cast const&) {
-    dout(10) << "bad vxattr value, unable to parse int for " << name << dendl;
-    return -EINVAL;
+    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) << "bad layout" << dendl;
-    return -EINVAL;
+    dout(10) << __func__ << ": bad layout" << dendl;
+    return -CEPHFS_EINVAL;
   }
   if (!mds->mdsmap->is_data_pool(layout->pool_id)) {
-    dout(10) << " invalid data pool " << layout->pool_id << dendl;
-    return -EINVAL;
+    dout(10) << __func__ << ": invalid data pool " << layout->pool_id << dendl;
+    return -CEPHFS_EINVAL;
   }
   return 0;
 }
@@ -5101,12 +5853,12 @@ int Server::parse_quota_vxattr(string name, string value, quota_info_t *quota)
       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 -EINVAL;
+        return -CEPHFS_EINVAL;
       }
       string left(begin, end);
       dout(10) << " parsed " << m << " left '" << left << "'" << dendl;
       if (begin != end)
-        return -EINVAL;
+        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)
@@ -5115,25 +5867,25 @@ int Server::parse_quota_vxattr(string name, string value, quota_info_t *quota)
     } else if (name == "quota.max_bytes") {
       int64_t q = boost::lexical_cast<int64_t>(value);
       if (q < 0)
-        return -EINVAL;
+        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 -EINVAL;
+        return -CEPHFS_EINVAL;
       quota->max_files = q;
     } else {
       dout(10) << " unknown quota vxattr " << name << dendl;
-      return -EINVAL;
+      return -CEPHFS_EINVAL;
     }
   } catch (boost::bad_lexical_cast const&) {
     dout(10) << "bad vxattr value, unable to parse int for " << name << dendl;
-    return -EINVAL;
+    return -CEPHFS_EINVAL;
   }
 
   if (!quota->is_valid()) {
     dout(10) << "bad quota" << dendl;
-    return -EINVAL;
+    return -CEPHFS_EINVAL;
   }
   return 0;
 }
@@ -5142,7 +5894,7 @@ void Server::create_quota_realm(CInode *in)
 {
   dout(10) << __func__ << " " << *in << dendl;
 
-  auto req = MClientRequest::create(CEPH_MDS_OP_SETXATTR);
+  auto req = make_message<MClientRequest>(CEPH_MDS_OP_SETXATTR);
   req->set_filepath(filepath(in->ino()));
   req->set_string2("ceph.quota");
   // empty vxattr value
@@ -5162,7 +5914,7 @@ int Server::check_layout_vxattr(MDRequestRef& mdr,
                                 string value,
                                 file_layout_t *layout)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   epoch_t epoch;
   int r;
 
@@ -5171,7 +5923,7 @@ int Server::check_layout_vxattr(MDRequestRef& mdr,
       epoch = osdmap.get_epoch();
     });
 
-  if (r == -ENOENT) {
+  if (r == -CEPHFS_ENOENT) {
 
     // we don't have the specified pool, make sure our map
     // is newer than or as new as the client.
@@ -5180,37 +5932,26 @@ int Server::check_layout_vxattr(MDRequestRef& mdr,
     if (req_epoch > epoch) {
 
       // well, our map is older. consult mds.
-      Context *fin = new C_IO_Wrapper(mds, new C_MDS_RetryRequest(mdcache, mdr));
-
-      if (!mds->objecter->wait_for_map(req_epoch, fin))
-        return r; // wait, fin will retry this request later
-
-      delete fin;
-
-      // now we have at least as new a map as the client, try again.
-      mds->objecter->with_osdmap([&](const OSDMap& osdmap) {
-          r = parse_layout_vxattr(name, value, osdmap, layout);
-          epoch = osdmap.get_epoch();
-        });
-
-      ceph_assert(epoch >= req_epoch); // otherwise wait_for_map() told a lie
+      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(new C_IO_Wrapper(
-                             mds, new C_MDS_RetryRequest(mdcache, mdr)));
+      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 == -ENOENT)
-      r = -EINVAL;
+    if (r == -CEPHFS_ENOENT)
+      r = -CEPHFS_EINVAL;
 
     respond_to_request(mdr, r);
     return r;
@@ -5220,11 +5961,10 @@ int Server::check_layout_vxattr(MDRequestRef& mdr,
   return 0;
 }
 
-void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
-                              file_layout_t *dir_layout,
-                              MutationImpl::LockOpVec& lov)
+void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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());
@@ -5240,18 +5980,33 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
     return;
   }
 
-  bool new_realm = false;
+  bool adjust_realm = false;
   if (name.compare(0, 15, "ceph.dir.layout") == 0) {
     if (!cur->is_dir()) {
-      respond_to_request(mdr, -EINVAL);
+      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 (dir_layout)
-      layout = *dir_layout;
+    else if (mdr->dir_layout != file_layout_t())
+      layout = mdr->dir_layout;
     else
       layout = mdcache->default_file_layout;
 
@@ -5259,22 +6014,18 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
     if (check_layout_vxattr(mdr, rest, value, &layout) < 0)
       return;
 
-    lov.add_xlock(&cur->policylock);
-    if (!mds->locker->acquire_locks(mdr, lov))
-      return;
-
-    auto &pi = cur->project_inode();
-    pi.inode.layout = layout;
+    auto pi = cur->project_inode(mdr);
+    pi.inode->layout = layout;
     mdr->no_early_reply = true;
-    pip = &pi.inode;
+    pip = pi.inode.get();
   } else if (name.compare(0, 16, "ceph.file.layout") == 0) {
     if (!cur->is_file()) {
-      respond_to_request(mdr, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
     if (cur->get_projected_inode()->size ||
         cur->get_projected_inode()->truncate_seq > 1) {
-      respond_to_request(mdr, -ENOTEMPTY);
+      respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
       return;
     }
     file_layout_t layout = cur->get_projected_inode()->layout;
@@ -5286,14 +6037,20 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
     if (!mds->locker->acquire_locks(mdr, lov))
       return;
 
-    auto &pi = cur->project_inode();
-    int64_t old_pool = pi.inode.layout.pool_id;
-    pi.inode.add_old_pool(old_pool);
-    pi.inode.layout = layout;
-    pip = &pi.inode;
+    /* 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() || cur->is_root()) {
-      respond_to_request(mdr, -EINVAL);
+    if (!cur->is_dir()) {
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
 
@@ -5306,33 +6063,101 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
       return;
     }
 
-    lov.add_xlock(&cur->policylock);
-    if (quota.is_enable() && !cur->get_projected_srnode()) {
-      lov.add_xlock(&cur->snaplock);
-      new_realm = true;
-    }
+    if (quota.is_enabled() && !cur->get_projected_srnode())
+      adjust_realm = true;
 
-    if (!mds->locker->acquire_locks(mdr, lov))
+    if (!xlock_policylock(mdr, cur, false, adjust_realm))
       return;
 
-    auto &pi = cur->project_inode(false, new_realm);
-    pi.inode.quota = quota;
-
-    if (new_realm) {
-      SnapRealm *realm = cur->find_snaprealm();
-      auto seq = realm->get_newest_seq();
-      auto &newsnap = *pi.snapnode;
-      newsnap.created = seq;
-      newsnap.seq = seq;
+    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;
+    pip = pi.inode.get();
 
     client_t exclude_ct = mdr->get_client();
     mdcache->broadcast_quota_to_client(cur, exclude_ct, true);
-  } else if (name.find("ceph.dir.pin") == 0) {
+  } 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, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
 
@@ -5340,22 +6165,75 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
     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, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
 
-    lov.add_xlock(&cur->policylock);
-    if (!mds->locker->acquire_locks(mdr, lov))
+    if (!xlock_policylock(mdr, cur))
       return;
 
-    auto &pi = cur->project_inode();
+    auto pi = cur->project_inode(mdr);
     cur->set_export_pin(rank);
-    pip = &pi.inode;
+    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, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
@@ -5376,42 +6254,41 @@ void Server::handle_set_vxattr(MDRequestRef& mdr, CInode *cur,
   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, new_realm));
+                                                                  false, false, adjust_realm));
   return;
 }
 
-void Server::handle_remove_vxattr(MDRequestRef& mdr, CInode *cur,
-                                 file_layout_t *dir_layout,
-                                 MutationImpl::LockOpVec& lov)
+void Server::handle_remove_vxattr(MDRequestRef& mdr, CInode *cur)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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, -ENODATA);
+      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, -EINVAL);
+      respond_to_request(mdr, -CEPHFS_EINVAL);
       return;
     }
 
     if (!cur->get_projected_inode()->has_layout()) {
-      respond_to_request(mdr, -ENODATA);
+      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();
-    pi.inode.clear_layout();
-    pi.inode.version = cur->pre_dirty();
+    auto pi = cur->project_inode(mdr);
+    pi.inode->clear_layout();
+    pi.inode->version = cur->pre_dirty();
 
     // log + wait
     mdr->ls = mdlog->get_current_segment();
@@ -5430,61 +6307,224 @@ void Server::handle_remove_vxattr(MDRequestRef& mdr, CInode *cur,
     // 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, dir_layout, lov);
+    handle_set_vxattr(mdr, cur);
     return;
   }
 
-  respond_to_request(mdr, -ENODATA);
+  respond_to_request(mdr, -CEPHFS_ENODATA);
 }
 
-class C_MDS_inode_xattr_update_finish : public ServerLogContext {
-  CInode *in;
-public:
+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
+  },
+};
 
-  C_MDS_inode_xattr_update_finish(Server *s, MDRequestRef& r, CInode *i) :
-    ServerLogContext(s, r), in(i) { }
-  void finish(int r) override {
-    ceph_assert(r == 0);
+const Server::XattrHandler* Server::get_xattr_or_default_handler(std::string_view xattr_name) {
+  const XattrHandler *default_xattr_handler = nullptr;
 
-    // apply
-    in->pop_and_dirty_projected_inode(mdr->ls);
-    
-    mdr->apply();
+  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;
+    }
+  }
 
-    get_mds()->balancer->hit_inode(in, META_POP_IWR);
+  ceph_assert(default_xattr_handler != nullptr);
+  dout(20) << "handler=" << default_xattr_handler->description << dendl;
+  return default_xattr_handler;
+}
 
-    server->respond_to_request(mdr, 0);
+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 MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   string name(req->get_path2());
-  MutationImpl::LockOpVec lov;
-  CInode *cur;
 
-  file_layout_t *dir_layout = NULL;
-  if (name.compare(0, 15, "ceph.dir.layout") == 0)
-    cur = rdlock_path_pin_ref(mdr, 0, lov, true, false, &dir_layout);
-  else
-    cur = rdlock_path_pin_ref(mdr, 0, lov, true);
-  if (!cur)
+  // 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 (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  if (!is_allowed_ceph_xattr(name)) {
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
-  int flags = req->head.args.setxattr.flags;
+  CInode *cur = rdlock_path_pin_ref(mdr, true);
+  if (!cur)
+    return;
 
-  // magic ceph.* namespace?
-  if (name.compare(0, 5, "ceph.") == 0) {
-    handle_set_vxattr(mdr, cur, dir_layout, lov);
+  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;
@@ -5492,136 +6532,371 @@ void Server::handle_client_setxattr(MDRequestRef& mdr)
   if (!check_access(mdr, cur, MAY_WRITE))
     return;
 
-  auto pxattrs = cur->get_projected_xattrs();
   size_t len = req->get_data().length();
   size_t inc = len + name.length();
 
-  // 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;
+  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;
     }
-    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, -ENOSPC);
+  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;
   }
 
-  if ((flags & CEPH_XATTR_CREATE) && pxattrs->count(mempool::mds_co::string(name))) {
-    dout(10) << "setxattr '" << name << "' XATTR_CREATE and EEXIST on " << *cur << dendl;
-    respond_to_request(mdr, -EEXIST);
+  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 ((flags & CEPH_XATTR_REPLACE) && !pxattrs->count(mempool::mds_co::string(name))) {
-    dout(10) << "setxattr '" << name << "' XATTR_REPLACE and ENODATA on " << *cur << dendl;
-    respond_to_request(mdr, -ENODATA);
+
+  if (!is_allowed_ceph_xattr(name)) {
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
-  dout(10) << "setxattr '" << name << "' len " << len << " on " << *cur << dendl;
+  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
+
 
-  // project update
-  auto &pi = cur->project_inode(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++;
-  auto &px = *pi.xattrs;
-  if ((flags & CEPH_XATTR_REMOVE)) {
-    px.erase(mempool::mds_co::string(name));
-  } else {
-    bufferptr b = buffer::create(len);
-    if (len)
-      req->get_data().copy(0, len, b.c_str());
-    auto em = px.emplace(std::piecewise_construct, std::forward_as_tuple(mempool::mds_co::string(name)), std::forward_as_tuple(b));
-    if (!em.second)
-      em.first->second = b;
+// ------------------------------------------------
+
+struct C_WaitUnlinkToFinish : public MDSContext {
+protected:
+  MDCache *mdcache;
+  CDentry *dn;
+  MDSContext *fin;
+
+  MDSRank *get_mds() override
+  {
+    ceph_assert(mdcache != NULL);
+    return mdcache->mds;
   }
 
-  // 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);
+public:
+  C_WaitUnlinkToFinish(MDCache *m, CDentry *d, MDSContext *f) :
+    mdcache(m), dn(d), fin(f) {}
+  void finish(int r) override {
+    fin->complete(r);
+    dn->put(CDentry::PIN_PURGING);
+  }
+};
 
-  journal_and_reply(mdr, cur, 0, le, new C_MDS_inode_update_finish(this, mdr, cur));
+bool Server::is_unlink_pending(CDentry *dn)
+{
+  CDentry::linkage_t *dnl = dn->get_projected_linkage();
+  if (!dnl->is_null() && dn->state_test(CDentry::STATE_UNLINKING)) {
+      return true;
+  }
+  return false;
 }
 
-void Server::handle_client_removexattr(MDRequestRef& mdr)
+void Server::wait_for_pending_unlink(CDentry *dn, MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
-  std::string name(req->get_path2());
+  dout(20) << __func__ << " dn " << *dn << dendl;
+  mds->locker->drop_locks(mdr.get());
+  auto fin = new C_MDS_RetryRequest(mdcache, mdr);
+  dn->get(CDentry::PIN_PURGING);
+  dn->add_waiter(CDentry::WAIT_UNLINK_FINISH, new C_WaitUnlinkToFinish(mdcache, dn, fin));
+}
 
-  MutationImpl::LockOpVec lov;
-  file_layout_t *dir_layout = nullptr;
-  CInode *cur;
-  if (name == "ceph.dir.layout")
-    cur = rdlock_path_pin_ref(mdr, 0, lov, true, false, &dir_layout);
-  else
-    cur = rdlock_path_pin_ref(mdr, 0, lov, true);
-  if (!cur)
-    return;
+struct C_WaitReintegrateToFinish : public MDSContext {
+protected:
+  MDCache *mdcache;
+  CDentry *dn;
+  MDSContext *fin;
 
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
-    return;
+  MDSRank *get_mds() override
+  {
+    ceph_assert(mdcache != NULL);
+    return mdcache->mds;
   }
 
-  if (name.compare(0, 5, "ceph.") == 0) {
-    handle_remove_vxattr(mdr, cur, dir_layout, lov);
-    return;
+public:
+  C_WaitReintegrateToFinish(MDCache *m, CDentry *d, MDSContext *f) :
+    mdcache(m), dn(d), fin(f) {}
+  void finish(int r) override {
+    fin->complete(r);
+    dn->put(CDentry::PIN_PURGING);
   }
+};
 
-  lov.add_xlock(&cur->xattrlock);
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
-
-  auto pxattrs = cur->get_projected_xattrs();
-  if (pxattrs->count(mempool::mds_co::string(name)) == 0) {
-    dout(10) << "removexattr '" << name << "' and ENODATA on " << *cur << dendl;
-    respond_to_request(mdr, -ENODATA);
-    return;
+bool Server::is_reintegrate_pending(CDentry *dn)
+{
+  CDentry::linkage_t *dnl = dn->get_projected_linkage();
+  if (!dnl->is_null() && dn->state_test(CDentry::STATE_REINTEGRATING)) {
+      return true;
   }
-
-  dout(10) << "removexattr '" << name << "' on " << *cur << dendl;
-
-  // project update
-  auto &pi = cur->project_inode(true);
-  auto &px = *pi.xattrs;
-  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++;
-  px.erase(mempool::mds_co::string(name));
-
-  // 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));
+  return false;
 }
 
-
-// =================================================================
-// DIRECTORY and NAMESPACE OPS
-
-
-// ------------------------------------------------
+void Server::wait_for_pending_reintegrate(CDentry *dn, MDRequestRef& mdr)
+{
+  dout(20) << __func__ << " dn " << *dn << dendl;
+  mds->locker->drop_locks(mdr.get());
+  auto fin = new C_MDS_RetryRequest(mdcache, mdr);
+  dn->get(CDentry::PIN_PURGING);
+  dn->add_waiter(CDentry::WAIT_REINTEGRATE_FINISH, new C_WaitReintegrateToFinish(mdcache, dn, fin));
+}
 
 // MKNOD
 
@@ -5634,22 +6909,23 @@ public:
   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->inode.version--; 
-    newi->mark_dirty(newi->inode.version + 1, mdr->ls);
+    newi->mark_dirty(mdr->ls);
     newi->mark_dirty_parent(mdr->ls, true);
 
     // mkdir?
-    if (newi->inode.is_dir()) { 
+    if (newi->is_dir()) {
       CDir *dir = newi->get_dirfrag(frag_t());
       ceph_assert(dir);
-      dir->fnode.version--;
-      dir->mark_dirty(dir->fnode.version + 1, mdr->ls);
+      dir->mark_dirty(mdr->ls);
       dir->mark_new(mdr->ls);
     }
 
@@ -5658,8 +6934,12 @@ public:
     MDRequestRef null_ref;
     get_mds()->mdcache->send_dentry_link(dn, null_ref);
 
-    if (newi->inode.is_file())
+    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);
@@ -5672,36 +6952,44 @@ public:
 
 void Server::handle_client_mknod(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   client_t client = mdr->get_client();
-  MutationImpl::LockOpVec lov;
-  file_layout_t *dir_layout = nullptr;
-  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, lov, false, false, false,
-                                        &dir_layout);
-  if (!dn) return;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+
+  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;
-  }
-  CInode *diri = dn->get_dir()->get_inode();
-  lov.add_rdlock(&diri->authlock);
-  if (!mds->locker->acquire_locks(mdr, lov))
+
+  if (is_unlink_pending(dn)) {
+    wait_for_pending_unlink(dn, mdr);
     return;
+  }
 
+  CDir *dir = dn->get_dir();
+  CInode *diri = dir->get_inode();
   if (!check_access(mdr, diri, MAY_WRITE))
     return;
-
-  if (!check_fragment_space(mdr, dn->get_dir()))
+  if (!check_fragment_space(mdr, dir))
+    return;
+  if (!check_dir_max_entries(mdr, dir))
     return;
 
-  unsigned mode = req->head.args.mknod.mode;
-  if ((mode & S_IFMT) == 0)
-    mode |= S_IFREG;
+  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 (dir_layout && S_ISREG(mode))
-    layout = *dir_layout;
+  if (mdr->dir_layout != file_layout_t())
+    layout = mdr->dir_layout;
   else
     layout = mdcache->default_file_layout;
 
@@ -5710,12 +6998,14 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
 
   dn->push_projected_linkage(newi);
 
-  newi->inode.rdev = req->head.args.mknod.rdev;
-  newi->inode.version = dn->pre_dirty();
-  newi->inode.rstat.rfiles = 1;
+  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)
-    newi->inode.add_old_pool(mdcache->default_file_layout.pool_id);
-  newi->inode.update_backtrace();
+    _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();
@@ -5723,10 +7013,10 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
 
   // 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(newi->inode.mode)) {
+  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->session, realm, req->is_replay());
+    Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr, realm);
     if (cap) {
       cap->set_wanted(0);
 
@@ -5736,9 +7026,10 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
       newi->xattrlock.set_state(LOCK_EXCL);
 
       dout(15) << " setting a client_range too, since this is a regular file" << dendl;
-      newi->inode.client_ranges[client].range.first = 0;
-      newi->inode.client_ranges[client].range.last = newi->inode.get_layout_size_increment();
-      newi->inode.client_ranges[client].follows = follows;
+      _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();
     }
   }
@@ -5746,7 +7037,7 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
   ceph_assert(dn->first == follows + 1);
   newi->first = dn->first;
     
-  dout(10) << "mknod mode " << newi->inode.mode << " rdev " << newi->inode.rdev << dendl;
+  dout(10) << "mknod mode " << _inode->mode << " rdev " << _inode->rdev << dendl;
 
   // prepare finisher
   mdr->ls = mdlog->get_current_segment();
@@ -5760,6 +7051,7 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
   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);
 }
 
 
@@ -5768,24 +7060,20 @@ void Server::handle_client_mknod(MDRequestRef& mdr)
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_mkdir(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
-  if (req->get_filepath().is_last_dot_or_dotdot()) {
-    respond_to_request(mdr, -EEXIST);
+  const cref_t<MClientRequest> &req = mdr->client_request;
+
+  mdr->disable_lock_cache();
+  CDentry *dn = rdlock_path_xlock_dentry(mdr, true);
+  if (!dn)
     return;
-  }
 
-  MutationImpl::LockOpVec lov;
-  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, lov, false, false, false);
-  if (!dn) return;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  if (is_unlink_pending(dn)) {
+    wait_for_pending_unlink(dn, mdr);
     return;
   }
+
   CDir *dir = dn->get_dir();
   CInode *diri = dir->get_inode();
-  lov.add_rdlock(&diri->authlock);
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
 
   // mkdir check access
   if (!check_access(mdr, diri, MAY_WRITE))
@@ -5793,20 +7081,32 @@ void Server::handle_client_mkdir(MDRequestRef& mdr)
 
   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, dn->get_dir(), inodeno_t(req->head.ino), mode);  
+  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);
 
-  newi->inode.version = dn->pre_dirty();
-  newi->inode.rstat.rsubdirs = 1;
-  newi->inode.update_backtrace();
+  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();
@@ -5820,7 +7120,7 @@ void Server::handle_client_mkdir(MDRequestRef& mdr)
   CDir *newdir = newi->get_or_open_dirfrag(mdcache, frag_t());
   newdir->state_set(CDir::STATE_CREATING);
   newdir->mark_complete();
-  newdir->fnode.version = newdir->pre_dirty();
+  newdir->_get_fnode()->version = newdir->pre_dirty();
 
   // prepare finisher
   mdr->ls = mdlog->get_current_segment();
@@ -5834,7 +7134,7 @@ void Server::handle_client_mkdir(MDRequestRef& mdr)
   
   // issue a cap on the directory
   int cmode = CEPH_FILE_MODE_RDWR;
-  Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr->session, realm, req->is_replay());
+  Capability *cap = mds->locker->issue_new_caps(newi, cmode, mdr, realm);
   if (cap) {
     cap->set_wanted(0);
 
@@ -5860,39 +7160,50 @@ void Server::handle_client_mkdir(MDRequestRef& mdr)
 
 void Server::handle_client_symlink(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
-  MutationImpl::LockOpVec lov;
-  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, lov, false, false, false);
-  if (!dn) return;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  const auto& req = mdr->client_request;
+
+  mdr->disable_lock_cache();
+  CDentry *dn = rdlock_path_xlock_dentry(mdr, true);
+  if (!dn)
+    return;
+
+  if (is_unlink_pending(dn)) {
+    wait_for_pending_unlink(dn, mdr);
     return;
   }
+
   CDir *dir = dn->get_dir();
   CInode *diri = dir->get_inode();
-  lov.add_rdlock(&diri->authlock);
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
 
   if (!check_access(mdr, diri, MAY_WRITE))
-   return;
-
+    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, dn->get_dir(), inodeno_t(req->head.ino), mode);
+  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();
-  newi->inode.size = newi->symlink.length();
-  newi->inode.rstat.rbytes = newi->inode.size;
-  newi->inode.rstat.rfiles = 1;
-  newi->inode.version = dn->pre_dirty();
-  newi->inode.update_backtrace();
+  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;
 
@@ -5906,6 +7217,12 @@ void Server::handle_client_symlink(MDRequestRef& mdr)
   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();
+  }
 }
 
 
@@ -5916,44 +7233,95 @@ void Server::handle_client_symlink(MDRequestRef& mdr)
 
 void Server::handle_client_link(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
 
   dout(7) << "handle_client_link " << req->get_filepath()
          << " to " << req->get_filepath2()
          << dendl;
 
-  MutationImpl::LockOpVec lov;
+  mdr->disable_lock_cache();
 
-  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, lov, false, false, false);
-  if (!dn) return;
-  CInode *targeti = rdlock_path_pin_ref(mdr, 1, lov, false);
-  if (!targeti) return;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  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();
+  }
+
+  if (is_unlink_pending(destdn)) {
+    wait_for_pending_unlink(destdn, mdr);
     return;
   }
 
-  CDir *dir = dn->get_dir();
-  dout(7) << "handle_client_link link " << dn->get_name() << " in " << *dir << dendl;
-  dout(7) << "target is " << *targeti << dendl;
+  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()) {
-    // if srcdn is replica, need to make sure its linkage is correct
-    vector<CDentry*>& trace = mdr->dn[1];
-    if (trace.empty() ||
-       trace.back()->is_auth() ||
-       trace.back()->lock.can_read(mdr->get_client())) {
-      dout(7) << "target is a dir, failing..." << dendl;
-      respond_to_request(mdr, -EINVAL);
-      return;
-    }
+    dout(7) << "target is a dir, failing..." << dendl;
+    respond_to_request(mdr, -CEPHFS_EINVAL);
+    return;
   }
 
-  lov.erase_rdlock(&targeti->snaplock);
-  lov.add_xlock(&targeti->snaplock);
-  lov.add_xlock(&targeti->linklock);
+  CDir *dir = destdn->get_dir();
+  dout(7) << "handle_client_link link " << destdn->get_name() << " in " << *dir << dendl;
+  dout(7) << "target is " << *targeti << dendl;
 
-  if (!mds->locker->acquire_locks(mdr, lov))
+  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))
@@ -5964,6 +7332,26 @@ void Server::handle_client_link(MDRequestRef& mdr)
 
     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()) {
+    if (target_pin->is_stray()) {
+      mds->locker->drop_locks(mdr.get());
+      targeti->add_waiter(CInode::WAIT_UNLINK,
+                          new C_MDS_RetryRequest(mdcache, mdr));
+      mdlog->flush();
+      return;
+    }
+    dout(7) << "target is in different subvolume, failing..." << dendl;
+    respond_to_request(mdr, -CEPHFS_EXDEV);
+    return;
   }
 
   // go!
@@ -5971,9 +7359,10 @@ void Server::handle_client_link(MDRequestRef& mdr)
 
   // local or remote?
   if (targeti->is_auth()) 
-    _link_local(mdr, dn, targeti);
+    _link_local(mdr, destdn, targeti, target_realm);
   else 
-    _link_remote(mdr, true, dn, targeti);
+    _link_remote(mdr, true, destdn, targeti);
+  mds->balancer->maybe_fragment(dir, false);  
 }
 
 
@@ -5995,7 +7384,7 @@ public:
 };
 
 
-void Server::_link_local(MDRequestRef& mdr, CDentry *dn, CInode *targeti)
+void Server::_link_local(MDRequestRef& mdr, CDentry *dn, CInode *targeti, SnapRealm *target_realm)
 {
   dout(10) << "_link_local " << *dn << " to " << *targeti << dendl;
 
@@ -6006,19 +7395,19 @@ void Server::_link_local(MDRequestRef& mdr, CDentry *dn, CInode *targeti)
   version_t tipv = targeti->pre_dirty();
   
   // project inode update
-  auto &pi = targeti->project_inode();
-  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;
+  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 (!targeti->is_projected_snaprealm_global()) {
+  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, NULL, targeti->get_projected_parent_dn(), true);
+    targeti->record_snaprealm_parent_dentry(newsnap, target_realm, targeti->get_projected_parent_dn(), true);
     adjust_realm = true;
   }
 
@@ -6050,8 +7439,6 @@ void Server::_link_local_finish(MDRequestRef& mdr, CDentry *dn, CInode *targeti,
   dn->mark_dirty(dnpv, mdr->ls);
 
   // target inode
-  targeti->pop_and_dirty_projected_inode(mdr->ls);
-
   mdr->apply();
 
   MDRequestRef null_ref;
@@ -6101,7 +7488,7 @@ void Server::_link_remote(MDRequestRef& mdr, bool inc, CDentry *dn, CInode *targ
     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_slave.empty())
+      if (mdr->more()->waiting_on_peer.empty())
        mds->wait_for_active_peer(linkauth, new C_MDS_RetryRequest(mdcache, mdr));
       return;
     }
@@ -6109,18 +7496,18 @@ void Server::_link_remote(MDRequestRef& mdr, bool inc, CDentry *dn, CInode *targ
     dout(10) << " targeti auth must prepare nlink++/--" << dendl;
     int op;
     if (inc)
-      op = MMDSSlaveRequest::OP_LINKPREP;
+      op = MMDSPeerRequest::OP_LINKPREP;
     else 
-      op = MMDSSlaveRequest::OP_UNLINKPREP;
-    auto req = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, op);
+      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_slave.count(linkauth) == 0);
-    mdr->more()->waiting_on_slave.insert(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;
@@ -6140,10 +7527,10 @@ void Server::_link_remote(MDRequestRef& mdr, bool inc, CDentry *dn, CInode *targ
   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_slaves " << mdr->more()->witnessed << dendl;
+    dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
     le->reqid = mdr->reqid;
-    le->had_slaves = true;
-    mdcache->add_uncommitted_master(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+    le->had_peers = true;
+    mdcache->add_uncommitted_leader(mdr->reqid, mdr->ls, mdr->more()->witnessed);
   }
 
   if (inc) {
@@ -6159,7 +7546,8 @@ void Server::_link_remote(MDRequestRef& mdr, bool inc, CDentry *dn, CInode *targ
     dn->push_projected_linkage();
   }
 
-  journal_and_reply(mdr, targeti, dn, le, new C_MDS_link_remote_finish(this, mdr, inc, dn, targeti));
+  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,
@@ -6173,7 +7561,7 @@ void Server::_link_remote_finish(MDRequestRef& mdr, bool inc,
   ceph_assert(g_conf()->mds_kill_link_at != 3);
 
   if (!mdr->more()->witnessed.empty())
-    mdcache->logged_master_update(mdr->reqid);
+    mdcache->logged_leader_update(mdr->reqid);
 
   if (inc) {
     // link the new dentry
@@ -6191,11 +7579,17 @@ void Server::_link_remote_finish(MDRequestRef& mdr, bool inc,
   mdr->apply();
 
   MDRequestRef null_ref;
-  if (inc)
+  if (inc) {
     mdcache->send_dentry_link(dn, null_ref);
-  else
+  } else {
+    dn->state_clear(CDentry::STATE_UNLINKING);
     mdcache->send_dentry_unlink(dn, NULL, null_ref);
-  
+
+    MDSContext::vec finished;
+    dn->take_waiting(CDentry::WAIT_UNLINK_FINISH, finished);
+    mdcache->mds->queue_waiters(finished);
+  }
+
   // bump target popularity
   mds->balancer->hit_inode(targeti, META_POP_IWR);
   mds->balancer->hit_dir(dn->get_dir(), META_POP_IWR);
@@ -6211,97 +7605,100 @@ void Server::_link_remote_finish(MDRequestRef& mdr, bool inc,
 
 // remote linking/unlinking
 
-class C_MDS_SlaveLinkPrep : public ServerLogContext {
+class C_MDS_PeerLinkPrep : public ServerLogContext {
   CInode *targeti;
   bool adjust_realm;
 public:
-  C_MDS_SlaveLinkPrep(Server *s, MDRequestRef& r, CInode *t, bool ar) :
+  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_slave_link(mdr, targeti, adjust_realm);
+    server->_logged_peer_link(mdr, targeti, adjust_realm);
   }
 };
 
-class C_MDS_SlaveLinkCommit : public ServerContext {
+class C_MDS_PeerLinkCommit : public ServerContext {
   MDRequestRef mdr;
   CInode *targeti;
 public:
-  C_MDS_SlaveLinkCommit(Server *s, MDRequestRef& r, CInode *t) :
+  C_MDS_PeerLinkCommit(Server *s, MDRequestRef& r, CInode *t) :
     ServerContext(s), mdr(r), targeti(t) { }
   void finish(int r) override {
-    server->_commit_slave_link(mdr, r, targeti);
+    server->_commit_peer_link(mdr, r, targeti);
   }
 };
 
-void Server::handle_slave_link_prep(MDRequestRef& mdr)
+void Server::handle_peer_link_prep(MDRequestRef& mdr)
 {
-  dout(10) << "handle_slave_link_prep " << *mdr 
-          << " on " << mdr->slave_request->get_object_info() 
+  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->slave_request->get_object_info().ino);
+  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->slave_request->op_stamp);
+  mdr->set_op_stamp(mdr->peer_request->op_stamp);
 
   mdr->auth_pin(targeti);
 
-  //ceph_abort();  // test hack: make sure master can handle a slave that fails to prepare...
+  //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();
-  ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_link_prep", mdr->reqid, mdr->slave_to_mds,
-                                     ESlaveUpdate::OP_PREPARE, ESlaveUpdate::LINK);
+  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();
+  auto pi = dnl->get_inode()->project_inode(mdr);
 
   // update journaled target inode
   bool inc;
   bool adjust_realm = false;
   bool realm_projected = false;
-  if (mdr->slave_request->get_op() == MMDSSlaveRequest::OP_LINKPREP) {
+  if (mdr->peer_request->get_op() == MMDSPeerRequest::OP_LINKPREP) {
     inc = true;
-    pi.inode.nlink++;
-    if (!targeti->is_projected_snaprealm_global()) {
+    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, NULL, targeti->get_projected_parent_dn(), true);
+      targeti->record_snaprealm_parent_dentry(newsnap, target_realm, target_pdn, true);
       adjust_realm = true;
       realm_projected = true;
     }
   } else {
     inc = false;
-    pi.inode.nlink--;
+    pi.inode->nlink--;
     if (targeti->is_projected_snaprealm_global()) {
-      ceph_assert(mdr->slave_request->desti_snapbl.length());
-      auto p = mdr->slave_request->desti_snapbl.cbegin();
+      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)
+      if (pi.inode->nlink == 0)
        ceph_assert(!newsnap->is_parent_global());
 
       realm_projected = true;
     } else {
-      ceph_assert(mdr->slave_request->desti_snapbl.length() == 0);
+      ceph_assert(mdr->peer_request->desti_snapbl.length() == 0);
     }
   }
 
   link_rollback rollback;
   rollback.reqid = mdr->reqid;
   rollback.ino = targeti->ino();
-  rollback.old_ctime = targeti->inode.ctime;   // we hold versionlock xlock; no concorrent projections
-  const fnode_t *pf = targeti->get_parent_dn()->get_dir()->get_projected_fnode();
+  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;
@@ -6316,40 +7713,40 @@ void Server::handle_slave_link_prep(MDRequestRef& mdr)
   encode(rollback, le->rollback);
   mdr->more()->rollback_bl = le->rollback;
 
-  pi.inode.ctime = mdr->get_op_stamp();
-  pi.inode.version = targeti->pre_dirty();
+  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;
+  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()->slave_commit = new C_MDS_SlaveLinkCommit(this, mdr, targeti);
+  mdr->more()->peer_commit = new C_MDS_PeerLinkCommit(this, mdr, targeti);
 
-  mdr->more()->slave_update_journaled = true;
-  submit_mdlog_entry(le, new C_MDS_SlaveLinkPrep(this, mdr, targeti, adjust_realm),
+  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_slave_link(MDRequestRef& mdr, CInode *targeti, bool adjust_realm)
+void Server::_logged_peer_link(MDRequestRef& mdr, CInode *targeti, bool adjust_realm)
 {
-  dout(10) << "_logged_slave_link " << *mdr
+  dout(10) << "_logged_peer_link " << *mdr
           << " " << *targeti << dendl;
 
   ceph_assert(g_conf()->mds_kill_link_at != 6);
 
   // update the target
-  targeti->pop_and_dirty_projected_inode(mdr->ls);
   mdr->apply();
 
   // hit pop
   mds->balancer->hit_inode(targeti, META_POP_IWR);
 
   // done.
-  mdr->reset_slave_request();
+  mdr->reset_peer_request();
 
   if (adjust_realm) {
     int op = CEPH_SNAP_OP_SPLIT;
@@ -6359,8 +7756,8 @@ void Server::_logged_slave_link(MDRequestRef& mdr, CInode *targeti, bool adjust_
 
   // ack
   if (!mdr->aborted) {
-    auto reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_LINKPREPACK);
-    mds->send_message_mds(reply, mdr->slave_to_mds);
+    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);
@@ -6368,16 +7765,16 @@ void Server::_logged_slave_link(MDRequestRef& mdr, CInode *targeti, bool adjust_
 }
 
 
-struct C_MDS_CommittedSlave : public ServerLogContext {
-  C_MDS_CommittedSlave(Server *s, MDRequestRef& m) : ServerLogContext(s, m) {}
+struct C_MDS_CommittedPeer : public ServerLogContext {
+  C_MDS_CommittedPeer(Server *s, MDRequestRef& m) : ServerLogContext(s, m) {}
   void finish(int r) override {
-    server->_committed_slave(mdr);
+    server->_committed_peer(mdr);
   }
 };
 
-void Server::_commit_slave_link(MDRequestRef& mdr, int r, CInode *targeti)
+void Server::_commit_peer_link(MDRequestRef& mdr, int r, CInode *targeti)
 {  
-  dout(10) << "_commit_slave_link " << *mdr
+  dout(10) << "_commit_peer_link " << *mdr
           << " r=" << r
           << " " << *targeti << dendl;
 
@@ -6388,32 +7785,34 @@ void Server::_commit_slave_link(MDRequestRef& mdr, int r, CInode *targeti)
     mdr->cleanup();
 
     // write a commit to the journal
-    ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_link_commit", mdr->reqid, mdr->slave_to_mds,
-                                       ESlaveUpdate::OP_COMMIT, ESlaveUpdate::LINK);
+    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_CommittedSlave(this, mdr), mdr, __func__);
+    submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
     mdlog->flush();
   } else {
-    do_link_rollback(mdr->more()->rollback_bl, mdr->slave_to_mds, mdr);
+    do_link_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr);
   }
 }
 
-void Server::_committed_slave(MDRequestRef& mdr)
+void Server::_committed_peer(MDRequestRef& mdr)
 {
-  dout(10) << "_committed_slave " << *mdr << dendl;
+  dout(10) << "_committed_peer " << *mdr << dendl;
 
   ceph_assert(g_conf()->mds_kill_link_at != 8);
 
-  auto req = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_COMMITTED);
-  mds->send_message_mds(req, mdr->slave_to_mds);
+  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,MClientSnap::ref> splits;
+  map<client_t,ref_t<MClientSnap>> splits;
   C_MDS_LoggedLinkRollback(Server *s, MutationRef& m, MDRequestRef& r,
-                          map<client_t,MClientSnap::ref>&& _splits) :
+                          map<client_t,ref_t<MClientSnap>>&& _splits) :
     ServerLogContext(s, r), mut(m), splits(std::move(_splits)) {
   }
   void finish(int r) override {
@@ -6421,7 +7820,7 @@ struct C_MDS_LoggedLinkRollback : public ServerLogContext {
   }
 };
 
-void Server::do_link_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef& mdr)
+void Server::do_link_rollback(bufferlist &rbl, mds_rank_t leader, MDRequestRef& mdr)
 {
   link_rollback rollback;
   auto p = rbl.cbegin();
@@ -6434,7 +7833,7 @@ void Server::do_link_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
 
   ceph_assert(g_conf()->mds_kill_link_at != 9);
 
-  mdcache->add_rollback(rollback.reqid, master); // need to finish this update before resolve finishes
+  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));
@@ -6443,33 +7842,31 @@ void Server::do_link_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
   CInode *in = mdcache->get_inode(rollback.ino);
   ceph_assert(in);
   dout(10) << " target is " << *in << dendl;
-  ceph_assert(!in->is_projected());  // live slave request hold versionlock xlock.
+  ceph_assert(!in->is_projected());  // live peer request hold versionlock xlock.
   
-  auto &pi = in->project_inode();
-  pi.inode.version = in->pre_dirty();
-  mut->add_projected_inode(in);
+  auto pi = in->project_inode(mut);
+  pi.inode->version = in->pre_dirty();
 
   // parent dir rctime
   CDir *parent = in->get_projected_parent_dn()->get_dir();
-  fnode_t *pf = parent->project_fnode();
-  mut->add_projected_fnode(parent);
+  auto pf = parent->project_fnode(mut);
   pf->version = parent->pre_dirty();
-  if (pf->fragstat.mtime == pi.inode.ctime) {
+  if (pf->fragstat.mtime == pi.inode->ctime) {
     pf->fragstat.mtime = rollback.old_dir_mtime;
-    if (pf->rstat.rctime == pi.inode.ctime)
+    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;
+  pi.inode->ctime = rollback.old_ctime;
   if (rollback.was_inc)
-    pi.inode.nlink--;
+    pi.inode->nlink--;
   else
-    pi.inode.nlink++;
+    pi.inode->nlink++;
 
-  map<client_t,MClientSnap::ref> splits;
+  map<client_t,ref_t<MClientSnap>> splits;
   if (rollback.snapbl.length() && in->snaprealm) {
     bool hadrealm;
     auto p = rollback.snapbl.cbegin();
@@ -6491,8 +7888,8 @@ void Server::do_link_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
   }
 
   // journal it
-  ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_link_rollback", rollback.reqid, master,
-                                     ESlaveUpdate::OP_ROLLBACK, ESlaveUpdate::LINK);
+  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);
@@ -6504,7 +7901,7 @@ void Server::do_link_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
 }
 
 void Server::_link_rollback_finish(MutationRef& mut, MDRequestRef& mdr,
-                                  map<client_t,MClientSnap::ref>& splits)
+                                  map<client_t,ref_t<MClientSnap>>& splits)
 {
   dout(10) << "_link_rollback_finish" << dendl;
 
@@ -6518,34 +7915,34 @@ void Server::_link_rollback_finish(MutationRef& mut, MDRequestRef& mdr,
   if (mdr)
     mdcache->request_finish(mdr);
 
-  mdcache->finish_rollback(mut->reqid);
+  mdcache->finish_rollback(mut->reqid, mdr);
 
   mut->cleanup();
 }
 
 
-void Server::handle_slave_link_prep_ack(MDRequestRef& mdr, const MMDSSlaveRequest::const_ref &m)
+void Server::handle_peer_link_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &m)
 {
-  dout(10) << "handle_slave_link_prep_ack " << *mdr 
+  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 slave
-  mdr->more()->slaves.insert(from);
+  // 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_slaves = true;
+  mdr->more()->has_journaled_peers = true;
   
   // remove from waiting list
-  ceph_assert(mdr->more()->waiting_on_slave.count(from));
-  mdr->more()->waiting_on_slave.erase(from);
+  ceph_assert(mdr->more()->waiting_on_peer.count(from));
+  mdr->more()->waiting_on_peer.erase(from);
 
-  ceph_assert(mdr->more()->waiting_on_slave.empty());
+  ceph_assert(mdr->more()->waiting_on_peer.empty());
 
   dispatch_client_request(mdr);  // go again!
 }
@@ -6558,54 +7955,36 @@ void Server::handle_slave_link_prep_ack(MDRequestRef& mdr, const MMDSSlaveReques
 
 void Server::handle_client_unlink(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   client_t client = mdr->get_client();
 
   // rmdir or unlink?
-  bool rmdir = false;
-  if (req->get_op() == CEPH_MDS_OP_RMDIR) rmdir = true;
+  bool rmdir = (req->get_op() == CEPH_MDS_OP_RMDIR);
 
-  const filepath& refpath = req->get_filepath();
-  if (refpath.depth() == 0) {
-    respond_to_request(mdr, -EINVAL);
+  if (rmdir)
+    mdr->disable_lock_cache();
+
+  CDentry *dn = rdlock_path_xlock_dentry(mdr, false, true);
+  if (!dn)
     return;
-  }
-  if (refpath.is_last_dot_or_dotdot()) {
-    respond_to_request(mdr, -ENOTEMPTY);
+
+  if (is_reintegrate_pending(dn)) {
+    wait_for_pending_reintegrate(dn, mdr);
     return;
   }
 
-  // traverse to path
-  vector<CDentry*> trace;
-  CInode *in;
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, refpath, &trace, &in, MDS_TRAVERSE_FORWARD);
-  if (r > 0) return;
-  if (r < 0) {
-    if (r == -ESTALE) {
-      dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
-      mdcache->find_ino_peers(refpath.get_ino(), new C_MDS_TryFindInode(this, mdr));
+  // notify replica MDSes the dentry is under unlink
+  if (!dn->state_test(CDentry::STATE_UNLINKING)) {
+    dn->state_set(CDentry::STATE_UNLINKING);
+    mdcache->send_dentry_unlink(dn, nullptr, mdr, true);
+    if (dn->replica_unlinking_ref) {
       return;
     }
-    respond_to_request(mdr, r);
-    return;
-  }
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
-    return;
-  }
-
-  CDentry *dn = trace.back();
-  ceph_assert(dn);
-  if (!dn->is_auth()) {
-    mdcache->request_forward(mdr, dn->authority().first);
-    return;
   }
 
-  CInode *diri = dn->get_dir()->get_inode();
-
   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;
@@ -6619,19 +7998,30 @@ void Server::handle_client_unlink(MDRequestRef& mdr)
     if (rmdir) {
       // do empty directory checks
       if (_dir_is_nonempty_unlocked(mdr, in)) {
-       respond_to_request(mdr, -ENOTEMPTY);
+        dn->state_clear(CDentry::STATE_UNLINKING);
+        respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
        return;
       }
     } else {
       dout(7) << "handle_client_unlink on dir " << *in << ", returning error" << dendl;
-      respond_to_request(mdr, -EISDIR);
+      dn->state_clear(CDentry::STATE_UNLINKING);
+      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, -ENOTDIR);
+      dn->state_clear(CDentry::STATE_UNLINKING);
+      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)) {
+      dn->state_clear(CDentry::STATE_UNLINKING);
       return;
     }
   }
@@ -6649,46 +8039,40 @@ void Server::handle_client_unlink(MDRequestRef& mdr)
   }
 
   // lock
-  MutationImpl::LockOpVec lov;
+  if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+    MutationImpl::LockOpVec lov;
 
-  for (int i=0; i<(int)trace.size()-1; i++)
-    lov.add_rdlock(&trace[i]->lock);
-  lov.add_xlock(&dn->lock);
-  lov.add_wrlock(&diri->filelock);
-  lov.add_wrlock(&diri->nestlock);
-  lov.add_xlock(&in->linklock);
-  if (straydn) {
-    lov.add_wrlock(&straydn->get_dir()->inode->filelock);
-    lov.add_wrlock(&straydn->get_dir()->inode->nestlock);
-    lov.add_xlock(&straydn->lock);
-  }
+    lov.add_xlock(&in->linklock);
+    lov.add_xlock(&in->snaplock);
+    if (in->is_dir())
+      lov.add_rdlock(&in->filelock);   // to verify it's empty
 
-  mds->locker->include_snap_rdlocks(diri, lov);
-  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;
+    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, -ENOTEMPTY);
+    respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
+    dn->state_clear(CDentry::STATE_UNLINKING);
     return;
   }
 
-  if ((!mdr->has_more() || mdr->more()->witnessed.empty())) {
-    if (!check_access(mdr, diri, MAY_WRITE))
-      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, NULL, dn, dnl->is_primary());
+      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;
@@ -6698,7 +8082,7 @@ void Server::handle_client_unlink(MDRequestRef& mdr)
        in->clear_snaprealm_global(new_srnode);
       mdr->more()->desti_srnode = new_srnode;
     } else if (dnl->is_primary()) {
-      // prepare snaprealm blob for slave request
+      // 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()) {
@@ -6721,17 +8105,20 @@ void Server::handle_client_unlink(MDRequestRef& mdr)
         ++p) {
       if (mdr->more()->witnessed.count(*p)) {
        dout(10) << " already witnessed by mds." << *p << dendl;
-      } else if (mdr->more()->waiting_on_slave.count(*p)) {
+      } 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, trace, straydn))
+       if (!_rmdir_prepare_witness(mdr, *p, mdr->dn[0], straydn))
          return;
       }
     }
-    if (!mdr->more()->waiting_on_slave.empty())
+    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());
@@ -6769,10 +8156,10 @@ void Server::_unlink_local(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
   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_slaves " << mdr->more()->witnessed << dendl;
+    dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
     le->reqid = mdr->reqid;
-    le->had_slaves = true;
-    mdcache->add_uncommitted_master(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+    le->had_peers = true;
+    mdcache->add_uncommitted_leader(mdr->reqid, mdr->ls, mdr->more()->witnessed);
   }
 
   if (straydn) {
@@ -6783,19 +8170,19 @@ void Server::_unlink_local(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
   // the unlinked dentry
   dn->pre_dirty();
 
-  auto &pi = in->project_inode();
+  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)
+    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) {
@@ -6811,10 +8198,9 @@ void Server::_unlink_local(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
     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();
+    pi.inode->update_backtrace();
     le->metablob.add_primary_dentry(straydn, in, true, true);
   } else {
-    mdr->add_projected_inode(in);
     // 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);
@@ -6851,7 +8237,7 @@ void Server::_unlink_local_finish(MDRequestRef& mdr,
   dout(10) << "_unlink_local_finish " << *dn << dendl;
 
   if (!mdr->more()->witnessed.empty())
-    mdcache->logged_master_update(mdr->reqid);
+    mdcache->logged_leader_update(mdr->reqid);
 
   CInode *strayin = NULL;
   bool hadrealm = false;
@@ -6866,22 +8252,24 @@ void Server::_unlink_local_finish(MDRequestRef& mdr,
   // 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();
-
-    strayin->pop_and_dirty_projected_inode(mdr->ls);
-
     mdcache->touch_dentry_bottom(straydn);
   }
 
-  dn->mark_dirty(dnpv, mdr->ls);
   mdr->apply();
-  
+
+  dn->state_clear(CDentry::STATE_UNLINKING);
   mdcache->send_dentry_unlink(dn, straydn, mdr);
-  
+
+  MDSContext::vec finished;
+  dn->take_waiting(CDentry::WAIT_UNLINK_FINISH, finished);
+  mdcache->mds->queue_waiters(finished);
+
   if (straydn) {
     // update subtree map?
     if (strayin->is_dir())
@@ -6896,7 +8284,7 @@ void Server::_unlink_local_finish(MDRequestRef& mdr,
 
   // reply
   respond_to_request(mdr, 0);
-  
+
   // removing a new dn?
   dn->get_dir()->try_remove_unlinked_dn(dn);
 
@@ -6914,64 +8302,66 @@ bool Server::_rmdir_prepare_witness(MDRequestRef& mdr, mds_rank_t who, vector<CD
   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_slave.empty())
+    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 = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RMDIRPREP);
+  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->replicate_stray(straydn, who, req->straybl);
+  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_slave.count(who) == 0);
-  mdr->more()->waiting_on_slave.insert(who);
+  ceph_assert(mdr->more()->waiting_on_peer.count(who) == 0);
+  mdr->more()->waiting_on_peer.insert(who);
   return true;
 }
 
-struct C_MDS_SlaveRmdirPrep : public ServerLogContext {
+struct C_MDS_PeerRmdirPrep : public ServerLogContext {
   CDentry *dn, *straydn;
-  C_MDS_SlaveRmdirPrep(Server *s, MDRequestRef& r, CDentry *d, CDentry *st)
+  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_slave_rmdir(mdr, dn, straydn);
+    server->_logged_peer_rmdir(mdr, dn, straydn);
   }
 };
 
-struct C_MDS_SlaveRmdirCommit : public ServerContext {
+struct C_MDS_PeerRmdirCommit : public ServerContext {
   MDRequestRef mdr;
   CDentry *straydn;
-  C_MDS_SlaveRmdirCommit(Server *s, MDRequestRef& r, CDentry *sd)
+  C_MDS_PeerRmdirCommit(Server *s, MDRequestRef& r, CDentry *sd)
     : ServerContext(s), mdr(r), straydn(sd) { }
   void finish(int r) override {
-    server->_commit_slave_rmdir(mdr, r, straydn);
+    server->_commit_peer_rmdir(mdr, r, straydn);
   }
 };
 
-void Server::handle_slave_rmdir_prep(MDRequestRef& mdr)
+void Server::handle_peer_rmdir_prep(MDRequestRef& mdr)
 {
-  dout(10) << "handle_slave_rmdir_prep " << *mdr 
-          << " " << mdr->slave_request->srcdnpath 
-          << " to " << mdr->slave_request->destdnpath
+  dout(10) << "handle_peer_rmdir_prep " << *mdr
+          << " " << mdr->peer_request->srcdnpath
+          << " to " << mdr->peer_request->destdnpath
           << dendl;
 
   vector<CDentry*> trace;
-  filepath srcpath(mdr->slave_request->srcdnpath);
+  filepath srcpath(mdr->peer_request->srcdnpath);
   dout(10) << " src " << srcpath << dendl;
   CInode *in;
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, srcpath, &trace, &in, MDS_TRAVERSE_DISCOVERXLOCK);
+  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 == -ESTALE) {
+  if (r == -CEPHFS_ESTALE) {
     mdcache->find_ino_peers(srcpath.get_ino(), new C_MDS_RetryRequest(mdcache, mdr),
-       mdr->slave_to_mds);
+                           mdr->peer_to_mds, true);
     return;
   }
   ceph_assert(r == 0);
@@ -6983,7 +8373,7 @@ void Server::handle_slave_rmdir_prep(MDRequestRef& mdr)
   CDentry *straydn = mdr->straydn;
   dout(10) << " straydn " << *straydn << dendl;
   
-  mdr->set_op_stamp(mdr->slave_request->op_stamp);
+  mdr->set_op_stamp(mdr->peer_request->op_stamp);
 
   rmdir_rollback rollback;
   rollback.reqid = mdr->reqid;
@@ -6991,7 +8381,7 @@ void Server::handle_slave_rmdir_prep(MDRequestRef& mdr)
   rollback.src_dname = dn->get_name();
   rollback.dest_dir = straydn->get_dir()->dirfrag();
   rollback.dest_dname = straydn->get_name();
-  if (mdr->slave_request->desti_snapbl.length()) {
+  if (mdr->peer_request->desti_snapbl.length()) {
     if (in->snaprealm) {
       encode(true, rollback.snapbl);
       in->encode_snap_blob(rollback.snapbl);
@@ -7004,7 +8394,7 @@ void Server::handle_slave_rmdir_prep(MDRequestRef& mdr)
   dout(20) << " rollback is " << mdr->more()->rollback_bl.length() << " bytes" << dendl;
 
   // set up commit waiter
-  mdr->more()->slave_commit = new C_MDS_SlaveRmdirCommit(this, mdr, straydn);
+  mdr->more()->peer_commit = new C_MDS_PeerRmdirCommit(this, mdr, straydn);
 
   straydn->push_projected_linkage(in);
   dn->push_projected_linkage();
@@ -7014,41 +8404,42 @@ void Server::handle_slave_rmdir_prep(MDRequestRef& mdr)
 
   if (!in->has_subtree_root_dirfrag(mds->get_nodeid())) {
     dout(10) << " no auth subtree in " << *in << ", skipping journal" << dendl;
-    _logged_slave_rmdir(mdr, dn, straydn);
+    _logged_peer_rmdir(mdr, dn, straydn);
     return;
   }
 
-  ESlaveUpdate *le =  new ESlaveUpdate(mdlog, "slave_rmdir", mdr->reqid, mdr->slave_to_mds,
-                                      ESlaveUpdate::OP_PREPARE, ESlaveUpdate::RMDIR);
+  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);
-  // slave: no need to journal original dentry
+  // 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()->slave_update_journaled = true;
-  submit_mdlog_entry(le, new C_MDS_SlaveRmdirPrep(this, mdr, dn, straydn),
+  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_slave_rmdir(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
+void Server::_logged_peer_rmdir(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
 {
-  dout(10) << "_logged_slave_rmdir " << *mdr << " on " << *dn << dendl;
+  dout(10) << "_logged_peer_rmdir " << *mdr << " on " << *dn << dendl;
   CInode *in = dn->get_linkage()->get_inode();
 
   bool new_realm;
-  if (mdr->slave_request->desti_snapbl.length()) {
+  if (mdr->peer_request->desti_snapbl.length()) {
     new_realm = !in->snaprealm;
-    in->decode_snap_blob(mdr->slave_request->desti_snapbl);
+    in->decode_snap_blob(mdr->peer_request->desti_snapbl);
     ceph_assert(in->snaprealm);
-    ceph_assert(in->snaprealm->have_past_parents_open());
   } else {
     new_realm = false;
   }
@@ -7059,54 +8450,54 @@ void Server::_logged_slave_rmdir(MDRequestRef& mdr, CDentry *dn, CDentry *strayd
   straydn->pop_projected_linkage();
   dn->pop_projected_linkage();
 
-  mdcache->adjust_subtree_after_rename(in, dn->get_dir(), mdr->more()->slave_update_journaled);
+  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_slave_request();
+  mdr->reset_peer_request();
   mdr->straydn = 0;
 
   if (!mdr->aborted) {
-    auto reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RMDIRPREPACK);
-    if (!mdr->more()->slave_update_journaled)
+    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->slave_to_mds);
+    mds->send_message_mds(reply, mdr->peer_to_mds);
   } else {
     dout(10) << " abort flag set, finishing" << dendl;
     mdcache->request_finish(mdr);
   }
 }
 
-void Server::handle_slave_rmdir_prep_ack(MDRequestRef& mdr, const MMDSSlaveRequest::const_ref &ack)
+void Server::handle_peer_rmdir_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
 {
-  dout(10) << "handle_slave_rmdir_prep_ack " << *mdr 
+  dout(10) << "handle_peer_rmdir_prep_ack " << *mdr
           << " " << *ack << dendl;
 
   mds_rank_t from = mds_rank_t(ack->get_source().num());
 
-  mdr->more()->slaves.insert(from);
+  mdr->more()->peers.insert(from);
   mdr->more()->witnessed.insert(from);
   if (!ack->is_not_journaled())
-    mdr->more()->has_journaled_slaves = true;
+    mdr->more()->has_journaled_peers = true;
 
   // remove from waiting list
-  ceph_assert(mdr->more()->waiting_on_slave.count(from));
-  mdr->more()->waiting_on_slave.erase(from);
+  ceph_assert(mdr->more()->waiting_on_peer.count(from));
+  mdr->more()->waiting_on_peer.erase(from);
 
-  if (mdr->more()->waiting_on_slave.empty())
+  if (mdr->more()->waiting_on_peer.empty())
     dispatch_client_request(mdr);  // go again!
   else 
-    dout(10) << "still waiting on slaves " << mdr->more()->waiting_on_slave << dendl;
+    dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
 }
 
-void Server::_commit_slave_rmdir(MDRequestRef& mdr, int r, CDentry *straydn)
+void Server::_commit_peer_rmdir(MDRequestRef& mdr, int r, CDentry *straydn)
 {
-  dout(10) << "_commit_slave_rmdir " << *mdr << " r=" << r << dendl;
-  
+  dout(10) << "_commit_peer_rmdir " << *mdr << " r=" << r << dendl;
+
   if (r == 0) {
-    if (mdr->more()->slave_update_journaled) {
+    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);
@@ -7114,20 +8505,20 @@ void Server::_commit_slave_rmdir(MDRequestRef& mdr, int r, CDentry *straydn)
 
     mdr->cleanup();
 
-    if (mdr->more()->slave_update_journaled) {
+    if (mdr->more()->peer_update_journaled) {
       // write a commit to the journal
-      ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_rmdir_commit", mdr->reqid,
-                                         mdr->slave_to_mds, ESlaveUpdate::OP_COMMIT,
-                                         ESlaveUpdate::RMDIR);
+      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_CommittedSlave(this, mdr), mdr, __func__);
+      submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
       mdlog->flush();
     } else {
-      _committed_slave(mdr);
+      _committed_peer(mdr);
     }
   } else {
     // abort
-    do_rmdir_rollback(mdr->more()->rollback_bl, mdr->slave_to_mds, mdr);
+    do_rmdir_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr);
   }
 }
 
@@ -7142,7 +8533,7 @@ struct C_MDS_LoggedRmdirRollback : public ServerLogContext {
   }
 };
 
-void Server::do_rmdir_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef& mdr)
+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
@@ -7154,7 +8545,7 @@ void Server::do_rmdir_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
   decode(rollback, p);
   
   dout(10) << "do_rmdir_rollback on " << rollback.reqid << dendl;
-  mdcache->add_rollback(rollback.reqid, master); // need to finish this update before resolve finishes
+  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);
@@ -7185,7 +8576,7 @@ void Server::do_rmdir_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
     }
   }
 
-  if (mdr && !mdr->more()->slave_update_journaled) {
+  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);
@@ -7193,13 +8584,13 @@ void Server::do_rmdir_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef&
   }
 
 
-  ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_rmdir_rollback", rollback.reqid, master,
-                                     ESlaveUpdate::OP_ROLLBACK, ESlaveUpdate::RMDIR);
+  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);
-  // slave: no need to journal straydn
+  // peer: no need to journal straydn
   
   dout(10) << " noting renamed (unlinked) dir ino " << in->ino() << " in metablob" << dendl;
   le->commit.renamed_dirino = in->ino();
@@ -7223,7 +8614,7 @@ void Server::_rmdir_rollback_finish(MDRequestRef& mdr, metareqid_t reqid, CDentr
 
   CInode *in = dn->get_linkage()->get_inode();
   mdcache->adjust_subtree_after_rename(in, straydn->get_dir(),
-                                      !mdr || mdr->more()->slave_update_journaled);
+                                      !mdr || mdr->more()->peer_update_journaled);
 
   if (mds->is_resolve()) {
     CDir *root = mdcache->get_subtree_root(straydn->get_dir());
@@ -7233,7 +8624,7 @@ void Server::_rmdir_rollback_finish(MDRequestRef& mdr, metareqid_t reqid, CDentr
   if (mdr)
     mdcache->request_finish(mdr);
 
-  mdcache->finish_rollback(reqid);
+  mdcache->finish_rollback(reqid, mdr);
 }
 
 
@@ -7249,13 +8640,13 @@ 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!
 
-  list<CDir*> ls;
-  in->get_dirfrags(ls);
-  for (list<CDir*>::iterator p = ls.begin(); p != ls.end(); ++p) {
-    CDir *dir = *p;
+  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()) {
@@ -7278,11 +8669,9 @@ bool Server::_dir_is_nonempty(MDRequestRef& mdr, CInode *in)
   frag_info_t dirstat;
   version_t dirstat_version = in->get_projected_inode()->dirstat.version;
 
-  list<CDir*> ls;
-  in->get_dirfrags(ls);
-  for (list<CDir*>::iterator p = ls.begin(); p != ls.end(); ++p) {
-    CDir *dir = *p;
-    const fnode_t *pf = dir->get_projected_fnode();
+  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;
@@ -7320,79 +8709,86 @@ public:
 
 /** handle_client_rename
  *
- * rename master is the destdn auth.  this is because cached inodes
+ * 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 master (destdn auth) verifies that
+ * 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 master.
+ * destdn is leader.
  *
  * This function takes responsibility for the passed mdr.
  */
 void Server::handle_client_rename(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const auto& req = mdr->client_request;
   dout(7) << "handle_client_rename " << *req << dendl;
 
   filepath destpath = req->get_filepath();
   filepath srcpath = req->get_filepath2();
-  if (destpath.depth() == 0 || srcpath.depth() == 0) {
-    respond_to_request(mdr, -EINVAL);
-    return;
-  }
   if (srcpath.is_last_dot_or_dotdot() || destpath.is_last_dot_or_dotdot()) {
-    respond_to_request(mdr, -EBUSY);
+    respond_to_request(mdr, -CEPHFS_EBUSY);
     return;
   }
 
-  std::string_view destname = destpath.last_dentry();
+  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;
+  }
 
-  vector<CDentry*>& srctrace = mdr->dn[1];
-  vector<CDentry*>& desttrace = mdr->dn[0];
+  auto [destdn, srcdn] = rdlock_two_paths_xlock_destdn(mdr, true);
+  if (!destdn)
+    return;
 
-  MutationImpl::LockOpVec lov;
+  if (is_unlink_pending(destdn)) {
+    wait_for_pending_unlink(destdn, mdr);
+    return;
+  }
 
-  CDentry *destdn = rdlock_path_xlock_dentry(mdr, 0, lov, true, false, true);
-  if (!destdn) return;
-  dout(10) << " destdn " << *destdn << dendl;
-  if (mdr->snapid != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
+  if (is_unlink_pending(srcdn)) {
+    wait_for_pending_unlink(srcdn, mdr);
     return;
   }
-  CDentry::linkage_t *destdnl = destdn->get_projected_linkage();
+
+  dout(10) << " destdn " << *destdn << dendl;
   CDir *destdir = destdn->get_dir();
   ceph_assert(destdir->is_auth());
+  CDentry::linkage_t *destdnl = destdn->get_projected_linkage();
 
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, srcpath, &srctrace, NULL, MDS_TRAVERSE_DISCOVER);
-  if (r > 0)
-    return; // delayed
-  if (r < 0) {
-    if (r == -ESTALE) {
-      dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
-      mdcache->find_ino_peers(srcpath.get_ino(), new C_MDS_TryFindInode(this, mdr));
-    } else {
-      dout(10) << "FAIL on error " << r << dendl;
-      respond_to_request(mdr, r);
-    }
-    return;
-
-  }
-  ceph_assert(!srctrace.empty());
-  CDentry *srcdn = srctrace.back();
   dout(10) << " srcdn " << *srcdn << dendl;
-  if (srcdn->last != CEPH_NOSNAP) {
-    respond_to_request(mdr, -EROFS);
-    return;
-  }
   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;
@@ -7402,36 +8798,37 @@ void Server::handle_client_rename(MDRequestRef& mdr)
 
     // 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, -ENOTEMPTY);
+      respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
       return;
     }
 
-    // if srcdn is replica, need to make sure its linkage is correct
-    if (srcdn->is_auth() ||
-       srcdn->lock.can_read(mdr->get_client()) ||
-       (srcdn->lock.is_xlocked() && srcdn->lock.get_xlock_by() == mdr)) {
-      // mv /some/thing /to/some/existing_other_thing
-      if (oldin->is_dir() && !srci->is_dir()) {
-       respond_to_request(mdr, -EISDIR);
-       return;
-      }
-      if (!oldin->is_dir() && srci->is_dir()) {
-       respond_to_request(mdr, -ENOTDIR);
-       return;
-      }
-      if (srci == oldin && !srcdir->inode->is_stray()) {
-       respond_to_request(mdr, 0);  // no-op.  POSIX makes no sense.
-       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;
     }
   }
 
-  // -- some sanity checks --
+  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_MDSDIR(srcpath.get_ino()))) {  // <-- mds 'rename' out of stray dir is ok!
+       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.
@@ -7447,41 +8844,12 @@ void Server::handle_client_rename(MDRequestRef& mdr)
     while (destbase != srcbase) {
       CDentry *pdn = destbase->get_projected_parent_dn();
       desttrace.insert(desttrace.begin(), pdn);
-      lov.add_rdlock(&pdn->lock);
       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;
   }
 
-  // src == dest?
-  if (srcdir == destdir && srcdn->get_name() == destname) {
-    dout(7) << "rename src=dest, noop" << dendl;
-    respond_to_request(mdr, 0);
-    return;
-  }
-
-  // dest a child of src?
-  // e.g. mv /usr /usr/foo
-  CDentry *pdn = destdir->inode->get_projected_parent_dn();
-  while (pdn) {
-    if (pdn == srcdn) {
-      dout(7) << "cannot rename item to be a child of itself" << dendl;
-      respond_to_request(mdr, -EINVAL);
-      return;
-    }
-    pdn = pdn->get_dir()->inode->parent;
-  }
-
-  // is this a stray migration, reintegration or merge? (sanity checks!)
-  if (mdr->reqid.name.is_mds() &&
-      !(MDS_INO_IS_MDSDIR(srcpath.get_ino()) &&
-       MDS_INO_IS_MDSDIR(destpath.get_ino())) &&
-      !(destdnl->is_remote() &&
-       destdnl->get_remote_ino() == srci->ino())) {
-    respond_to_request(mdr, -EINVAL);  // actually, this won't reply, but whatev.
-    return;
-  }
 
   bool linkmerge = srcdnl->get_inode() == destdnl->get_inode();
   if (linkmerge)
@@ -7499,93 +8867,48 @@ void Server::handle_client_rename(MDRequestRef& mdr)
     mdr->straydn = NULL;
   }
 
-  // -- prepare witness list --
-  /*
-   * 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;
-
 
   // -- locks --
+  if (!(mdr->locking_state & MutationImpl::ALL_LOCKED)) {
+    MutationImpl::LockOpVec lov;
 
-  // srctrace items.  this mirrors locks taken in rdlock_path_xlock_dentry
-  for (int i=0; i<(int)srctrace.size(); i++) 
-    lov.add_rdlock(&srctrace[i]->lock);
-  lov.add_xlock(&srcdn->lock);
-  mds_rank_t srcdirauth = srcdir->authority().first;
-  if (srcdirauth != mds->get_nodeid()) {
-    dout(10) << " will remote_wrlock srcdir scatterlocks on mds." << srcdirauth << dendl;
-    lov.add_remote_wrlock(&srcdir->inode->filelock, srcdirauth);
-    lov.add_remote_wrlock(&srcdir->inode->nestlock, srcdirauth);
-    if (srci->is_dir())
-      lov.add_rdlock(&srci->dirfragtreelock);
-  } else {
-    lov.add_wrlock(&srcdir->inode->filelock);
-    lov.add_wrlock(&srcdir->inode->nestlock);
-  }
-  mds->locker->include_snap_rdlocks(srcdir->inode, lov);
-
-  // straydn?
-  if (straydn) {
-    lov.add_wrlock(&straydn->get_dir()->inode->filelock);
-    lov.add_wrlock(&straydn->get_dir()->inode->nestlock);
-    lov.add_xlock(&straydn->lock);
-  }
+    // 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);
 
-  // xlock versionlock on dentries if there are witnesses.
-  //  replicas can't see projected dentry linkages, and will get
-  //  confused if we try to pipeline things.
-  if (!witnesses.empty()) {
-    // take xlock on all projected ancestor dentries for srcdn and destdn.
-    // this ensures the srcdn and destdn can be traversed to by the witnesses.
-    for (int i= 0; i<(int)srctrace.size(); i++) {
-      if (srctrace[i]->is_auth() && srctrace[i]->is_projected())
-         lov.add_xlock(&srctrace[i]->versionlock);
-    }
-    for (int i=0; i<(int)desttrace.size(); i++) {
-      if (desttrace[i]->is_auth() && desttrace[i]->is_projected())
-         lov.add_xlock(&desttrace[i]->versionlock);
-    }
-    // xlock srci and oldin's primary dentries, so witnesses can call
-    // open_remote_ino() with 'want_locked=true' when the srcdn or destdn
-    // is traversed.
-    if (srcdnl->is_remote())
-      lov.add_xlock(&srci->get_projected_parent_dn()->lock);
-    if (destdnl->is_remote())
-      lov.add_xlock(&oldin->get_projected_parent_dn()->lock);
-  }
-
-  // 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())
+    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
-  }
 
-  CInode *auth_pin_freeze = !srcdn->is_auth() && srcdnl->is_primary() ? srci : NULL;
-  if (!mds->locker->acquire_locks(mdr, lov, auth_pin_freeze))
-    return;
+       // 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());
@@ -7597,7 +8920,10 @@ void Server::handle_client_rename(MDRequestRef& mdr)
     if (!check_access(mdr, destdn->get_dir()->get_inode(), MAY_WRITE))
       return;
 
-    if (!check_fragment_space(mdr, destdn->get_dir()))
+    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))
@@ -7608,7 +8934,7 @@ void Server::handle_client_rename(MDRequestRef& mdr)
   if (oldin &&
       oldin->is_dir() &&
       _dir_is_nonempty(mdr, oldin)) {
-    respond_to_request(mdr, -ENOTEMPTY);
+    respond_to_request(mdr, -CEPHFS_ENOTEMPTY);
     return;
   }
 
@@ -7628,6 +8954,21 @@ void Server::handle_client_rename(MDRequestRef& mdr)
   }
   */
 
+  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 --
@@ -7658,7 +8999,7 @@ void Server::handle_client_rename(MDRequestRef& mdr)
        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, NULL, destdn, false);
+      srci->record_snaprealm_parent_dentry(new_srnode, nullptr, destdn, false);
 
       srci->clear_snaprealm_global(new_srnode);
       mdr->more()->srci_srnode = new_srnode;
@@ -7667,7 +9008,7 @@ void Server::handle_client_rename(MDRequestRef& mdr)
     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, NULL, destdn, destdnl->is_primary());
+       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;
@@ -7677,7 +9018,6 @@ void Server::handle_client_rename(MDRequestRef& mdr)
          oldin->clear_snaprealm_global(new_srnode);
        mdr->more()->desti_srnode = new_srnode;
       } else if (destdnl->is_primary()) {
-       SnapRealm *dest_realm = destdir->inode->find_snaprealm();
        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);
@@ -7687,13 +9027,11 @@ void Server::handle_client_rename(MDRequestRef& mdr)
       }
     }
     if (!mdr->more()->srci_srnode) {
-      SnapRealm *dest_realm = destdir->inode->find_snaprealm();
       if (srci->is_projected_snaprealm_global()) {
        sr_t *new_srnode = srci->prepare_new_srnode(0);
-       srci->record_snaprealm_parent_dentry(new_srnode, dest_realm, srcdn, srcdnl->is_primary());
+       srci->record_snaprealm_parent_dentry(new_srnode, src_realm, srcdn, srcdnl->is_primary());
        mdr->more()->srci_srnode = new_srnode;
       } else if (srcdnl->is_primary()) {
-       SnapRealm *src_realm = srcdir->inode->find_snaprealm();
        snapid_t follows = src_realm->get_newest_seq();
        if (src_realm != dest_realm &&
            (srci->snaprealm || follows + 1 > srci->get_oldest_snap())) {
@@ -7707,6 +9045,58 @@ void Server::handle_client_rename(MDRequestRef& mdr)
 
   // -- 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()) {
@@ -7729,27 +9119,27 @@ void Server::handle_client_rename(MDRequestRef& mdr)
     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_slave.count(*p)) {
+    } 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_slave.empty())
+  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_slave.count(last) == 0);
+    ceph_assert(mdr->more()->waiting_on_peer.count(last) == 0);
     _rename_prepare_witness(mdr, last, witnesses, srctrace, desttrace, straydn);
     return;
   }
 
-  // test hack: bail after slave does prepare, so we can verify it's _live_ rollback.
-  if (!mdr->more()->slaves.empty() && !srci->is_dir())
+  // 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()->slaves.empty() && srci->is_dir())
+  if (!mdr->more()->peers.empty() && srci->is_dir())
     ceph_assert(g_conf()->mds_kill_rename_at != 4);
 
   // -- declare now --
@@ -7759,19 +9149,19 @@ void Server::handle_client_rename(MDRequestRef& mdr)
   mdr->ls = mdlog->get_current_segment();
   EUpdate *le = new EUpdate(mdlog, "rename");
   mdlog->start_entry(le);
-  le->metablob.add_client_req(mdr->reqid, mdr->client_request->get_oldest_client_tid());
+  le->metablob.add_client_req(mdr->reqid, req->get_oldest_client_tid());
   if (!mdr->more()->witnessed.empty()) {
-    dout(20) << " noting uncommitted_slaves " << mdr->more()->witnessed << dendl;
+    dout(20) << " noting uncommitted_peers " << mdr->more()->witnessed << dendl;
     
     le->reqid = mdr->reqid;
-    le->had_slaves = true;
+    le->had_peers = true;
     
-    mdcache->add_uncommitted_master(mdr->reqid, mdr->ls, mdr->more()->witnessed);
+    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, straydn);
+  _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();
 
@@ -7779,6 +9169,12 @@ void Server::handle_client_rename(MDRequestRef& mdr)
   C_MDS_rename_finish *fin = new C_MDS_rename_finish(this, mdr, srcdn, destdn, straydn);
 
   journal_and_reply(mdr, srci, destdn, le, fin);
+
+  // trigger to flush mdlog in case reintegrating or migrating the stray dn,
+  // because the link requests maybe waiting.
+  if (srcdn->get_dir()->inode->is_stray()) {
+    mdlog->flush();
+  }
   mds->balancer->maybe_fragment(destdn->get_dir(), false);
 }
 
@@ -7788,7 +9184,7 @@ void Server::_rename_finish(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn,
   dout(10) << "_rename_finish " << *mdr << dendl;
 
   if (!mdr->more()->witnessed.empty())
-    mdcache->logged_master_update(mdr->reqid);
+    mdcache->logged_leader_update(mdr->reqid);
 
   // apply
   _rename_apply(mdr, srcdn, destdn, straydn);
@@ -7799,10 +9195,10 @@ void Server::_rename_finish(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn,
   CInode *in = destdnl->get_inode();
   bool need_eval = mdr->more()->cap_imports.count(in);
 
-  // test hack: test slave commit
-  if (!mdr->more()->slaves.empty() && !in->is_dir())
+  // 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()->slaves.empty() && in->is_dir())
+  if (!mdr->more()->peers.empty() && in->is_dir())
     ceph_assert(g_conf()->mds_kill_rename_at != 6);
   
   // bump popularity
@@ -7834,16 +9230,19 @@ void Server::_rename_finish(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn,
 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_slave.empty())
+    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 = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RENAMEPREP);
+  auto req = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREP);
 
   req->srcdnpath = filepath(srctrace.front()->get_dir()->ino());
   for (auto dn : srctrace)
@@ -7851,8 +9250,9 @@ bool Server::_rename_prepare_witness(MDRequestRef& mdr, mds_rank_t who, set<mds_
   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->replicate_stray(straydn, who, req->straybl);
+    mdcache->encode_replica_stray(straydn, who, req->straybl);
 
   if (mdr->more()->srci_srnode)
     encode(*mdr->more()->srci_srnode, req->srci_snapbl);
@@ -7867,8 +9267,8 @@ bool Server::_rename_prepare_witness(MDRequestRef& mdr, mds_rank_t who, set<mds_
   req->op_stamp = mdr->get_op_stamp();
   mds->send_message_mds(req, who);
   
-  ceph_assert(mdr->more()->waiting_on_slave.count(who) == 0);
-  mdr->more()->waiting_on_slave.insert(who);
+  ceph_assert(mdr->more()->waiting_on_peer.count(who) == 0);
+  mdr->more()->waiting_on_peer.insert(who);
   return true;
 }
 
@@ -7904,8 +9304,7 @@ version_t Server::_rename_prepare_import(MDRequestRef& mdr, CDentry *srcdn, buff
 
 bool Server::_need_force_journal(CInode *diri, bool empty)
 {
-  std::vector<CDir*> dirs;
-  diri->get_dirfrags(dirs);
+  auto&& dirs = diri->get_dirfrags();
 
   bool force_journal = false;
   if (empty) {
@@ -7944,7 +9343,8 @@ bool Server::_need_force_journal(CInode *diri, bool empty)
 
 void Server::_rename_prepare(MDRequestRef& mdr,
                             EMetaBlob *metablob, bufferlist *client_map_bl,
-                            CDentry *srcdn, CDentry *destdn, CDentry *straydn)
+                            CDentry *srcdn, CDentry *destdn, std::string_view alternate_name,
+                             CDentry *straydn)
 {
   dout(10) << "_rename_prepare " << *mdr << " " << *srcdn << " " << *destdn << dendl;
   if (straydn)
@@ -8003,23 +9403,27 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       ceph_assert(straydn);  // moving to straydn.
       // link--, and move.
       if (destdn->is_auth()) {
-       auto &pi= oldin->project_inode(); //project_snaprealm
-       pi.inode.version = straydn->pre_dirty(pi.inode.version);
-       pi.inode.update_backtrace();
-        tpi = &pi.inode;
+       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();
-       pi.inode.version = oldin->pre_dirty();
-        tpi = &pi.inode;
+       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
@@ -8028,16 +9432,16 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       destdn->push_projected_linkage(srcdnl->get_remote_ino(), srcdnl->get_remote_d_type());
       // srci
       if (srci->is_auth()) {
-       auto &pi = srci->project_inode();
-       pi.inode.version = srci->pre_dirty();
-        spi = &pi.inode;
+       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();
-       pi.inode.version = mdr->more()->pvmap[destdn] = destdn->pre_dirty(oldin->inode.version);
-        spi = &pi.inode;
+       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
@@ -8051,21 +9455,19 @@ void Server::_rename_prepare(MDRequestRef& mdr,
        // note which dirfrags have child subtrees in the journal
        // event, so that we can open those (as bounds) during replay.
        if (srci->is_dir()) {
-         list<CDir*> ls;
-         srci->get_dirfrags(ls);
-         for (list<CDir*>::iterator p = ls.begin(); p != ls.end(); ++p) {
-           CDir *dir = *p;
+         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(); // project snaprealm if srcdnl->is_primary
+      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;
+      pi.inode->version = mdr->more()->pvmap[destdn] = destdn->pre_dirty(oldpv);
+      pi.inode->update_backtrace();
+      spi = pi.inode.get();
     }
     destdn->push_projected_linkage(srci);
   }
@@ -8113,6 +9515,12 @@ void Server::_rename_prepare(MDRequestRef& mdr,
     }
   }
 
+  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(),
@@ -8123,6 +9531,12 @@ void Server::_rename_prepare(MDRequestRef& mdr,
                                        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;
@@ -8155,10 +9569,10 @@ void Server::_rename_prepare(MDRequestRef& mdr,
     } else if (destdnl->is_remote()) {
       if (oldin->is_auth()) {
        sr_t *new_srnode = NULL;
-       if (mdr->slave_request) {
-         if (mdr->slave_request->desti_snapbl.length() > 0) {
+       if (mdr->peer_request) {
+         if (mdr->peer_request->desti_snapbl.length() > 0) {
            new_srnode = new sr_t();
-           auto p = mdr->slave_request->desti_snapbl.cbegin();
+           auto p = mdr->peer_request->desti_snapbl.cbegin();
            decode(*new_srnode, p);
          }
        } else if (auto& desti_srnode = mdr->more()->desti_srnode) {
@@ -8171,10 +9585,9 @@ void Server::_rename_prepare(MDRequestRef& mdr,
            ceph_assert(!new_srnode->is_parent_global());
        }
        // auth for targeti
-       metablob->add_dir_context(oldin->get_projected_parent_dir());
-       mdcache->journal_cow_dentry(mdr.get(), metablob, oldin->get_projected_parent_dn(),
-                                   CEPH_NOSNAP, 0, destdnl);
-       metablob->add_primary_dentry(oldin->get_projected_parent_dn(), oldin, true);
+       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);
       }
     }
   }
@@ -8191,10 +9604,10 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       metablob->add_remote_dentry(destdn, true, srcdnl->get_remote_ino(), srcdnl->get_remote_d_type());
 
     if (srci->is_auth() ) { // it's remote
-      if (mdr->slave_request) {
-       if (mdr->slave_request->srci_snapbl.length() > 0) {
+      if (mdr->peer_request) {
+       if (mdr->peer_request->srci_snapbl.length() > 0) {
          sr_t *new_srnode = new sr_t();
-         auto p = mdr->slave_request->srci_snapbl.cbegin();
+         auto p = mdr->peer_request->srci_snapbl.cbegin();
          decode(*new_srnode, p);
          srci->project_snaprealm(new_srnode);
        }
@@ -8204,8 +9617,7 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       }
 
       CDentry *srci_pdn = srci->get_projected_parent_dn();
-      metablob->add_dir_context(srci_pdn->get_dir());
-      mdcache->journal_cow_dentry(mdr.get(), metablob, srci_pdn, CEPH_NOSNAP, 0, srcdnl);
+      mdcache->journal_cow_dentry(mdr.get(), metablob, srci_pdn);
       metablob->add_primary_dentry(srci_pdn, srci, true);
     }
   } else if (srcdnl->is_primary()) {
@@ -8221,6 +9633,16 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       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);
@@ -8230,10 +9652,8 @@ void Server::_rename_prepare(MDRequestRef& mdr,
       metablob->add_primary_dentry(destdn, srci, true);
       if (srcdn->is_auth() && srci->is_dir()) {
        // journal new subtrees root dirfrags
-       list<CDir*> ls;
-       srci->get_dirfrags(ls);
-       for (list<CDir*>::iterator p = ls.begin(); p != ls.end(); ++p) {
-         CDir *dir = *p;
+       auto&& ls = srci->get_dirfrags();
+       for (const auto& dir : ls) {
          if (dir->is_auth())
            metablob->add_dir(dir, true);
        }
@@ -8245,7 +9665,7 @@ void Server::_rename_prepare(MDRequestRef& mdr,
   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 slave rename rollback. It is Ok to add
+    // 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())
@@ -8306,34 +9726,33 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
        oldin->early_pop_projected_snaprealm();
        new_oldin_snaprealm = (oldin->snaprealm && !hadrealm);
       } else {
-       ceph_assert(mdr->slave_request);
-       if (mdr->slave_request->desti_snapbl.length()) {
+       ceph_assert(mdr->peer_request);
+       if (mdr->peer_request->desti_snapbl.length()) {
          new_oldin_snaprealm = !oldin->snaprealm;
-         oldin->decode_snap_blob(mdr->slave_request->desti_snapbl);
+         oldin->decode_snap_blob(mdr->peer_request->desti_snapbl);
          ceph_assert(oldin->snaprealm);
-         ceph_assert(oldin->snaprealm->have_past_parents_open());
        }
       }
 
       destdn->get_dir()->unlink_inode(destdn, false);
 
       straydn->pop_projected_linkage();
-      if (mdr->is_slave() && !mdr->more()->slave_update_journaled)
+      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);
+       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);
-      } else if (mdr->slave_request) {
-       if (mdr->slave_request->desti_snapbl.length() > 0) {
+       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->slave_request->desti_snapbl);
+         oldin->decode_snap_blob(mdr->peer_request->desti_snapbl);
        }
       } else if (auto& desti_srnode = mdr->more()->desti_srnode) {
        delete desti_srnode;
@@ -8355,24 +9774,31 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
       in->early_pop_projected_snaprealm();
       new_in_snaprealm = (in->snaprealm && !hadrealm);
     } else {
-      ceph_assert(mdr->slave_request);
-      if (mdr->slave_request->srci_snapbl.length()) {
+      ceph_assert(mdr->peer_request);
+      if (mdr->peer_request->srci_snapbl.length()) {
        new_in_snaprealm = !in->snaprealm;
-       in->decode_snap_blob(mdr->slave_request->srci_snapbl);
+       in->decode_snap_blob(mdr->peer_request->srci_snapbl);
        ceph_assert(in->snaprealm);
-       ceph_assert(in->snaprealm->have_past_parents_open());
       }
     }
   }
 
   srcdn->get_dir()->unlink_inode(srcdn);
 
+  // After the stray dn being unlinked from the corresponding inode in case of
+  // reintegrate_stray/migrate_stray, just wake up the waitiers.
+  MDSContext::vec finished;
+  in->take_waiting(CInode::WAIT_UNLINK, finished);
+  if (!finished.empty()) {
+    mds->queue_waiters(finished);
+  }
+
   // dest
   if (srcdn_was_remote) {
     if (!linkmerge) {
       // destdn
       destdnl = destdn->pop_projected_linkage();
-      if (mdr->is_slave() && !mdr->more()->slave_update_journaled)
+      if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
        ceph_assert(!destdn->is_projected()); // no other projected
 
       destdn->link_remote(destdnl, in);
@@ -8380,11 +9806,11 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
        destdn->mark_dirty(mdr->more()->pvmap[destdn], mdr->ls);
       // in
       if (in->is_auth()) {
-       in->pop_and_dirty_projected_inode(mdr->ls);
-      } else if (mdr->slave_request) {
-       if (mdr->slave_request->srci_snapbl.length() > 0) {
+       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->slave_request->srci_snapbl);
+         in->decode_snap_blob(mdr->peer_request->srci_snapbl);
        }
       } else if (auto& srci_srnode = mdr->more()->srci_srnode) {
        delete srci_srnode;
@@ -8392,7 +9818,7 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
       }
     } else {
       dout(10) << "merging remote onto primary link" << dendl;
-      oldin->pop_and_dirty_projected_inode(mdr->ls);
+      oldin->pop_and_dirty_projected_inode(mdr->ls, mdr);
     }
   } else { // primary
     if (linkmerge) {
@@ -8400,7 +9826,7 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
       destdn->get_dir()->unlink_inode(destdn, false);
     }
     destdnl = destdn->pop_projected_linkage();
-    if (mdr->is_slave() && !mdr->more()->slave_update_journaled)
+    if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
       ceph_assert(!destdn->is_projected()); // no other projected
 
     // srcdn inode import?
@@ -8443,14 +9869,14 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
     }
 
     if (destdn->is_auth())
-      in->pop_and_dirty_projected_inode(mdr->ls);
+      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_slave() && !mdr->more()->slave_update_journaled)
+  if (mdr->is_peer() && !mdr->more()->peer_update_journaled)
     ceph_assert(!srcdn->is_projected()); // no other projected
   
   // apply remaining projected inodes (nested)
@@ -8476,65 +9902,67 @@ void Server::_rename_apply(MDRequestRef& mdr, CDentry *srcdn, CDentry *destdn, C
 
 
 // ------------
-// SLAVE
+// PEER
 
-class C_MDS_SlaveRenamePrep : public ServerLogContext {
+class C_MDS_PeerRenamePrep : public ServerLogContext {
   CDentry *srcdn, *destdn, *straydn;
 public:
-  C_MDS_SlaveRenamePrep(Server *s, MDRequestRef& m, CDentry *sr, CDentry *de, CDentry *st) :
+  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_slave_rename(mdr, srcdn, destdn, straydn);
+    server->_logged_peer_rename(mdr, srcdn, destdn, straydn);
   }
 };
 
-class C_MDS_SlaveRenameCommit : public ServerContext {
+class C_MDS_PeerRenameCommit : public ServerContext {
   MDRequestRef mdr;
   CDentry *srcdn, *destdn, *straydn;
 public:
-  C_MDS_SlaveRenameCommit(Server *s, MDRequestRef& m, CDentry *sr, CDentry *de, CDentry *st) :
+  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_slave_rename(mdr, r, srcdn, destdn, straydn);
+    server->_commit_peer_rename(mdr, r, srcdn, destdn, straydn);
   }
 };
 
-class C_MDS_SlaveRenameSessionsFlushed : public ServerContext {
+class C_MDS_PeerRenameSessionsFlushed : public ServerContext {
   MDRequestRef mdr;
 public:
-  C_MDS_SlaveRenameSessionsFlushed(Server *s, MDRequestRef& r) :
+  C_MDS_PeerRenameSessionsFlushed(Server *s, MDRequestRef& r) :
     ServerContext(s), mdr(r) {}
   void finish(int r) override {
-    server->_slave_rename_sessions_flushed(mdr);
+    server->_peer_rename_sessions_flushed(mdr);
   }
 };
 
-void Server::handle_slave_rename_prep(MDRequestRef& mdr)
+void Server::handle_peer_rename_prep(MDRequestRef& mdr)
 {
-  dout(10) << "handle_slave_rename_prep " << *mdr 
-          << " " << mdr->slave_request->srcdnpath 
-          << " to " << mdr->slave_request->destdnpath
+  dout(10) << "handle_peer_rename_prep " << *mdr
+          << " " << mdr->peer_request->srcdnpath
+          << " to " << mdr->peer_request->destdnpath
           << dendl;
 
-  if (mdr->slave_request->is_interrupted()) {
-    dout(10) << " slave request interrupted, sending noop reply" << dendl;
-    auto reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RENAMEPREPACK);
+  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->slave_to_mds);
-    mdr->reset_slave_request();
+    mds->send_message_mds(reply, mdr->peer_to_mds);
+    mdr->reset_peer_request();
     return;
   }
 
   // discover destdn
-  filepath destpath(mdr->slave_request->destdnpath);
+  filepath destpath(mdr->peer_request->destdnpath);
   dout(10) << " dest " << destpath << dendl;
   vector<CDentry*> trace;
-  CF_MDS_MDRContextFactory cf(mdcache, mdr);
-  int r = mdcache->path_traverse(mdr, cf, destpath, &trace, NULL, MDS_TRAVERSE_DISCOVERXLOCK);
+  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 == -ESTALE) {
+  if (r == -CEPHFS_ESTALE) {
     mdcache->find_ino_peers(destpath.get_ino(), new C_MDS_RetryRequest(mdcache, mdr),
-                           mdr->slave_to_mds);
+                           mdr->peer_to_mds, true);
     return;
   }
   ceph_assert(r == 0);  // we shouldn't get an error here!
@@ -8545,16 +9973,15 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
   mdr->pin(destdn);
   
   // discover srcdn
-  filepath srcpath(mdr->slave_request->srcdnpath);
+  filepath srcpath(mdr->peer_request->srcdnpath);
   dout(10) << " src " << srcpath << dendl;
   CInode *srci = nullptr;
-  r = mdcache->path_traverse(mdr, cf, srcpath, &trace, &srci, MDS_TRAVERSE_DISCOVERXLOCK);
+  r = mdcache->path_traverse(mdr, cf, srcpath,
+                            MDS_TRAVERSE_DISCOVER | MDS_TRAVERSE_PATH_LOCKED,
+                            &trace, &srci);
   if (r > 0) return;
   ceph_assert(r == 0);
 
-  // srcpath must not point to a null dentry
-  ceph_assert(srci != nullptr);
-      
   CDentry *srcdn = trace.back();
   CDentry::linkage_t *srcdnl = srcdn->get_projected_linkage();
   dout(10) << " srcdn " << *srcdn << dendl;
@@ -8569,12 +9996,12 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
   if (destdnl->is_primary() && !linkmerge)
     ceph_assert(straydn);
 
-  mdr->set_op_stamp(mdr->slave_request->op_stamp);
+  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()->slave_commit)
-    mdr->more()->slave_commit = new C_MDS_SlaveRenameCommit(this, mdr, srcdn, destdn, straydn);
+  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()) {
@@ -8604,27 +10031,27 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
       /*
        * set ambiguous auth for srci
        * NOTE: we don't worry about ambiguous cache expire as we do
-       * with subtree migrations because all slaves will pin
+       * 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 master will send another OP_RENAMEPREP slave request later.
-      if (mdr->slave_request->witnesses.size() > 1) {
+      // 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->slave_to_mds ||
+       if (*p == mdr->peer_to_mds ||
            (mds->is_cluster_degraded() &&
             !mds->mdsmap->is_clientreplay_or_active_or_stopping(*p)))
          continue;
-       auto notify = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RENAMENOTIFY);
+       auto notify = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMENOTIFY);
        mds->send_message_mds(notify, *p);
-       mdr->more()->waiting_on_slave.insert(*p);
+       mdr->more()->waiting_on_peer.insert(*p);
       }
 
       // make sure clients have received all cap related messages
@@ -8634,16 +10061,16 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
       MDSGatherBuilder gather(g_ceph_context);
       flush_client_sessions(export_client_set, gather);
       if (gather.has_subs()) {
-       mdr->more()->waiting_on_slave.insert(MDS_RANK_NONE);
-       gather.set_finisher(new C_MDS_SlaveRenameSessionsFlushed(this, mdr));
+       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->slave_to_mds ||
-         mdr->slave_request->witnesses.count(*p)) continue;
+      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;
@@ -8651,16 +10078,16 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
 
     if (reply_witness) {
       ceph_assert(!srcdnrep.empty());
-      auto reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RENAMEPREPACK);
+      auto reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREPACK);
       reply->witnesses.swap(srcdnrep);
-      mds->send_message_mds(reply, mdr->slave_to_mds);
-      mdr->reset_slave_request();
+      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_slave.empty()) {
+    if (!mdr->more()->waiting_on_peer.empty()) {
       dout(10) << " still waiting for rename notify acks from "
-              << mdr->more()->waiting_on_slave << dendl;
+              << mdr->more()->waiting_on_peer << dendl;
       return;
     }
   } else if (srcdnl->is_primary() && srcdn->authority() != destdn->authority()) {
@@ -8702,7 +10129,7 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
     rollback.stray.dirfrag_old_rctime = straydn->get_dir()->get_projected_fnode()->rstat.rctime;
     rollback.stray.dname = straydn->get_name();
   }
-  if (mdr->slave_request->desti_snapbl.length()) {
+  if (mdr->peer_request->desti_snapbl.length()) {
     CInode *oldin = destdnl->get_inode();
     if (oldin->snaprealm) {
       encode(true, rollback.desti_snapbl);
@@ -8711,7 +10138,7 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
       encode(false, rollback.desti_snapbl);
     }
   }
-  if (mdr->slave_request->srci_snapbl.length()) {
+  if (mdr->peer_request->srci_snapbl.length()) {
     if (srci->snaprealm) {
       encode(true, rollback.srci_snapbl);
       srci->encode_snap_blob(rollback.srci_snapbl);
@@ -8725,37 +10152,38 @@ void Server::handle_slave_rename_prep(MDRequestRef& mdr)
 
   // journal.
   mdr->ls = mdlog->get_current_segment();
-  ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_rename_prep", mdr->reqid, mdr->slave_to_mds,
-                                     ESlaveUpdate::OP_PREPARE, ESlaveUpdate::RENAME);
+  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 slave
-  _rename_prepare(mdr, &le->commit, &blah, srcdn, destdn, straydn);
+  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_slave_rename(mdr, srcdn, destdn, straydn);
+    _logged_peer_rename(mdr, srcdn, destdn, straydn);
   } else {
-    mdr->more()->slave_update_journaled = true;
-    submit_mdlog_entry(le, new C_MDS_SlaveRenamePrep(this, mdr, srcdn, destdn, straydn),
+    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_slave_rename(MDRequestRef& mdr,
+void Server::_logged_peer_rename(MDRequestRef& mdr,
                                  CDentry *srcdn, CDentry *destdn, CDentry *straydn)
 {
-  dout(10) << "_logged_slave_rename " << *mdr << dendl;
+  dout(10) << "_logged_peer_rename " << *mdr << dendl;
 
   // prepare ack
-  MMDSSlaveRequest::ref reply;
+  ref_t<MMDSPeerRequest> reply;
   if (!mdr->aborted) {
-    reply = MMDSSlaveRequest::create(mdr->reqid, mdr->attempt, MMDSSlaveRequest::OP_RENAMEPREPACK);
-    if (!mdr->more()->slave_update_journaled)
+    reply = make_message<MMDSPeerRequest>(mdr->reqid, mdr->attempt, MMDSPeerRequest::OP_RENAMEPREPACK);
+    if (!mdr->more()->peer_update_journaled)
       reply->mark_not_journaled();
   }
 
@@ -8766,11 +10194,12 @@ void Server::_logged_slave_rename(MDRequestRef& mdr,
   if (srcdn->is_auth() && srcdnl->is_primary()) {
     // set export bounds for CInode::encode_export()
     if (reply) {
-      list<CDir*> bounds;
+      std::vector<CDir*> bounds;
       if (srcdnl->get_inode()->is_dir()) {
        srcdnl->get_inode()->get_dirfrags(bounds);
-       for (list<CDir*>::iterator p = bounds.begin(); p != bounds.end(); ++p)
-         (*p)->state_set(CDir::STATE_EXPORTBOUND);
+       for (const auto& bound : bounds) {
+         bound->state_set(CDir::STATE_EXPORTBOUND);
+        }
       }
 
       map<client_t,entity_inst_t> exported_client_map;
@@ -8780,13 +10209,14 @@ void Server::_logged_slave_rename(MDRequestRef& mdr,
                                             exported_client_map,
                                             exported_client_metadata_map);
 
-      for (list<CDir*>::iterator p = bounds.begin(); p != bounds.end(); ++p)
-       (*p)->state_clear(CDir::STATE_EXPORTBOUND);
+      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()->inode.version;
+      reply->inode_export_v = srcdnl->get_inode()->get_version();
     }
 
     // remove mdr auth pin
@@ -8810,11 +10240,11 @@ void Server::_logged_slave_rename(MDRequestRef& mdr,
     mds->balancer->hit_inode(destdnl->get_inode(), META_POP_IWR);
 
   // done.
-  mdr->reset_slave_request();
+  mdr->reset_peer_request();
   mdr->straydn = 0;
 
   if (reply) {
-    mds->send_message_mds(reply, mdr->slave_to_mds);
+    mds->send_message_mds(reply, mdr->peer_to_mds);
   } else {
     ceph_assert(mdr->aborted);
     dout(10) << " abort flag set, finishing" << dendl;
@@ -8822,10 +10252,10 @@ void Server::_logged_slave_rename(MDRequestRef& mdr,
   }
 }
 
-void Server::_commit_slave_rename(MDRequestRef& mdr, int r,
+void Server::_commit_peer_rename(MDRequestRef& mdr, int r,
                                  CDentry *srcdn, CDentry *destdn, CDentry *straydn)
 {
-  dout(10) << "_commit_slave_rename " << *mdr << " r=" << r << dendl;
+  dout(10) << "_commit_peer_rename " << *mdr << " r=" << r << dendl;
 
   CInode *in = destdn->get_linkage()->get_inode();
 
@@ -8858,7 +10288,7 @@ void Server::_commit_slave_rename(MDRequestRef& mdr, int r,
       decode(peer_imported, bp);
 
       dout(10) << " finishing inode export on " << *in << dendl;
-      mdcache->migrator->finish_export_inode(in, mdr->slave_to_mds, peer_imported, finished);
+      mdcache->migrator->finish_export_inode(in, mdr->peer_to_mds, peer_imported, finished);
       mds->queue_waiters(finished);   // this includes SINGLEAUTH waiters.
 
       // unfreeze
@@ -8872,7 +10302,7 @@ void Server::_commit_slave_rename(MDRequestRef& mdr, int r,
       mdr->more()->is_ambiguous_auth = false;
     }
 
-    if (straydn && mdr->more()->slave_update_journaled) {
+    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);
@@ -8881,36 +10311,36 @@ void Server::_commit_slave_rename(MDRequestRef& mdr, int r,
     mds->queue_waiters(finished);
     mdr->cleanup();
 
-    if (mdr->more()->slave_update_journaled) {
+    if (mdr->more()->peer_update_journaled) {
       // write a commit to the journal
-      ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_rename_commit", mdr->reqid,
-                                         mdr->slave_to_mds, ESlaveUpdate::OP_COMMIT,
-                                         ESlaveUpdate::RENAME);
+      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_CommittedSlave(this, mdr), mdr, __func__);
+      submit_mdlog_entry(le, new C_MDS_CommittedPeer(this, mdr), mdr, __func__);
       mdlog->flush();
     } else {
-      _committed_slave(mdr);
+      _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 master, and they failed before we tried prep again.
+    // 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_slave_update(mdr->reqid, mdr->slave_to_mds)) {
-       mdcache->remove_ambiguous_slave_update(mdr->reqid, mdr->slave_to_mds);
-       // rollback but preserve the slave request
-       do_rename_rollback(mdr->more()->rollback_bl, mdr->slave_to_mds, mdr, false);
+      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->slave_to_mds, mdr, true);
+       do_rename_rollback(mdr->more()->rollback_bl, mdr->peer_to_mds, mdr, true);
     } else {
-      dout(10) << " rollback_bl empty, not rollback back rename (master failed after getting extra witnesses?)" << dendl;
+      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())
@@ -8928,24 +10358,23 @@ void Server::_commit_slave_rename(MDRequestRef& mdr, int r,
     mdcache->shutdown_export_stray_finish(migrated_stray);
 }
 
-void _rollback_repair_dir(MutationRef& mut, CDir *dir, rename_rollback::drec &r, utime_t ctime,
-                         bool isdir, int linkunlink, nest_info_t &rstat)
+static void _rollback_repair_dir(MutationRef& mut, CDir *dir,
+                                rename_rollback::drec &r, utime_t ctime,
+                                bool isdir, const nest_info_t &rstat)
 {
-  fnode_t *pf;
-  pf = dir->project_fnode();
-  mut->add_projected_fnode(dir);
+  auto pf = dir->project_fnode(mut);
   pf->version = dir->pre_dirty();
 
   if (isdir) {
-    pf->fragstat.nsubdirs += linkunlink;
+    pf->fragstat.nsubdirs += 1;
   } else {
-    pf->fragstat.nfiles += linkunlink;
+    pf->fragstat.nfiles += 1;
   }    
   if (r.ino) {
-    pf->rstat.rbytes += linkunlink * rstat.rbytes;
-    pf->rstat.rfiles += linkunlink * rstat.rfiles;
-    pf->rstat.rsubdirs += linkunlink * rstat.rsubdirs;
-    pf->rstat.rsnaps += linkunlink * rstat.rsnaps;
+    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;
@@ -8962,11 +10391,11 @@ struct C_MDS_LoggedRenameRollback : public ServerLogContext {
   version_t srcdnpv;
   CDentry *destdn;
   CDentry *straydn;
-  map<client_t,MClientSnap::ref> splits[2];
+  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,MClientSnap::ref> _splits[2], bool f) :
+                            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]);
@@ -8978,7 +10407,7 @@ struct C_MDS_LoggedRenameRollback : public ServerLogContext {
   }
 };
 
-void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef& mdr,
+void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t leader, MDRequestRef& mdr,
                                bool finish_mdr)
 {
   rename_rollback rollback;
@@ -8987,7 +10416,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
 
   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, master);
+  mdcache->add_rollback(rollback.reqid, leader);
 
   MutationRef mut(new MutationImpl(nullptr, utime_t(), rollback.reqid));
   mut->ls = mds->mdlog->get_current_segment();
@@ -9055,7 +10484,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
 
   // can't use is_auth() in the resolve stage
   mds_rank_t whoami = mds->get_nodeid();
-  // slave
+  // peer
   ceph_assert(!destdn || destdn->authority().first != whoami);
   ceph_assert(!straydn || straydn->authority().first != whoami);
 
@@ -9079,23 +10508,31 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
                                    rollback.orig_src.remote_d_type);
   }
 
-  map<client_t,MClientSnap::ref> splits[2];
+  map<client_t,ref_t<MClientSnap>> splits[2];
 
-  CInode::mempool_inode *pip = nullptr;
+  const CInode::mempool_inode *pip = nullptr;
   if (in) {
     bool projected;
-    if (in->get_projected_parent_dn()->authority().first == whoami) {
-      auto &pi = in->project_inode();
-      pip = &pi.inode;
-      mut->add_projected_inode(in);
-      pip->version = in->pre_dirty();
+    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 {
-      pip = in->get_projected_inode();
+      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;
     }
-    if (pip->ctime == rollback.ctime)
-      pip->ctime = rollback.orig_src.old_ctime;
+    pip = in->get_projected_inode().get();
 
     if (rollback.srci_snapbl.length() && in->snaprealm) {
       bool hadrealm;
@@ -9126,12 +10563,6 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     }
   }
 
-  if (srcdn && srcdn->authority().first == whoami) {
-    nest_info_t blah;
-    _rollback_repair_dir(mut, srcdir, rollback.orig_src, rollback.ctime,
-                        in ? in->is_dir() : false, 1, pip ? pip->accounted_rstat : blah);
-  }
-
   // repair dest
   if (destdn) {
     if (rollback.orig_dest.ino && target) {
@@ -9152,17 +10583,22 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
 
   if (target) {
     bool projected;
-    CInode::mempool_inode *ti = nullptr;
-    if (target->get_projected_parent_dn()->authority().first == whoami) {
-      auto &pi = target->project_inode();
-      ti = &pi.inode;
-      mut->add_projected_inode(target);
-      ti->version = target->pre_dirty();
+    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 = target->get_projected_inode();
+      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)) {
@@ -9174,6 +10610,9 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     } else
       ti->nlink++;
 
+    if (!projected)
+      target->reset_inode(ti);
+
     if (rollback.desti_snapbl.length() && target->snaprealm) {
       bool hadrealm;
       auto p = rollback.desti_snapbl.cbegin();
@@ -9203,6 +10642,12 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     }
   }
 
+  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)
@@ -9213,8 +10658,8 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     dout(0) << "  desti back to " << *target << dendl;
   
   // journal it
-  ESlaveUpdate *le = new ESlaveUpdate(mdlog, "slave_rename_rollback", rollback.reqid, master,
-                                     ESlaveUpdate::OP_ROLLBACK, ESlaveUpdate::RENAME);
+  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)) {
@@ -9237,7 +10682,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     le->commit.add_primary_dentry(destdn, 0, true);
   }
 
-  // slave: no need to journal straydn
+  // peer: no need to journal straydn
 
   if (target && target != in && target->authority().first == whoami) {
     ceph_assert(rollback.orig_dest.remote_ino);
@@ -9249,10 +10694,8 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     dout(10) << " noting renamed dir ino " << in->ino() << " in metablob" << dendl;
     le->commit.renamed_dirino = in->ino();
     if (srcdn->authority().first == whoami) {
-      list<CDir*> ls;
-      in->get_dirfrags(ls);
-      for (list<CDir*>::iterator p = ls.begin(); p != ls.end(); ++p) {
-       CDir *dir = *p;
+      auto&& ls = in->get_dirfrags();
+      for (const auto& dir : ls) {
        if (!dir->is_auth())
          le->commit.renamed_dir_frags.push_back(dir->get_frag());
       }
@@ -9273,7 +10716,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
     mdcache->project_subtree_rename(in, destdir, srcdir);
   }
 
-  if (mdr && !mdr->more()->slave_update_journaled) {
+  if (mdr && !mdr->more()->peer_update_journaled) {
     ceph_assert(le->commit.empty());
     mdlog->cancel_entry(le);
     mut->ls = NULL;
@@ -9281,7 +10724,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
   } else {
     ceph_assert(!le->commit.empty());
     if (mdr)
-      mdr->more()->slave_update_journaled = false;
+      mdr->more()->peer_update_journaled = false;
     MDSLogContextBase *fin = new C_MDS_LoggedRenameRollback(this, mut, mdr,
                                                            srcdn, srcdnpv, destdn, straydn,
                                                            splits, finish_mdr);
@@ -9292,7 +10735,7 @@ void Server::do_rename_rollback(bufferlist &rbl, mds_rank_t master, MDRequestRef
 
 void Server::_rename_rollback_finish(MutationRef& mut, MDRequestRef& mdr, CDentry *srcdn,
                                     version_t srcdnpv, CDentry *destdn, CDentry *straydn,
-                                    map<client_t,MClientSnap::ref> splits[2], bool finish_mdr)
+                                    map<client_t,ref_t<MClientSnap>> splits[2], bool finish_mdr)
 {
   dout(10) << "_rename_rollback_finish " << mut->reqid << dendl;
 
@@ -9358,23 +10801,23 @@ void Server::_rename_rollback_finish(MutationRef& mut, MDRequestRef& mdr, CDentr
     if (finish_mdr || mdr->aborted)
       mdcache->request_finish(mdr);
     else
-      mdr->more()->slave_rolling_back = false;
+      mdr->more()->peer_rolling_back = false;
   }
 
-  mdcache->finish_rollback(mut->reqid);
+  mdcache->finish_rollback(mut->reqid, mdr);
 
   mut->cleanup();
 }
 
-void Server::handle_slave_rename_prep_ack(MDRequestRef& mdr, const MMDSSlaveRequest::const_ref &ack)
+void Server::handle_peer_rename_prep_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
 {
-  dout(10) << "handle_slave_rename_prep_ack " << *mdr 
+  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 slave
-  mdr->more()->slaves.insert(from);
+  // 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) {
@@ -9384,11 +10827,11 @@ void Server::handle_slave_rename_prep_ack(MDRequestRef& mdr, const MMDSSlaveRequ
   // witnessed?  or add extra witnesses?
   ceph_assert(mdr->more()->witnessed.count(from) == 0);
   if (ack->is_interrupted()) {
-    dout(10) << " slave request interrupted, noop" << dendl;
+    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_slaves = true;
+      mdr->more()->has_journaled_peers = true;
   } else {
     dout(10) << " extra witnesses (srcdn replicas) are " << ack->witnesses << dendl;
     mdr->more()->extra_witnesses = ack->witnesses;
@@ -9403,47 +10846,47 @@ void Server::handle_slave_rename_prep_ack(MDRequestRef& mdr, const MMDSSlaveRequ
   }
 
   // remove from waiting list
-  ceph_assert(mdr->more()->waiting_on_slave.count(from));
-  mdr->more()->waiting_on_slave.erase(from);
+  ceph_assert(mdr->more()->waiting_on_peer.count(from));
+  mdr->more()->waiting_on_peer.erase(from);
 
-  if (mdr->more()->waiting_on_slave.empty())
+  if (mdr->more()->waiting_on_peer.empty())
     dispatch_client_request(mdr);  // go again!
   else 
-    dout(10) << "still waiting on slaves " << mdr->more()->waiting_on_slave << dendl;
+    dout(10) << "still waiting on peers " << mdr->more()->waiting_on_peer << dendl;
 }
 
-void Server::handle_slave_rename_notify_ack(MDRequestRef& mdr, const MMDSSlaveRequest::const_ref &ack)
+void Server::handle_peer_rename_notify_ack(MDRequestRef& mdr, const cref_t<MMDSPeerRequest> &ack)
 {
-  dout(10) << "handle_slave_rename_notify_ack " << *mdr << " from mds."
+  dout(10) << "handle_peer_rename_notify_ack " << *mdr << " from mds."
           << ack->get_source() << dendl;
-  ceph_assert(mdr->is_slave());
+  ceph_assert(mdr->is_peer());
   mds_rank_t from = mds_rank_t(ack->get_source().num());
 
-  if (mdr->more()->waiting_on_slave.count(from)) {
-    mdr->more()->waiting_on_slave.erase(from);
+  if (mdr->more()->waiting_on_peer.count(from)) {
+    mdr->more()->waiting_on_peer.erase(from);
 
-    if (mdr->more()->waiting_on_slave.empty()) {
-      if (mdr->slave_request)
-       dispatch_slave_request(mdr);
+    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_slave << dendl;
+              << mdr->more()->waiting_on_peer << dendl;
   }
 }
 
-void Server::_slave_rename_sessions_flushed(MDRequestRef& mdr)
+void Server::_peer_rename_sessions_flushed(MDRequestRef& mdr)
 {
-  dout(10) << "_slave_rename_sessions_flushed " << *mdr << dendl;
+  dout(10) << "_peer_rename_sessions_flushed " << *mdr << dendl;
 
-  if (mdr->more()->waiting_on_slave.count(MDS_RANK_NONE)) {
-    mdr->more()->waiting_on_slave.erase(MDS_RANK_NONE);
+  if (mdr->more()->waiting_on_peer.count(MDS_RANK_NONE)) {
+    mdr->more()->waiting_on_peer.erase(MDS_RANK_NONE);
 
-    if (mdr->more()->waiting_on_slave.empty()) {
-      if (mdr->slave_request)
-       dispatch_slave_request(mdr);
+    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_slave << dendl;
+       << mdr->more()->waiting_on_peer << dendl;
   }
 }
 
@@ -9451,28 +10894,21 @@ void Server::_slave_rename_sessions_flushed(MDRequestRef& mdr)
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_lssnap(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
 
   // traverse to path
-  CInode *diri = mdcache->get_inode(req->get_filepath().get_ino());
-  if (!diri || diri->state_test(CInode::STATE_PURGING)) {
-     respond_to_request(mdr, -ESTALE);
-     return;
-  }
-  if (!diri->is_auth()) {
-    mdcache->request_forward(mdr, diri->authority().first);
+  CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+  if (!diri)
     return;
-  }
+
   if (!diri->is_dir()) {
-    respond_to_request(mdr, -ENOTDIR);
+    respond_to_request(mdr, -CEPHFS_ENOTDIR);
     return;
   }
   dout(10) << "lssnap on " << *diri << dendl;
 
   // lock snap
-  MutationImpl::LockOpVec lov;
-  mds->locker->include_snap_rdlocks(diri, lov);
-  if (!mds->locker->acquire_locks(mdr, lov))
+  if (!mds->locker->try_rdlock_snap_layout(diri, mdr))
     return;
 
   if (!check_access(mdr, diri, MAY_READ))
@@ -9521,7 +10957,7 @@ void Server::handle_client_lssnap(MDRequestRef& mdr)
 
     encode(snap_name, dnbl);
     //infinite lease
-    LeaseStat e(-1, -1, 0);
+    LeaseStat e(CEPH_LEASE_VALID, -1, 0);
     mds->locker->encode_lease(dnbl, mdr->session->info, e);
     dout(20) << "encode_infinite_lease" << dendl;
 
@@ -9566,7 +11002,7 @@ struct C_MDS_mksnap_finish : public ServerLogContext {
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_mksnap(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  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));
@@ -9574,29 +11010,24 @@ void Server::handle_client_mksnap(MDRequestRef& mdr)
   }
   if (!mds->mdsmap->allows_snaps()) {
     // you can't make snapshots until you set an option right now
-    respond_to_request(mdr, -EPERM);
-    return;
-  }
-
-  CInode *diri = mdcache->get_inode(req->get_filepath().get_ino());
-  if (!diri || diri->state_test(CInode::STATE_PURGING)) {
-    respond_to_request(mdr, -ESTALE);
+    dout(5) << "new snapshots are disabled for this fs" << dendl;
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
 
-  if (!diri->is_auth()) {    // fw to auth?
-    mdcache->request_forward(mdr, diri->authority().first);
+  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, -ENOTDIR);
+    respond_to_request(mdr, -CEPHFS_ENOTDIR);
     return;
   }
   if (diri->is_system() && !diri->is_root()) {
     // no snaps in system dirs (root is ok)
-    respond_to_request(mdr, -EPERM);
+    dout(5) << "is an internal system dir" << dendl;
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
   
@@ -9604,34 +11035,54 @@ void Server::handle_client_mksnap(MDRequestRef& mdr)
 
   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, -EPERM);
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
   
   dout(10) << "mksnap " << snapname << " on " << *diri << dendl;
 
   // lock snap
-  MutationImpl::LockOpVec lov;
+  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;
+  }
 
-  mds->locker->include_snap_rdlocks(diri, lov);
-  lov.erase_rdlock(&diri->snaplock);
-  lov.add_xlock(&diri->snaplock);
+  if (!check_access(mdr, diri, MAY_WRITE|MAY_SNAPSHOT))
+    return;
 
-  if (!mds->locker->acquire_locks(mdr, lov))
+  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;
+  }
 
-  if (!check_access(mdr, diri, MAY_WRITE|MAY_SNAPSHOT))
+  // 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, -EEXIST);
+    respond_to_request(mdr, -CEPHFS_EEXIST);
     return;
   }
   if (snapname.length() == 0 ||
+      snapname.length() > snapshot_name_max ||
       snapname[0] == '_') {
-    respond_to_request(mdr, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
@@ -9653,19 +11104,32 @@ void Server::handle_client_mksnap(MDRequestRef& mdr)
 
   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(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();
+  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;
@@ -9675,6 +11139,8 @@ void Server::handle_client_mksnap(MDRequestRef& mdr)
     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();
@@ -9698,7 +11164,6 @@ void Server::_mksnap_finish(MDRequestRef& mdr, CInode *diri, SnapInfo &info)
 
   int op = (diri->snaprealm? CEPH_SNAP_OP_CREATE : CEPH_SNAP_OP_SPLIT);
 
-  diri->pop_and_dirty_projected_inode(mdr->ls);
   mdr->apply();
 
   mds->snapclient->commit(mdr->more()->stid, mdr->ls);
@@ -9734,19 +11199,14 @@ struct C_MDS_rmsnap_finish : public ServerLogContext {
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_rmsnap(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
 
-  CInode *diri = mdcache->get_inode(req->get_filepath().get_ino());
-  if (!diri || diri->state_test(CInode::STATE_PURGING)) {
-    respond_to_request(mdr, -ESTALE);
-    return;
-  }
-  if (!diri->is_auth()) {    // fw to auth?
-    mdcache->request_forward(mdr, diri->authority().first);
+  CInode *diri = try_get_auth_inode(mdr, req->get_filepath().get_ino());
+  if (!diri)
     return;
-  }
+
   if (!diri->is_dir()) {
-    respond_to_request(mdr, -ENOTDIR);
+    respond_to_request(mdr, -CEPHFS_ENOTDIR);
     return;
   }
 
@@ -9754,7 +11214,7 @@ void Server::handle_client_rmsnap(MDRequestRef& mdr)
 
   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, -EPERM);
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
 
@@ -9762,23 +11222,26 @@ void Server::handle_client_rmsnap(MDRequestRef& mdr)
 
   // does snap exist?
   if (snapname.length() == 0 || snapname[0] == '_') {
-    respond_to_request(mdr, -EINVAL);   // can't prune a parent snap, currently.
+    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, -ENOENT);
+    respond_to_request(mdr, -CEPHFS_ENOENT);
     return;
   }
   snapid_t snapid = diri->snaprealm->resolve_snapname(snapname, diri->ino());
   dout(10) << " snapname " << snapname << " is " << snapid << dendl;
-
-  MutationImpl::LockOpVec lov;
-  mds->locker->include_snap_rdlocks(diri, lov);
-  lov.erase_rdlock(&diri->snaplock);
-  lov.add_xlock(&diri->snaplock);
-
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
+  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;
@@ -9799,12 +11262,12 @@ void Server::handle_client_rmsnap(MDRequestRef& mdr)
   ceph_assert(mds->snapclient->get_cached_version() >= stid);
 
   // journal
-  auto &pi = diri->project_inode(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--;
+  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");
@@ -9815,6 +11278,8 @@ void Server::handle_client_rmsnap(MDRequestRef& mdr)
   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);
@@ -9830,11 +11295,7 @@ void Server::_rmsnap_finish(MDRequestRef& mdr, CInode *diri, snapid_t snapid)
 {
   dout(10) << "_rmsnap_finish " << *mdr << " " << snapid << dendl;
   snapid_t stid = mdr->more()->stid;
-  auto p = mdr->more()->snapidbl.cbegin();
-  snapid_t seq;
-  decode(seq, p);  
 
-  diri->pop_and_dirty_projected_inode(mdr->ls);
   mdr->apply();
 
   mds->snapclient->commit(stid, mdr->ls);
@@ -9848,11 +11309,12 @@ void Server::_rmsnap_finish(MDRequestRef& mdr, CInode *diri, snapid_t snapid)
 
   // yay
   mdr->in[0] = diri;
+  mdr->tracei = diri;
+  mdr->snapid = snapid;
   respond_to_request(mdr, 0);
 
   // purge snapshot data
-  if (diri->snaprealm->have_past_parents_open())
-    diri->purge_stale_snap_data(diri->snaprealm->get_snaps());
+  diri->purge_stale_snap_data(diri->snaprealm->get_snaps());
 }
 
 struct C_MDS_renamesnap_finish : public ServerLogContext {
@@ -9868,31 +11330,24 @@ struct C_MDS_renamesnap_finish : public ServerLogContext {
 /* This function takes responsibility for the passed mdr*/
 void Server::handle_client_renamesnap(MDRequestRef& mdr)
 {
-  const MClientRequest::const_ref &req = mdr->client_request;
+  const cref_t<MClientRequest> &req = mdr->client_request;
   if (req->get_filepath().get_ino() != req->get_filepath2().get_ino()) {
-    respond_to_request(mdr, -EINVAL);
-    return;
-  }
-
-  CInode *diri = mdcache->get_inode(req->get_filepath().get_ino());
-  if (!diri || diri->state_test(CInode::STATE_PURGING)) {
-    respond_to_request(mdr, -ESTALE);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
 
-  if (!diri->is_auth()) {    // fw to auth?
-    mdcache->request_forward(mdr, diri->authority().first);
+  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, -ENOTDIR);
+    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, -EPERM);
+    respond_to_request(mdr, -CEPHFS_EPERM);
     return;
   }
 
@@ -9901,19 +11356,19 @@ void Server::handle_client_renamesnap(MDRequestRef& mdr)
   dout(10) << "renamesnap " << srcname << "->" << dstname << " on " << *diri << dendl;
 
   if (srcname.length() == 0 || srcname[0] == '_') {
-    respond_to_request(mdr, -EINVAL);   // can't rename a parent snap.
+    respond_to_request(mdr, -CEPHFS_EINVAL);   // can't rename a parent snap.
     return;
   }
   if (!diri->snaprealm || !diri->snaprealm->exists(srcname)) {
-    respond_to_request(mdr, -ENOENT);
+    respond_to_request(mdr, -CEPHFS_ENOENT);
     return;
   }
   if (dstname.length() == 0 || dstname[0] == '_') {
-    respond_to_request(mdr, -EINVAL);
+    respond_to_request(mdr, -CEPHFS_EINVAL);
     return;
   }
   if (diri->snaprealm->exists(dstname)) {
-    respond_to_request(mdr, -EEXIST);
+    respond_to_request(mdr, -CEPHFS_EEXIST);
     return;
   }
 
@@ -9921,14 +11376,17 @@ void Server::handle_client_renamesnap(MDRequestRef& mdr)
   dout(10) << " snapname " << srcname << " is " << snapid << dendl;
 
   // lock snap
-  MutationImpl::LockOpVec lov;
-
-  mds->locker->include_snap_rdlocks(diri, lov);
-  lov.erase_rdlock(&diri->snaplock);
-  lov.add_xlock(&diri->snaplock);
-
-  if (!mds->locker->acquire_locks(mdr, lov))
-    return;
+  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;
@@ -9947,17 +11405,19 @@ void Server::handle_client_renamesnap(MDRequestRef& mdr)
   ceph_assert(mds->snapclient->get_cached_version() >= stid);
 
   // journal
-  auto &pi = diri->project_inode(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();
+  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();
@@ -9979,7 +11439,6 @@ void Server::_renamesnap_finish(MDRequestRef& mdr, CInode *diri, snapid_t snapid
 {
   dout(10) << "_renamesnap_finish " << *mdr << " " << snapid << dendl;
 
-  diri->pop_and_dirty_projected_inode(mdr->ls);
   mdr->apply();
 
   mds->snapclient->commit(mdr->more()->stid, mdr->ls);
@@ -10013,3 +11472,18 @@ void Server::dump_reconnect_status(Formatter *f) const
   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);
+}