]>
git.proxmox.com Git - ceph.git/blob - ceph/src/common/admin_socket.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2011 New Dream Network
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
17 #include "common/admin_socket.h"
18 #include "common/admin_socket_client.h"
19 #include "common/dout.h"
20 #include "common/errno.h"
21 #include "common/safe_io.h"
22 #include "common/Thread.h"
23 #include "common/version.h"
26 // re-include our assert to clobber the system one; fix dout:
27 #include "include/ceph_assert.h"
28 #include "include/compat.h"
29 #include "include/sock_compat.h"
31 #define dout_subsys ceph_subsys_asok
33 #define dout_prefix *_dout << "asok(" << (void*)m_cct << ") "
36 using std::ostringstream
;
39 * UNIX domain sockets created by an application persist even after that
40 * application closes, unless they're explicitly unlinked. This is because the
41 * directory containing the socket keeps a reference to the socket.
43 * This code makes things a little nicer by unlinking those dead sockets when
44 * the application exits normally.
47 template<typename F
, typename
... Args
>
48 inline int retry_sys_call(F f
, Args
... args
) {
52 } while (r
< 0 && errno
== EINTR
);
57 static std::mutex cleanup_lock
;
58 static std::vector
<std::string
> cleanup_files
;
59 static bool cleanup_atexit
= false;
61 static void remove_cleanup_file(std::string_view file
) {
62 std::unique_lock
l(cleanup_lock
);
64 if (auto i
= std::find(cleanup_files
.cbegin(), cleanup_files
.cend(), file
);
65 i
!= cleanup_files
.cend()) {
66 retry_sys_call(::unlink
, i
->c_str());
67 cleanup_files
.erase(i
);
71 void remove_all_cleanup_files() {
72 std::unique_lock
l(cleanup_lock
);
73 for (const auto& s
: cleanup_files
) {
74 retry_sys_call(::unlink
, s
.c_str());
76 cleanup_files
.clear();
79 static void add_cleanup_file(std::string file
) {
80 std::unique_lock
l(cleanup_lock
);
81 cleanup_files
.push_back(std::move(file
));
82 if (!cleanup_atexit
) {
83 atexit(remove_all_cleanup_files
);
84 cleanup_atexit
= true;
88 AdminSocket::AdminSocket(CephContext
*cct
)
92 AdminSocket::~AdminSocket()
98 * This thread listens on the UNIX domain socket for incoming connections.
99 * It only handles one connection at a time at the moment. All I/O is nonblocking,
100 * so that we can implement sensible timeouts. [TODO: make all I/O nonblocking]
102 * This thread also listens to m_shutdown_rd_fd. If there is any data sent to this
103 * pipe, the thread terminates itself gracefully, allowing the
104 * AdminSocketConfigObs class to join() it.
107 std::string
AdminSocket::create_shutdown_pipe(int *pipe_rd
, int *pipe_wr
)
110 if (pipe_cloexec(pipefd
) < 0) {
113 oss
<< "AdminSocket::create_shutdown_pipe error: " << cpp_strerror(e
);
117 *pipe_rd
= pipefd
[0];
118 *pipe_wr
= pipefd
[1];
122 std::string
AdminSocket::destroy_shutdown_pipe()
124 // Send a byte to the shutdown pipe that the thread is listening to
125 char buf
[1] = { 0x0 };
126 int ret
= safe_write(m_shutdown_wr_fd
, buf
, sizeof(buf
));
129 retry_sys_call(::close
, m_shutdown_wr_fd
);
130 m_shutdown_wr_fd
= -1;
134 oss
<< "AdminSocket::destroy_shutdown_pipe error: failed to write"
135 "to thread shutdown pipe: error " << ret
;
141 // Close read end. Doing this before join() blocks the listenter and prevents
143 retry_sys_call(::close
, m_shutdown_rd_fd
);
144 m_shutdown_rd_fd
= -1;
149 std::string
AdminSocket::bind_and_listen(const std::string
&sock_path
, int *fd
)
151 ldout(m_cct
, 5) << "bind_and_listen " << sock_path
<< dendl
;
153 struct sockaddr_un address
;
154 if (sock_path
.size() > sizeof(address
.sun_path
) - 1) {
156 oss
<< "AdminSocket::bind_and_listen: "
157 << "The UNIX domain socket path " << sock_path
<< " is too long! The "
158 << "maximum length on this system is "
159 << (sizeof(address
.sun_path
) - 1);
162 int sock_fd
= socket_cloexec(PF_UNIX
, SOCK_STREAM
, 0);
166 oss
<< "AdminSocket::bind_and_listen: "
167 << "failed to create socket: " << cpp_strerror(err
);
170 memset(&address
, 0, sizeof(struct sockaddr_un
));
171 address
.sun_family
= AF_UNIX
;
172 snprintf(address
.sun_path
, sizeof(address
.sun_path
),
173 "%s", sock_path
.c_str());
174 if (::bind(sock_fd
, (struct sockaddr
*)&address
,
175 sizeof(struct sockaddr_un
)) != 0) {
177 if (err
== EADDRINUSE
) {
178 AdminSocketClient
client(sock_path
);
182 ldout(m_cct
, 20) << "socket " << sock_path
<< " is in use" << dendl
;
185 ldout(m_cct
, 20) << "unlink stale file " << sock_path
<< dendl
;
186 retry_sys_call(::unlink
, sock_path
.c_str());
187 if (::bind(sock_fd
, (struct sockaddr
*)&address
,
188 sizeof(struct sockaddr_un
)) == 0) {
197 oss
<< "AdminSocket::bind_and_listen: "
198 << "failed to bind the UNIX domain socket to '" << sock_path
199 << "': " << cpp_strerror(err
);
204 if (listen(sock_fd
, 5) != 0) {
207 oss
<< "AdminSocket::bind_and_listen: "
208 << "failed to listen to socket: " << cpp_strerror(err
);
210 retry_sys_call(::unlink
, sock_path
.c_str());
217 void AdminSocket::entry() noexcept
219 ldout(m_cct
, 5) << "entry start" << dendl
;
221 struct pollfd fds
[2];
222 memset(fds
, 0, sizeof(fds
));
223 fds
[0].fd
= m_sock_fd
;
224 fds
[0].events
= POLLIN
| POLLRDBAND
;
225 fds
[1].fd
= m_shutdown_rd_fd
;
226 fds
[1].events
= POLLIN
| POLLRDBAND
;
228 int ret
= poll(fds
, 2, -1);
234 lderr(m_cct
) << "AdminSocket: poll(2) error: '"
235 << cpp_strerror(err
) << dendl
;
239 if (fds
[0].revents
& POLLIN
) {
240 // Send out some data
243 if (fds
[1].revents
& POLLIN
) {
244 // Parent wants us to shut down
248 ldout(m_cct
, 5) << "entry exit" << dendl
;
251 void AdminSocket::chown(uid_t uid
, gid_t gid
)
253 if (m_sock_fd
>= 0) {
254 int r
= ::chown(m_path
.c_str(), uid
, gid
);
257 lderr(m_cct
) << "AdminSocket: failed to chown socket: "
258 << cpp_strerror(r
) << dendl
;
263 void AdminSocket::chmod(mode_t mode
)
265 if (m_sock_fd
>= 0) {
266 int r
= ::chmod(m_path
.c_str(), mode
);
269 lderr(m_cct
) << "AdminSocket: failed to chmod socket: "
270 << cpp_strerror(r
) << dendl
;
275 bool AdminSocket::do_accept()
277 struct sockaddr_un address
;
278 socklen_t address_length
= sizeof(address
);
279 ldout(m_cct
, 30) << "AdminSocket: calling accept" << dendl
;
280 int connection_fd
= accept_cloexec(m_sock_fd
, (struct sockaddr
*) &address
,
282 if (connection_fd
< 0) {
284 lderr(m_cct
) << "AdminSocket: do_accept error: '"
285 << cpp_strerror(err
) << dendl
;
288 ldout(m_cct
, 30) << "AdminSocket: finished accept" << dendl
;
294 int ret
= safe_read(connection_fd
, &cmd
[pos
], 1);
297 lderr(m_cct
) << "AdminSocket: error reading request code: "
298 << cpp_strerror(ret
) << dendl
;
300 retry_sys_call(::close
, connection_fd
);
303 if (cmd
[0] == '\0') {
304 // old protocol: __be32
305 if (pos
== 3 && cmd
[0] == '\0') {
311 c
= "perfcounters_dump";
314 c
= "perfcounters_schema";
323 // new protocol: null or \n terminated string
324 if (cmd
[pos
] == '\n' || cmd
[pos
] == '\0') {
330 if (++pos
>= sizeof(cmd
)) {
331 lderr(m_cct
) << "AdminSocket: error reading request too long" << dendl
;
332 retry_sys_call(::close
, connection_fd
);
341 vector
<string
> cmdvec
;
343 cmdvec
.push_back(cmd
);
344 if (!cmdmap_from_json(cmdvec
, &cmdmap
, errss
)) {
345 ldout(m_cct
, 0) << "AdminSocket: " << errss
.str() << dendl
;
346 retry_sys_call(::close
, connection_fd
);
350 cmd_getval(m_cct
, cmdmap
, "format", format
);
351 cmd_getval(m_cct
, cmdmap
, "prefix", c
);
352 } catch (const bad_cmd_get
& e
) {
353 retry_sys_call(::close
, connection_fd
);
356 if (format
!= "json" && format
!= "json-pretty" &&
357 format
!= "xml" && format
!= "xml-pretty")
358 format
= "json-pretty";
360 std::unique_lock
l(lock
);
361 decltype(hooks
)::iterator p
;
363 while (match
.size()) {
364 p
= hooks
.find(match
);
365 if (p
!= hooks
.cend())
368 // drop right-most word
369 size_t pos
= match
.rfind(' ');
370 if (pos
== std::string::npos
) {
371 match
.clear(); // we fail
379 if (p
== hooks
.cend()) {
380 lderr(m_cct
) << "AdminSocket: request '" << c
<< "' not defined" << dendl
;
384 args
= c
.substr(match
.length() + 1);
387 // Drop lock to avoid cycles in cases where the hook takes
388 // the same lock that was held during calls to register/unregister,
389 // and set in_hook to allow unregister to wait for us before
390 // removing this hook.
392 auto match_hook
= p
->second
.hook
;
394 bool success
= (validate(match
, cmdmap
, out
) &&
395 match_hook
->call(match
, cmdmap
, format
, out
));
398 in_hook_cond
.notify_all();
401 ldout(m_cct
, 0) << "AdminSocket: request '" << match
<< "' args '" << args
402 << "' to " << match_hook
<< " failed" << dendl
;
403 out
.append("failed");
405 ldout(m_cct
, 5) << "AdminSocket: request '" << match
<< "' '" << args
406 << "' to " << match_hook
407 << " returned " << out
.length() << " bytes" << dendl
;
409 uint32_t len
= htonl(out
.length());
410 int ret
= safe_write(connection_fd
, &len
, sizeof(len
));
412 lderr(m_cct
) << "AdminSocket: error writing response length "
413 << cpp_strerror(ret
) << dendl
;
415 if (out
.write_fd(connection_fd
) >= 0)
421 retry_sys_call(::close
, connection_fd
);
425 bool AdminSocket::validate(const std::string
& command
,
426 const cmdmap_t
& cmdmap
,
427 bufferlist
& out
) const
430 if (validate_cmd(m_cct
, hooks
.at(command
).desc
, cmdmap
, os
)) {
438 int AdminSocket::register_command(std::string_view command
,
439 std::string_view cmddesc
,
440 AdminSocketHook
*hook
,
441 std::string_view help
)
444 std::unique_lock
l(lock
);
445 auto i
= hooks
.find(command
);
446 if (i
!= hooks
.cend()) {
447 ldout(m_cct
, 5) << "register_command " << command
<< " hook " << hook
448 << " EEXIST" << dendl
;
451 ldout(m_cct
, 5) << "register_command " << command
<< " hook " << hook
453 hooks
.emplace_hint(i
,
454 std::piecewise_construct
,
455 std::forward_as_tuple(command
),
456 std::forward_as_tuple(hook
, cmddesc
, help
));
462 int AdminSocket::unregister_command(std::string_view command
)
465 std::unique_lock
l(lock
);
466 auto i
= hooks
.find(command
);
467 if (i
!= hooks
.cend()) {
468 ldout(m_cct
, 5) << "unregister_command " << command
<< dendl
;
470 // If we are currently processing a command, wait for it to
471 // complete in case it referenced the hook that we are
473 in_hook_cond
.wait(l
, [this]() { return !in_hook
; });
480 ldout(m_cct
, 5) << "unregister_command " << command
<< " ENOENT" << dendl
;
486 void AdminSocket::unregister_commands(const AdminSocketHook
*hook
)
488 std::unique_lock
l(lock
);
489 auto i
= hooks
.begin();
490 while (i
!= hooks
.end()) {
491 if (i
->second
.hook
== hook
) {
492 ldout(m_cct
, 5) << __func__
<< " " << i
->first
<< dendl
;
494 // If we are currently processing a command, wait for it to
495 // complete in case it referenced the hook that we are
497 in_hook_cond
.wait(l
, [this]() { return !in_hook
; });
505 class VersionHook
: public AdminSocketHook
{
507 bool call(std::string_view command
, const cmdmap_t
& cmdmap
,
508 std::string_view format
, bufferlist
& out
) override
{
509 if (command
== "0"sv
) {
510 out
.append(CEPH_ADMIN_SOCK_VERSION
);
513 jf
.open_object_section("version");
514 if (command
== "version") {
515 jf
.dump_string("version", ceph_version_to_str());
516 jf
.dump_string("release", ceph_release_name(ceph_release()));
517 jf
.dump_string("release_type", ceph_release_type());
518 } else if (command
== "git_version") {
519 jf
.dump_string("git_version", git_version_to_str());
523 jf
.enable_line_break();
525 out
.append(ss
.str());
531 class HelpHook
: public AdminSocketHook
{
534 explicit HelpHook(AdminSocket
*as
) : m_as(as
) {}
535 bool call(std::string_view command
, const cmdmap_t
& cmdmap
,
536 std::string_view format
,
537 bufferlist
& out
) override
{
538 std::unique_ptr
<Formatter
> f(Formatter::create(format
, "json-pretty"sv
,
540 f
->open_object_section("help");
541 for (const auto& [command
, info
] : m_as
->hooks
) {
542 if (info
.help
.length())
543 f
->dump_string(command
.c_str(), info
.help
);
548 out
.append(ss
.str());
553 class GetdescsHook
: public AdminSocketHook
{
556 explicit GetdescsHook(AdminSocket
*as
) : m_as(as
) {}
557 bool call(std::string_view command
, const cmdmap_t
& cmdmap
,
558 std::string_view format
, bufferlist
& out
) override
{
561 jf
.open_object_section("command_descriptions");
562 for (const auto& [command
, info
] : m_as
->hooks
) {
563 // GCC 8 actually has [[maybe_unused]] on a structured binding
564 // do what you'd expect. GCC 7 does not.
566 ostringstream secname
;
567 secname
<< "cmd" << setfill('0') << std::setw(3) << cmdnum
;
568 dump_cmd_and_help_to_json(&jf
,
570 secname
.str().c_str(),
575 jf
.close_section(); // command_descriptions
576 jf
.enable_line_break();
579 out
.append(ss
.str());
584 bool AdminSocket::init(const std::string
& path
)
586 ldout(m_cct
, 5) << "init " << path
<< dendl
;
588 /* Set up things for the new thread */
590 int pipe_rd
= -1, pipe_wr
= -1;
591 err
= create_shutdown_pipe(&pipe_rd
, &pipe_wr
);
593 lderr(m_cct
) << "AdminSocketConfigObs::init: error: " << err
<< dendl
;
597 err
= bind_and_listen(path
, &sock_fd
);
599 lderr(m_cct
) << "AdminSocketConfigObs::init: failed: " << err
<< dendl
;
605 /* Create new thread */
607 m_shutdown_rd_fd
= pipe_rd
;
608 m_shutdown_wr_fd
= pipe_wr
;
611 version_hook
= std::make_unique
<VersionHook
>();
612 register_command("0", "0", version_hook
.get(), "");
613 register_command("version", "version", version_hook
.get(), "get ceph version");
614 register_command("git_version", "git_version", version_hook
.get(),
616 help_hook
= std::make_unique
<HelpHook
>(this);
617 register_command("help", "help", help_hook
.get(),
618 "list available commands");
619 getdescs_hook
= std::make_unique
<GetdescsHook
>(this);
620 register_command("get_command_descriptions", "get_command_descriptions",
621 getdescs_hook
.get(), "list available commands");
623 th
= make_named_thread("admin_socket", &AdminSocket::entry
, this);
624 add_cleanup_file(m_path
.c_str());
628 void AdminSocket::shutdown()
630 // Under normal operation this is unlikely to occur. However for some unit
631 // tests, some object members are not initialized and so cannot be deleted
633 if (m_shutdown_wr_fd
< 0)
636 ldout(m_cct
, 5) << "shutdown" << dendl
;
638 auto err
= destroy_shutdown_pipe();
640 lderr(m_cct
) << "AdminSocket::shutdown: error: " << err
<< dendl
;
643 retry_sys_call(::close
, m_sock_fd
);
645 unregister_commands(version_hook
.get());
646 version_hook
.reset();
648 unregister_command("help");
651 unregister_command("get_command_descriptions");
652 getdescs_hook
.reset();
654 remove_cleanup_file(m_path
);