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.
15 #include "common/admin_socket.h"
16 #include "common/admin_socket_client.h"
17 #include "common/errno.h"
18 #include "common/pipe.h"
19 #include "common/safe_io.h"
20 #include "common/version.h"
21 #include "include/compat.h"
26 // re-include our assert to clobber the system one; fix dout:
27 #include "include/assert.h"
29 #define dout_subsys ceph_subsys_asok
31 #define dout_prefix *_dout << "asok(" << (void*)m_cct << ") "
34 using std::ostringstream
;
37 * UNIX domain sockets created by an application persist even after that
38 * application closes, unless they're explicitly unlinked. This is because the
39 * directory containing the socket keeps a reference to the socket.
41 * This code makes things a little nicer by unlinking those dead sockets when
42 * the application exits normally.
44 static pthread_mutex_t cleanup_lock
= PTHREAD_MUTEX_INITIALIZER
;
45 static std::vector
<const char*> cleanup_files
;
46 static bool cleanup_atexit
= false;
48 static void remove_cleanup_file(const char *file
)
50 pthread_mutex_lock(&cleanup_lock
);
51 VOID_TEMP_FAILURE_RETRY(unlink(file
));
52 for (std::vector
<const char*>::iterator i
= cleanup_files
.begin();
53 i
!= cleanup_files
.end(); ++i
) {
54 if (strcmp(file
, *i
) == 0) {
56 cleanup_files
.erase(i
);
60 pthread_mutex_unlock(&cleanup_lock
);
63 static void remove_all_cleanup_files()
65 pthread_mutex_lock(&cleanup_lock
);
66 for (std::vector
<const char*>::iterator i
= cleanup_files
.begin();
67 i
!= cleanup_files
.end(); ++i
) {
68 VOID_TEMP_FAILURE_RETRY(unlink(*i
));
71 cleanup_files
.clear();
72 pthread_mutex_unlock(&cleanup_lock
);
75 static void add_cleanup_file(const char *file
)
77 char *fname
= strdup(file
);
80 pthread_mutex_lock(&cleanup_lock
);
81 cleanup_files
.push_back(fname
);
82 if (!cleanup_atexit
) {
83 atexit(remove_all_cleanup_files
);
84 cleanup_atexit
= true;
86 pthread_mutex_unlock(&cleanup_lock
);
90 AdminSocket::AdminSocket(CephContext
*cct
)
96 m_lock("AdminSocket::m_lock"),
103 AdminSocket::~AdminSocket()
109 * This thread listens on the UNIX domain socket for incoming connections.
110 * It only handles one connection at a time at the moment. All I/O is nonblocking,
111 * so that we can implement sensible timeouts. [TODO: make all I/O nonblocking]
113 * This thread also listens to m_shutdown_rd_fd. If there is any data sent to this
114 * pipe, the thread terminates itself gracefully, allowing the
115 * AdminSocketConfigObs class to join() it.
118 #define PFL_SUCCESS ((void*)(intptr_t)0)
119 #define PFL_FAIL ((void*)(intptr_t)1)
121 std::string
AdminSocket::create_shutdown_pipe(int *pipe_rd
, int *pipe_wr
)
124 int ret
= pipe_cloexec(pipefd
);
127 oss
<< "AdminSocket::create_shutdown_pipe error: " << cpp_strerror(ret
);
131 *pipe_rd
= pipefd
[0];
132 *pipe_wr
= pipefd
[1];
136 std::string
AdminSocket::destroy_shutdown_pipe()
138 // Send a byte to the shutdown pipe that the thread is listening to
139 char buf
[1] = { 0x0 };
140 int ret
= safe_write(m_shutdown_wr_fd
, buf
, sizeof(buf
));
143 VOID_TEMP_FAILURE_RETRY(close(m_shutdown_wr_fd
));
144 m_shutdown_wr_fd
= -1;
148 oss
<< "AdminSocket::destroy_shutdown_pipe error: failed to write"
149 "to thread shutdown pipe: error " << ret
;
155 // Close read end. Doing this before join() blocks the listenter and prevents
157 VOID_TEMP_FAILURE_RETRY(close(m_shutdown_rd_fd
));
158 m_shutdown_rd_fd
= -1;
163 std::string
AdminSocket::bind_and_listen(const std::string
&sock_path
, int *fd
)
165 ldout(m_cct
, 5) << "bind_and_listen " << sock_path
<< dendl
;
167 struct sockaddr_un address
;
168 if (sock_path
.size() > sizeof(address
.sun_path
) - 1) {
170 oss
<< "AdminSocket::bind_and_listen: "
171 << "The UNIX domain socket path " << sock_path
<< " is too long! The "
172 << "maximum length on this system is "
173 << (sizeof(address
.sun_path
) - 1);
176 int sock_fd
= socket(PF_UNIX
, SOCK_STREAM
, 0);
180 oss
<< "AdminSocket::bind_and_listen: "
181 << "failed to create socket: " << cpp_strerror(err
);
184 int r
= fcntl(sock_fd
, F_SETFD
, FD_CLOEXEC
);
187 VOID_TEMP_FAILURE_RETRY(::close(sock_fd
));
189 oss
<< "AdminSocket::bind_and_listen: failed to fcntl on socket: " << cpp_strerror(r
);
192 memset(&address
, 0, sizeof(struct sockaddr_un
));
193 address
.sun_family
= AF_UNIX
;
194 snprintf(address
.sun_path
, sizeof(address
.sun_path
),
195 "%s", sock_path
.c_str());
196 if (::bind(sock_fd
, (struct sockaddr
*)&address
,
197 sizeof(struct sockaddr_un
)) != 0) {
199 if (err
== EADDRINUSE
) {
200 AdminSocketClient
client(sock_path
);
204 ldout(m_cct
, 20) << "socket " << sock_path
<< " is in use" << dendl
;
207 ldout(m_cct
, 20) << "unlink stale file " << sock_path
<< dendl
;
208 VOID_TEMP_FAILURE_RETRY(unlink(sock_path
.c_str()));
209 if (::bind(sock_fd
, (struct sockaddr
*)&address
,
210 sizeof(struct sockaddr_un
)) == 0) {
219 oss
<< "AdminSocket::bind_and_listen: "
220 << "failed to bind the UNIX domain socket to '" << sock_path
221 << "': " << cpp_strerror(err
);
226 if (listen(sock_fd
, 5) != 0) {
229 oss
<< "AdminSocket::bind_and_listen: "
230 << "failed to listen to socket: " << cpp_strerror(err
);
232 VOID_TEMP_FAILURE_RETRY(unlink(sock_path
.c_str()));
239 void* AdminSocket::entry()
241 ldout(m_cct
, 5) << "entry start" << dendl
;
243 struct pollfd fds
[2];
244 memset(fds
, 0, sizeof(fds
));
245 fds
[0].fd
= m_sock_fd
;
246 fds
[0].events
= POLLIN
| POLLRDBAND
;
247 fds
[1].fd
= m_shutdown_rd_fd
;
248 fds
[1].events
= POLLIN
| POLLRDBAND
;
250 int ret
= poll(fds
, 2, -1);
256 lderr(m_cct
) << "AdminSocket: poll(2) error: '"
257 << cpp_strerror(err
) << dendl
;
261 if (fds
[0].revents
& POLLIN
) {
262 // Send out some data
265 if (fds
[1].revents
& POLLIN
) {
266 // Parent wants us to shut down
270 ldout(m_cct
, 5) << "entry exit" << dendl
;
273 void AdminSocket::chown(uid_t uid
, gid_t gid
)
275 if (m_sock_fd
>= 0) {
276 int r
= ::chown(m_path
.c_str(), uid
, gid
);
279 lderr(m_cct
) << "AdminSocket: failed to chown socket: "
280 << cpp_strerror(r
) << dendl
;
285 void AdminSocket::chmod(mode_t mode
)
287 if (m_sock_fd
>= 0) {
288 int r
= ::chmod(m_path
.c_str(), mode
);
291 lderr(m_cct
) << "AdminSocket: failed to chmod socket: "
292 << cpp_strerror(r
) << dendl
;
297 bool AdminSocket::do_accept()
299 struct sockaddr_un address
;
300 socklen_t address_length
= sizeof(address
);
301 ldout(m_cct
, 30) << "AdminSocket: calling accept" << dendl
;
302 int connection_fd
= accept(m_sock_fd
, (struct sockaddr
*) &address
,
304 ldout(m_cct
, 30) << "AdminSocket: finished accept" << dendl
;
305 if (connection_fd
< 0) {
307 lderr(m_cct
) << "AdminSocket: do_accept error: '"
308 << cpp_strerror(err
) << dendl
;
316 int ret
= safe_read(connection_fd
, &cmd
[pos
], 1);
319 lderr(m_cct
) << "AdminSocket: error reading request code: "
320 << cpp_strerror(ret
) << dendl
;
322 VOID_TEMP_FAILURE_RETRY(close(connection_fd
));
325 //ldout(m_cct, 0) << "AdminSocket read byte " << (int)cmd[pos] << " pos " << pos << dendl;
326 if (cmd
[0] == '\0') {
327 // old protocol: __be32
328 if (pos
== 3 && cmd
[0] == '\0') {
334 c
= "perfcounters_dump";
337 c
= "perfcounters_schema";
346 // new protocol: null or \n terminated string
347 if (cmd
[pos
] == '\n' || cmd
[pos
] == '\0') {
353 if (++pos
>= sizeof(cmd
)) {
354 lderr(m_cct
) << "AdminSocket: error reading request too long" << dendl
;
355 VOID_TEMP_FAILURE_RETRY(close(connection_fd
));
362 map
<string
, cmd_vartype
> cmdmap
;
364 vector
<string
> cmdvec
;
366 cmdvec
.push_back(cmd
);
367 if (!cmdmap_from_json(cmdvec
, &cmdmap
, errss
)) {
368 ldout(m_cct
, 0) << "AdminSocket: " << errss
.rdbuf() << dendl
;
369 VOID_TEMP_FAILURE_RETRY(close(connection_fd
));
372 cmd_getval(m_cct
, cmdmap
, "format", format
);
373 if (format
!= "json" && format
!= "json-pretty" &&
374 format
!= "xml" && format
!= "xml-pretty")
375 format
= "json-pretty";
376 cmd_getval(m_cct
, cmdmap
, "prefix", c
);
379 map
<string
,AdminSocketHook
*>::iterator p
;
381 while (match
.size()) {
382 p
= m_hooks
.find(match
);
383 if (p
!= m_hooks
.end())
386 // drop right-most word
387 size_t pos
= match
.rfind(' ');
388 if (pos
== std::string::npos
) {
389 match
.clear(); // we fail
397 if (p
== m_hooks
.end()) {
398 lderr(m_cct
) << "AdminSocket: request '" << c
<< "' not defined" << dendl
;
402 args
= c
.substr(match
.length() + 1);
405 // Drop lock to avoid cycles in cases where the hook takes
406 // the same lock that was held during calls to register/unregister,
407 // and set in_hook to allow unregister to wait for us before
408 // removing this hook.
410 auto match_hook
= p
->second
;
412 bool success
= match_hook
->call(match
, cmdmap
, format
, out
);
415 in_hook_cond
.Signal();
418 ldout(m_cct
, 0) << "AdminSocket: request '" << match
<< "' args '" << args
419 << "' to " << p
->second
<< " failed" << dendl
;
420 out
.append("failed");
422 ldout(m_cct
, 5) << "AdminSocket: request '" << match
<< "' '" << args
423 << "' to " << p
->second
424 << " returned " << out
.length() << " bytes" << dendl
;
426 uint32_t len
= htonl(out
.length());
427 int ret
= safe_write(connection_fd
, &len
, sizeof(len
));
429 lderr(m_cct
) << "AdminSocket: error writing response length "
430 << cpp_strerror(ret
) << dendl
;
432 if (out
.write_fd(connection_fd
) >= 0)
438 VOID_TEMP_FAILURE_RETRY(close(connection_fd
));
442 int AdminSocket::register_command(std::string command
, std::string cmddesc
, AdminSocketHook
*hook
, std::string help
)
446 if (m_hooks
.count(command
)) {
447 ldout(m_cct
, 5) << "register_command " << command
<< " hook " << hook
<< " EEXIST" << dendl
;
450 ldout(m_cct
, 5) << "register_command " << command
<< " hook " << hook
<< dendl
;
451 m_hooks
[command
] = hook
;
452 m_descs
[command
] = cmddesc
;
453 m_help
[command
] = help
;
460 int AdminSocket::unregister_command(std::string command
)
464 if (m_hooks
.count(command
)) {
465 ldout(m_cct
, 5) << "unregister_command " << command
<< dendl
;
466 m_hooks
.erase(command
);
467 m_descs
.erase(command
);
468 m_help
.erase(command
);
470 // If we are currently processing a command, wait for it to
471 // complete in case it referenced the hook that we are
474 in_hook_cond
.Wait(m_lock
);
479 ldout(m_cct
, 5) << "unregister_command " << command
<< " ENOENT" << dendl
;
486 class VersionHook
: public AdminSocketHook
{
488 bool call(std::string command
, cmdmap_t
&cmdmap
, std::string format
,
489 bufferlist
& out
) override
{
490 if (command
== "0") {
491 out
.append(CEPH_ADMIN_SOCK_VERSION
);
494 jf
.open_object_section("version");
495 if (command
== "version") {
496 jf
.dump_string("version", ceph_version_to_str());
497 jf
.dump_string("release", ceph_release_name(ceph_release()));
498 jf
.dump_string("release_type", ceph_release_type());
499 } else if (command
== "git_version") {
500 jf
.dump_string("git_version", git_version_to_str());
505 out
.append(ss
.str());
511 class HelpHook
: public AdminSocketHook
{
514 explicit HelpHook(AdminSocket
*as
) : m_as(as
) {}
515 bool call(string command
, cmdmap_t
&cmdmap
, string format
, bufferlist
& out
) override
{
516 Formatter
*f
= Formatter::create(format
, "json-pretty", "json-pretty");
517 f
->open_object_section("help");
518 for (map
<string
,string
>::iterator p
= m_as
->m_help
.begin();
519 p
!= m_as
->m_help
.end();
521 if (p
->second
.length())
522 f
->dump_string(p
->first
.c_str(), p
->second
);
527 out
.append(ss
.str());
533 class GetdescsHook
: public AdminSocketHook
{
536 explicit GetdescsHook(AdminSocket
*as
) : m_as(as
) {}
537 bool call(string command
, cmdmap_t
&cmdmap
, string format
, bufferlist
& out
) override
{
539 JSONFormatter
jf(false);
540 jf
.open_object_section("command_descriptions");
541 for (map
<string
,string
>::iterator p
= m_as
->m_descs
.begin();
542 p
!= m_as
->m_descs
.end();
544 ostringstream secname
;
545 secname
<< "cmd" << setfill('0') << std::setw(3) << cmdnum
;
546 dump_cmd_and_help_to_json(&jf
,
547 secname
.str().c_str(),
549 m_as
->m_help
[p
->first
]);
552 jf
.close_section(); // command_descriptions
555 out
.append(ss
.str());
560 bool AdminSocket::init(const std::string
&path
)
562 ldout(m_cct
, 5) << "init " << path
<< dendl
;
564 /* Set up things for the new thread */
566 int pipe_rd
= -1, pipe_wr
= -1;
567 err
= create_shutdown_pipe(&pipe_rd
, &pipe_wr
);
569 lderr(m_cct
) << "AdminSocketConfigObs::init: error: " << err
<< dendl
;
573 err
= bind_and_listen(path
, &sock_fd
);
575 lderr(m_cct
) << "AdminSocketConfigObs::init: failed: " << err
<< dendl
;
581 /* Create new thread */
583 m_shutdown_rd_fd
= pipe_rd
;
584 m_shutdown_wr_fd
= pipe_wr
;
587 m_version_hook
= new VersionHook
;
588 register_command("0", "0", m_version_hook
, "");
589 register_command("version", "version", m_version_hook
, "get ceph version");
590 register_command("git_version", "git_version", m_version_hook
, "get git sha1");
591 m_help_hook
= new HelpHook(this);
592 register_command("help", "help", m_help_hook
, "list available commands");
593 m_getdescs_hook
= new GetdescsHook(this);
594 register_command("get_command_descriptions", "get_command_descriptions",
595 m_getdescs_hook
, "list available commands");
597 create("admin_socket");
598 add_cleanup_file(m_path
.c_str());
602 void AdminSocket::shutdown()
606 // Under normal operation this is unlikely to occur. However for some unit
607 // tests, some object members are not initialized and so cannot be deleted
609 if (m_shutdown_wr_fd
< 0)
612 ldout(m_cct
, 5) << "shutdown" << dendl
;
614 err
= destroy_shutdown_pipe();
616 lderr(m_cct
) << "AdminSocket::shutdown: error: " << err
<< dendl
;
619 VOID_TEMP_FAILURE_RETRY(close(m_sock_fd
));
621 unregister_command("version");
622 unregister_command("git_version");
623 unregister_command("0");
624 delete m_version_hook
;
626 unregister_command("help");
629 unregister_command("get_command_descriptions");
630 delete m_getdescs_hook
;
632 remove_cleanup_file(m_path
.c_str());