]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/common/admin_socket.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / common / admin_socket.cc
index 62c05ce5026b5a468170b95b9b46164c9651f462..c7f21f7b0f206c4b73b2d8abfc4e619c311f0bb4 100644 (file)
@@ -1,4 +1,4 @@
-// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
 /*
  * Ceph - scalable distributed file system
@@ -7,23 +7,24 @@
  *
  * This is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
- * License version 2.1, as published by the Free Software 
+ * License version 2.1, as published by the Free Software
  * Foundation.  See file COPYING.
- * 
+ *
  */
+#include <poll.h>
+#include <sys/un.h>
 
 #include "common/admin_socket.h"
 #include "common/admin_socket_client.h"
+#include "common/dout.h"
 #include "common/errno.h"
 #include "common/safe_io.h"
+#include "common/Thread.h"
 #include "common/version.h"
-#include "include/compat.h"
 
-#include <poll.h>
-#include <sys/un.h>
 
 // re-include our assert to clobber the system one; fix dout:
-#include "include/assert.h"
+#include "include/ceph_assert.h"
 #include "include/compat.h"
 #include "include/sock_compat.h"
 
@@ -42,64 +43,51 @@ using std::ostringstream;
  * This code makes things a little nicer by unlinking those dead sockets when
  * the application exits normally.
  */
-static pthread_mutex_t cleanup_lock = PTHREAD_MUTEX_INITIALIZER;
-static std::vector <const char*> cleanup_files;
+
+template<typename F, typename... Args>
+inline int retry_sys_call(F f, Args... args) {
+  int r;
+  do {
+    r = f(args...);
+  } while (r < 0 && errno == EINTR);
+  return r;
+};
+
+
+static std::mutex cleanup_lock;
+static std::vector<std::string> cleanup_files;
 static bool cleanup_atexit = false;
 
-static void remove_cleanup_file(const char *file)
-{
-  pthread_mutex_lock(&cleanup_lock);
-  VOID_TEMP_FAILURE_RETRY(unlink(file));
-  for (std::vector <const char*>::iterator i = cleanup_files.begin();
-       i != cleanup_files.end(); ++i) {
-    if (strcmp(file, *i) == 0) {
-      free((void*)*i);
-      cleanup_files.erase(i);
-      break;
-    }
+static void remove_cleanup_file(std::string_view file) {
+  std::unique_lock l(cleanup_lock);
+
+  if (auto i = std::find(cleanup_files.cbegin(), cleanup_files.cend(), file);
+      i != cleanup_files.cend()) {
+    retry_sys_call(::unlink, i->c_str());
+    cleanup_files.erase(i);
   }
-  pthread_mutex_unlock(&cleanup_lock);
 }
 
-static void remove_all_cleanup_files()
-{
-  pthread_mutex_lock(&cleanup_lock);
-  for (std::vector <const char*>::iterator i = cleanup_files.begin();
-       i != cleanup_files.end(); ++i) {
-    VOID_TEMP_FAILURE_RETRY(unlink(*i));
-    free((void*)*i);
+void remove_all_cleanup_files() {
+  std::unique_lock l(cleanup_lock);
+  for (const auto& s : cleanup_files) {
+    retry_sys_call(::unlink, s.c_str());
   }
   cleanup_files.clear();
-  pthread_mutex_unlock(&cleanup_lock);
 }
 
-static void add_cleanup_file(const char *file)
-{
-  char *fname = strdup(file);
-  if (!fname)
-    return;
-  pthread_mutex_lock(&cleanup_lock);
-  cleanup_files.push_back(fname);
+static void add_cleanup_file(std::string file) {
+  std::unique_lock l(cleanup_lock);
+  cleanup_files.push_back(std::move(file));
   if (!cleanup_atexit) {
     atexit(remove_all_cleanup_files);
     cleanup_atexit = true;
   }
-  pthread_mutex_unlock(&cleanup_lock);
 }
 
-
 AdminSocket::AdminSocket(CephContext *cct)
-  : m_cct(cct),
-    m_sock_fd(-1),
-    m_shutdown_rd_fd(-1),
-    m_shutdown_wr_fd(-1),
-    in_hook(false),
-    m_lock("AdminSocket::m_lock"),
-    m_version_hook(NULL),
-    m_help_hook(NULL),
-    m_getdescs_hook(NULL)
-{
-}
+  : m_cct(cct)
+{}
 
 AdminSocket::~AdminSocket()
 {
@@ -116,9 +104,6 @@ AdminSocket::~AdminSocket()
  * AdminSocketConfigObs class to join() it.
  */
 
-#define PFL_SUCCESS ((void*)(intptr_t)0)
-#define PFL_FAIL ((void*)(intptr_t)1)
-
 std::string AdminSocket::create_shutdown_pipe(int *pipe_rd, int *pipe_wr)
 {
   int pipefd[2];
@@ -141,7 +126,7 @@ std::string AdminSocket::destroy_shutdown_pipe()
   int ret = safe_write(m_shutdown_wr_fd, buf, sizeof(buf));
 
   // Close write end
-  VOID_TEMP_FAILURE_RETRY(close(m_shutdown_wr_fd));
+  retry_sys_call(::close, m_shutdown_wr_fd);
   m_shutdown_wr_fd = -1;
 
   if (ret != 0) {
@@ -151,11 +136,11 @@ std::string AdminSocket::destroy_shutdown_pipe()
     return oss.str();
   }
 
-  join();
+  th.join();
 
   // Close read end. Doing this before join() blocks the listenter and prevents
   // joining.
-  VOID_TEMP_FAILURE_RETRY(close(m_shutdown_rd_fd));
+  retry_sys_call(::close, m_shutdown_rd_fd);
   m_shutdown_rd_fd = -1;
 
   return "";
@@ -198,7 +183,7 @@ std::string AdminSocket::bind_and_listen(const std::string &sock_path, int *fd)
        err = EEXIST;
       } else {
        ldout(m_cct, 20) << "unlink stale file " << sock_path << dendl;
-       VOID_TEMP_FAILURE_RETRY(unlink(sock_path.c_str()));
+       retry_sys_call(::unlink, sock_path.c_str());
        if (::bind(sock_fd, (struct sockaddr*)&address,
                 sizeof(struct sockaddr_un)) == 0) {
          err = 0;
@@ -222,14 +207,14 @@ std::string AdminSocket::bind_and_listen(const std::string &sock_path, int *fd)
     oss << "AdminSocket::bind_and_listen: "
          << "failed to listen to socket: " << cpp_strerror(err);
     close(sock_fd);
-    VOID_TEMP_FAILURE_RETRY(unlink(sock_path.c_str()));
+    retry_sys_call(::unlink, sock_path.c_str());
     return oss.str();
   }
   *fd = sock_fd;
   return "";
 }
 
-void* AdminSocket::entry()
+void AdminSocket::entry() noexcept
 {
   ldout(m_cct, 5) << "entry start" << dendl;
   while (true) {
@@ -248,7 +233,7 @@ void* AdminSocket::entry()
       }
       lderr(m_cct) << "AdminSocket: poll(2) error: '"
                   << cpp_strerror(err) << dendl;
-      return PFL_FAIL;
+      return;
     }
 
     if (fds[0].revents & POLLIN) {
@@ -257,7 +242,7 @@ void* AdminSocket::entry()
     }
     if (fds[1].revents & POLLIN) {
       // Parent wants us to shut down
-      return PFL_SUCCESS;
+      return;
     }
   }
   ldout(m_cct, 5) << "entry exit" << dendl;
@@ -312,10 +297,9 @@ bool AdminSocket::do_accept()
         lderr(m_cct) << "AdminSocket: error reading request code: "
                     << cpp_strerror(ret) << dendl;
       }
-      VOID_TEMP_FAILURE_RETRY(close(connection_fd));
+      retry_sys_call(::close, connection_fd);
       return false;
     }
-    //ldout(m_cct, 0) << "AdminSocket read byte " << (int)cmd[pos] << " pos " << pos << dendl;
     if (cmd[0] == '\0') {
       // old protocol: __be32
       if (pos == 3 && cmd[0] == '\0') {
@@ -345,37 +329,42 @@ bool AdminSocket::do_accept()
     }
     if (++pos >= sizeof(cmd)) {
       lderr(m_cct) << "AdminSocket: error reading request too long" << dendl;
-      VOID_TEMP_FAILURE_RETRY(close(connection_fd));
+      retry_sys_call(::close, connection_fd);
       return false;
     }
   }
 
   bool rval = false;
 
-  map<string, cmd_vartype> cmdmap;
+  cmdmap_t cmdmap;
   string format;
   vector<string> cmdvec;
   stringstream errss;
   cmdvec.push_back(cmd);
   if (!cmdmap_from_json(cmdvec, &cmdmap, errss)) {
     ldout(m_cct, 0) << "AdminSocket: " << errss.str() << dendl;
-    VOID_TEMP_FAILURE_RETRY(close(connection_fd));
+    retry_sys_call(::close, connection_fd);
+    return false;
+  }
+  try {
+    cmd_getval(m_cct, cmdmap, "format", format);
+    cmd_getval(m_cct, cmdmap, "prefix", c);
+  } catch (const bad_cmd_get& e) {
+    retry_sys_call(::close, connection_fd);
     return false;
   }
-  cmd_getval(m_cct, cmdmap, "format", format);
   if (format != "json" && format != "json-pretty" &&
       format != "xml" && format != "xml-pretty")
     format = "json-pretty";
-  cmd_getval(m_cct, cmdmap, "prefix", c);
 
-  m_lock.Lock();
-  map<string,AdminSocketHook*>::iterator p;
+  std::unique_lock l(lock);
+  decltype(hooks)::iterator p;
   string match = c;
   while (match.size()) {
-    p = m_hooks.find(match);
-    if (p != m_hooks.end())
+    p = hooks.find(match);
+    if (p != hooks.cend())
       break;
-    
+
     // drop right-most word
     size_t pos = match.rfind(' ');
     if (pos == std::string::npos) {
@@ -387,7 +376,7 @@ bool AdminSocket::do_accept()
   }
 
   bufferlist out;
-  if (p == m_hooks.end()) {
+  if (p == hooks.cend()) {
     lderr(m_cct) << "AdminSocket: request '" << c << "' not defined" << dendl;
   } else {
     string args;
@@ -400,20 +389,21 @@ bool AdminSocket::do_accept()
     // and set in_hook to allow unregister to wait for us before
     // removing this hook.
     in_hook = true;
-    auto match_hook = p->second;
-    m_lock.Unlock();
-    bool success = match_hook->call(match, cmdmap, format, out);
-    m_lock.Lock();
+    auto match_hook = p->second.hook;
+    l.unlock();
+    bool success = (validate(match, cmdmap, out) &&
+                    match_hook->call(match, cmdmap, format, out));
+    l.lock();
     in_hook = false;
-    in_hook_cond.Signal();
+    in_hook_cond.notify_all();
 
     if (!success) {
       ldout(m_cct, 0) << "AdminSocket: request '" << match << "' args '" << args
-                     << "' to " << p->second << " failed" << dendl;
+                     << "' to " << match_hook << " failed" << dendl;
       out.append("failed");
     } else {
       ldout(m_cct, 5) << "AdminSocket: request '" << match << "' '" << args
-                      << "' to " << p->second
+                      << "' to " << match_hook
                       << " returned " << out.length() << " bytes" << dendl;
     }
     uint32_t len = htonl(out.length());
@@ -426,61 +416,97 @@ bool AdminSocket::do_accept()
        rval = true;
     }
   }
-  m_lock.Unlock();
+  l.unlock();
 
-  VOID_TEMP_FAILURE_RETRY(close(connection_fd));
+  retry_sys_call(::close, connection_fd);
   return rval;
 }
 
-int AdminSocket::register_command(std::string command, std::string cmddesc, AdminSocketHook *hook, std::string help)
+bool AdminSocket::validate(const std::string& command,
+                          const cmdmap_t& cmdmap,
+                          bufferlist& out) const
+{
+  stringstream os;
+  if (validate_cmd(m_cct, hooks.at(command).desc, cmdmap, os)) {
+    return true;
+  } else {
+    out.append(os);
+    return false;
+  }
+}
+
+int AdminSocket::register_command(std::string_view command,
+                                 std::string_view cmddesc,
+                                 AdminSocketHook *hook,
+                                 std::string_view help)
 {
   int ret;
-  m_lock.Lock();
-  if (m_hooks.count(command)) {
-    ldout(m_cct, 5) << "register_command " << command << " hook " << hook << " EEXIST" << dendl;
+  std::unique_lock l(lock);
+  auto i = hooks.find(command);
+  if (i != hooks.cend()) {
+    ldout(m_cct, 5) << "register_command " << command << " hook " << hook
+                   << " EEXIST" << dendl;
     ret = -EEXIST;
   } else {
-    ldout(m_cct, 5) << "register_command " << command << " hook " << hook << dendl;
-    m_hooks[command] = hook;
-    m_descs[command] = cmddesc;
-    m_help[command] = help;
+    ldout(m_cct, 5) << "register_command " << command << " hook " << hook
+                   << dendl;
+    hooks.emplace_hint(i,
+                      std::piecewise_construct,
+                      std::forward_as_tuple(command),
+                      std::forward_as_tuple(hook, cmddesc, help));
     ret = 0;
-  }  
-  m_lock.Unlock();
+  }
   return ret;
 }
 
-int AdminSocket::unregister_command(std::string command)
+int AdminSocket::unregister_command(std::string_view command)
 {
   int ret;
-  m_lock.Lock();
-  if (m_hooks.count(command)) {
+  std::unique_lock l(lock);
+  auto i = hooks.find(command);
+  if (i != hooks.cend()) {
     ldout(m_cct, 5) << "unregister_command " << command << dendl;
-    m_hooks.erase(command);
-    m_descs.erase(command);
-    m_help.erase(command);
 
     // If we are currently processing a command, wait for it to
     // complete in case it referenced the hook that we are
     // unregistering.
-    if (in_hook) {
-      in_hook_cond.Wait(m_lock);
-    }
+    in_hook_cond.wait(l, [this]() { return !in_hook; });
+
+    hooks.erase(i);
+
 
     ret = 0;
   } else {
     ldout(m_cct, 5) << "unregister_command " << command << " ENOENT" << dendl;
     ret = -ENOENT;
-  }  
-  m_lock.Unlock();
+  }
   return ret;
 }
 
+void AdminSocket::unregister_commands(const AdminSocketHook *hook)
+{
+  std::unique_lock l(lock);
+  auto i = hooks.begin();
+  while (i != hooks.end()) {
+    if (i->second.hook == hook) {
+      ldout(m_cct, 5) << __func__ << " " << i->first << dendl;
+
+      // If we are currently processing a command, wait for it to
+      // complete in case it referenced the hook that we are
+      // unregistering.
+      in_hook_cond.wait(l, [this]() { return !in_hook; });
+      hooks.erase(i++);
+    } else {
+      i++;
+    }
+  }
+}
+
 class VersionHook : public AdminSocketHook {
 public:
-  bool call(std::string command, cmdmap_t &cmdmap, std::string format,
-                   bufferlist& out) override {
-    if (command == "0") {
+  bool call(std::string_view command, const cmdmap_t& cmdmap,
+           std::string_view format, bufferlist& out) override {
+    if (command == "0"sv) {
       out.append(CEPH_ADMIN_SOCK_VERSION);
     } else {
       JSONFormatter jf;
@@ -494,6 +520,7 @@ public:
       }
       ostringstream ss;
       jf.close_section();
+      jf.enable_line_break();
       jf.flush(ss);
       out.append(ss.str());
     }
@@ -505,20 +532,20 @@ class HelpHook : public AdminSocketHook {
   AdminSocket *m_as;
 public:
   explicit HelpHook(AdminSocket *as) : m_as(as) {}
-  bool call(string command, cmdmap_t &cmdmap, string format, bufferlist& out) override {
-    Formatter *f = Formatter::create(format, "json-pretty", "json-pretty");
+  bool call(std::string_view command, const cmdmap_t& cmdmap,
+           std::string_view format,
+           bufferlist& out) override {
+    std::unique_ptr<Formatter> f(Formatter::create(format, "json-pretty"sv,
+                                                  "json-pretty"sv));
     f->open_object_section("help");
-    for (map<string,string>::iterator p = m_as->m_help.begin();
-        p != m_as->m_help.end();
-        ++p) {
-      if (p->second.length())
-       f->dump_string(p->first.c_str(), p->second);
+    for (const auto& [command, info] : m_as->hooks) {
+      if (info.help.length())
+       f->dump_string(command.c_str(), info.help);
     }
     f->close_section();
     ostringstream ss;
     f->flush(ss);
     out.append(ss.str());
-    delete f;
     return true;
   }
 };
@@ -527,19 +554,22 @@ class GetdescsHook : public AdminSocketHook {
   AdminSocket *m_as;
 public:
   explicit GetdescsHook(AdminSocket *as) : m_as(as) {}
-  bool call(string command, cmdmap_t &cmdmap, string format, bufferlist& out) override {
+  bool call(std::string_view command, const cmdmap_t& cmdmap,
+           std::string_view format, bufferlist& out) override {
     int cmdnum = 0;
     JSONFormatter jf;
     jf.open_object_section("command_descriptions");
-    for (map<string,string>::iterator p = m_as->m_descs.begin();
-        p != m_as->m_descs.end();
-        ++p) {
+    for (const auto& [command, info] : m_as->hooks) {
+      // GCC 8 actually has [[maybe_unused]] on a structured binding
+      // do what you'd expect. GCC 7 does not.
+      (void)command;
       ostringstream secname;
       secname << "cmd" << setfill('0') << std::setw(3) << cmdnum;
       dump_cmd_and_help_to_json(&jf,
+                                CEPH_FEATURES_ALL,
                                secname.str().c_str(),
-                               p->second.c_str(),
-                               m_as->m_help[p->first]);
+                               info.desc,
+                               info.help);
       cmdnum++;
     }
     jf.close_section(); // command_descriptions
@@ -551,7 +581,7 @@ public:
   }
 };
 
-bool AdminSocket::init(const std::string &path)
+bool AdminSocket::init(const std::stringpath)
 {
   ldout(m_cct, 5) << "init " << path << dendl;
 
@@ -578,25 +608,25 @@ bool AdminSocket::init(const std::string &path)
   m_shutdown_wr_fd = pipe_wr;
   m_path = path;
 
-  m_version_hook = new VersionHook;
-  register_command("0", "0", m_version_hook, "");
-  register_command("version", "version", m_version_hook, "get ceph version");
-  register_command("git_version", "git_version", m_version_hook, "get git sha1");
-  m_help_hook = new HelpHook(this);
-  register_command("help", "help", m_help_hook, "list available commands");
-  m_getdescs_hook = new GetdescsHook(this);
+  version_hook = std::make_unique<VersionHook>();
+  register_command("0", "0", version_hook.get(), "");
+  register_command("version", "version", version_hook.get(), "get ceph version");
+  register_command("git_version", "git_version", version_hook.get(),
+                  "get git sha1");
+  help_hook = std::make_unique<HelpHook>(this);
+  register_command("help", "help", help_hook.get(),
+                  "list available commands");
+  getdescs_hook = std::make_unique<GetdescsHook>(this);
   register_command("get_command_descriptions", "get_command_descriptions",
-                  m_getdescs_hook, "list available commands");
+                  getdescs_hook.get(), "list available commands");
 
-  create("admin_socket");
+  th = make_named_thread("admin_socket", &AdminSocket::entry, this);
   add_cleanup_file(m_path.c_str());
   return true;
 }
 
 void AdminSocket::shutdown()
 {
-  std::string err;
-
   // Under normal operation this is unlikely to occur.  However for some unit
   // tests, some object members are not initialized and so cannot be deleted
   // without fault.
@@ -605,24 +635,22 @@ void AdminSocket::shutdown()
 
   ldout(m_cct, 5) << "shutdown" << dendl;
 
-  err = destroy_shutdown_pipe();
+  auto err = destroy_shutdown_pipe();
   if (!err.empty()) {
     lderr(m_cct) << "AdminSocket::shutdown: error: " << err << dendl;
   }
 
-  VOID_TEMP_FAILURE_RETRY(close(m_sock_fd));
+  retry_sys_call(::close, m_sock_fd);
 
-  unregister_command("version");
-  unregister_command("git_version");
-  unregister_command("0");
-  delete m_version_hook;
+  unregister_commands(version_hook.get());
+  version_hook.reset();
 
   unregister_command("help");
-  delete m_help_hook;
+  help_hook.reset();
 
   unregister_command("get_command_descriptions");
-  delete m_getdescs_hook;
+  getdescs_hook.reset();
 
-  remove_cleanup_file(m_path.c_str());
+  remove_cleanup_file(m_path);
   m_path.clear();
 }