]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/admin_socket.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / common / admin_socket.cc
CommitLineData
11fdf7f2 1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
7c673cae
FG
2// vim: ts=8 sw=2 smarttab
3/*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2011 New Dream Network
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
11fdf7f2 10 * License version 2.1, as published by the Free Software
7c673cae 11 * Foundation. See file COPYING.
11fdf7f2 12 *
7c673cae 13 */
11fdf7f2
TL
14#include <poll.h>
15#include <sys/un.h>
7c673cae 16
7c673cae
FG
17#include "common/admin_socket.h"
18#include "common/admin_socket_client.h"
11fdf7f2 19#include "common/dout.h"
7c673cae 20#include "common/errno.h"
7c673cae 21#include "common/safe_io.h"
11fdf7f2 22#include "common/Thread.h"
7c673cae 23#include "common/version.h"
9f95a23c 24#include "common/ceph_mutex.h"
7c673cae 25
9f95a23c
TL
26#ifndef WITH_SEASTAR
27#include "common/Cond.h"
28#endif
29
30#include "messages/MCommand.h"
31#include "messages/MCommandReply.h"
32#include "messages/MMonCommand.h"
33#include "messages/MMonCommandAck.h"
7c673cae 34
31f18b77 35// re-include our assert to clobber the system one; fix dout:
11fdf7f2 36#include "include/ceph_assert.h"
91327a77
AA
37#include "include/compat.h"
38#include "include/sock_compat.h"
7c673cae
FG
39
40#define dout_subsys ceph_subsys_asok
41#undef dout_prefix
42#define dout_prefix *_dout << "asok(" << (void*)m_cct << ") "
43
44
45using std::ostringstream;
9f95a23c 46using namespace TOPNSPC::common;
7c673cae
FG
47
48/*
49 * UNIX domain sockets created by an application persist even after that
50 * application closes, unless they're explicitly unlinked. This is because the
51 * directory containing the socket keeps a reference to the socket.
52 *
53 * This code makes things a little nicer by unlinking those dead sockets when
54 * the application exits normally.
55 */
11fdf7f2
TL
56
57template<typename F, typename... Args>
58inline int retry_sys_call(F f, Args... args) {
59 int r;
60 do {
61 r = f(args...);
62 } while (r < 0 && errno == EINTR);
63 return r;
64};
65
66
67static std::mutex cleanup_lock;
68static std::vector<std::string> cleanup_files;
7c673cae
FG
69static bool cleanup_atexit = false;
70
11fdf7f2
TL
71static void remove_cleanup_file(std::string_view file) {
72 std::unique_lock l(cleanup_lock);
73
74 if (auto i = std::find(cleanup_files.cbegin(), cleanup_files.cend(), file);
75 i != cleanup_files.cend()) {
76 retry_sys_call(::unlink, i->c_str());
77 cleanup_files.erase(i);
7c673cae 78 }
7c673cae
FG
79}
80
11fdf7f2
TL
81void remove_all_cleanup_files() {
82 std::unique_lock l(cleanup_lock);
83 for (const auto& s : cleanup_files) {
84 retry_sys_call(::unlink, s.c_str());
7c673cae
FG
85 }
86 cleanup_files.clear();
7c673cae
FG
87}
88
11fdf7f2
TL
89static void add_cleanup_file(std::string file) {
90 std::unique_lock l(cleanup_lock);
91 cleanup_files.push_back(std::move(file));
7c673cae
FG
92 if (!cleanup_atexit) {
93 atexit(remove_all_cleanup_files);
94 cleanup_atexit = true;
95 }
7c673cae
FG
96}
97
7c673cae 98AdminSocket::AdminSocket(CephContext *cct)
11fdf7f2
TL
99 : m_cct(cct)
100{}
7c673cae
FG
101
102AdminSocket::~AdminSocket()
103{
104 shutdown();
105}
106
107/*
108 * This thread listens on the UNIX domain socket for incoming connections.
109 * It only handles one connection at a time at the moment. All I/O is nonblocking,
110 * so that we can implement sensible timeouts. [TODO: make all I/O nonblocking]
111 *
9f95a23c
TL
112 * This thread also listens to m_wakeup_rd_fd. If there is any data sent to this
113 * pipe, the thread wakes up. If m_shutdown is set, the thread terminates
114 * itself gracefully, allowing the AdminSocketConfigObs class to join() it.
7c673cae
FG
115 */
116
9f95a23c 117std::string AdminSocket::create_wakeup_pipe(int *pipe_rd, int *pipe_wr)
7c673cae
FG
118{
119 int pipefd[2];
9f95a23c 120 if (pipe_cloexec(pipefd, O_NONBLOCK) < 0) {
91327a77 121 int e = errno;
7c673cae 122 ostringstream oss;
9f95a23c 123 oss << "AdminSocket::create_wakeup_pipe error: " << cpp_strerror(e);
7c673cae
FG
124 return oss.str();
125 }
126
127 *pipe_rd = pipefd[0];
128 *pipe_wr = pipefd[1];
129 return "";
130}
131
9f95a23c 132std::string AdminSocket::destroy_wakeup_pipe()
7c673cae 133{
9f95a23c 134 // Send a byte to the wakeup pipe that the thread is listening to
7c673cae 135 char buf[1] = { 0x0 };
9f95a23c 136 int ret = safe_write(m_wakeup_wr_fd, buf, sizeof(buf));
7c673cae
FG
137
138 // Close write end
9f95a23c
TL
139 retry_sys_call(::close, m_wakeup_wr_fd);
140 m_wakeup_wr_fd = -1;
7c673cae
FG
141
142 if (ret != 0) {
143 ostringstream oss;
144 oss << "AdminSocket::destroy_shutdown_pipe error: failed to write"
145 "to thread shutdown pipe: error " << ret;
146 return oss.str();
147 }
148
11fdf7f2 149 th.join();
7c673cae
FG
150
151 // Close read end. Doing this before join() blocks the listenter and prevents
152 // joining.
9f95a23c
TL
153 retry_sys_call(::close, m_wakeup_rd_fd);
154 m_wakeup_rd_fd = -1;
7c673cae
FG
155
156 return "";
157}
158
159std::string AdminSocket::bind_and_listen(const std::string &sock_path, int *fd)
160{
161 ldout(m_cct, 5) << "bind_and_listen " << sock_path << dendl;
162
163 struct sockaddr_un address;
164 if (sock_path.size() > sizeof(address.sun_path) - 1) {
165 ostringstream oss;
166 oss << "AdminSocket::bind_and_listen: "
167 << "The UNIX domain socket path " << sock_path << " is too long! The "
168 << "maximum length on this system is "
169 << (sizeof(address.sun_path) - 1);
170 return oss.str();
171 }
91327a77 172 int sock_fd = socket_cloexec(PF_UNIX, SOCK_STREAM, 0);
7c673cae
FG
173 if (sock_fd < 0) {
174 int err = errno;
175 ostringstream oss;
176 oss << "AdminSocket::bind_and_listen: "
177 << "failed to create socket: " << cpp_strerror(err);
178 return oss.str();
179 }
92f5a8d4 180 // FIPS zeroization audit 20191115: this memset is fine.
7c673cae
FG
181 memset(&address, 0, sizeof(struct sockaddr_un));
182 address.sun_family = AF_UNIX;
183 snprintf(address.sun_path, sizeof(address.sun_path),
184 "%s", sock_path.c_str());
185 if (::bind(sock_fd, (struct sockaddr*)&address,
186 sizeof(struct sockaddr_un)) != 0) {
187 int err = errno;
188 if (err == EADDRINUSE) {
189 AdminSocketClient client(sock_path);
190 bool ok;
191 client.ping(&ok);
192 if (ok) {
193 ldout(m_cct, 20) << "socket " << sock_path << " is in use" << dendl;
194 err = EEXIST;
195 } else {
196 ldout(m_cct, 20) << "unlink stale file " << sock_path << dendl;
11fdf7f2 197 retry_sys_call(::unlink, sock_path.c_str());
7c673cae
FG
198 if (::bind(sock_fd, (struct sockaddr*)&address,
199 sizeof(struct sockaddr_un)) == 0) {
200 err = 0;
201 } else {
202 err = errno;
203 }
204 }
205 }
206 if (err != 0) {
207 ostringstream oss;
208 oss << "AdminSocket::bind_and_listen: "
209 << "failed to bind the UNIX domain socket to '" << sock_path
210 << "': " << cpp_strerror(err);
211 close(sock_fd);
212 return oss.str();
213 }
214 }
215 if (listen(sock_fd, 5) != 0) {
216 int err = errno;
217 ostringstream oss;
218 oss << "AdminSocket::bind_and_listen: "
219 << "failed to listen to socket: " << cpp_strerror(err);
220 close(sock_fd);
11fdf7f2 221 retry_sys_call(::unlink, sock_path.c_str());
7c673cae
FG
222 return oss.str();
223 }
224 *fd = sock_fd;
225 return "";
226}
227
11fdf7f2 228void AdminSocket::entry() noexcept
7c673cae
FG
229{
230 ldout(m_cct, 5) << "entry start" << dendl;
231 while (true) {
232 struct pollfd fds[2];
92f5a8d4 233 // FIPS zeroization audit 20191115: this memset is fine.
7c673cae
FG
234 memset(fds, 0, sizeof(fds));
235 fds[0].fd = m_sock_fd;
236 fds[0].events = POLLIN | POLLRDBAND;
9f95a23c 237 fds[1].fd = m_wakeup_rd_fd;
7c673cae
FG
238 fds[1].events = POLLIN | POLLRDBAND;
239
9f95a23c 240 ldout(m_cct,20) << __func__ << " waiting" << dendl;
7c673cae
FG
241 int ret = poll(fds, 2, -1);
242 if (ret < 0) {
243 int err = errno;
244 if (err == EINTR) {
245 continue;
246 }
247 lderr(m_cct) << "AdminSocket: poll(2) error: '"
248 << cpp_strerror(err) << dendl;
11fdf7f2 249 return;
7c673cae 250 }
9f95a23c 251 ldout(m_cct,20) << __func__ << " awake" << dendl;
7c673cae
FG
252
253 if (fds[0].revents & POLLIN) {
254 // Send out some data
255 do_accept();
256 }
257 if (fds[1].revents & POLLIN) {
9f95a23c
TL
258 // read off one byte
259 char buf;
260 auto s = ::read(m_wakeup_rd_fd, &buf, 1);
261 if (s == -1) {
262 int e = errno;
263 ldout(m_cct, 5) << "AdminSocket: (ignoring) read(2) error: '"
264 << cpp_strerror(e) << dendl;
265 }
266 do_tell_queue();
267 }
268 if (m_shutdown) {
7c673cae 269 // Parent wants us to shut down
11fdf7f2 270 return;
7c673cae
FG
271 }
272 }
273 ldout(m_cct, 5) << "entry exit" << dendl;
274}
275
276void AdminSocket::chown(uid_t uid, gid_t gid)
277{
278 if (m_sock_fd >= 0) {
279 int r = ::chown(m_path.c_str(), uid, gid);
280 if (r < 0) {
281 r = -errno;
282 lderr(m_cct) << "AdminSocket: failed to chown socket: "
283 << cpp_strerror(r) << dendl;
284 }
285 }
286}
287
288void AdminSocket::chmod(mode_t mode)
289{
290 if (m_sock_fd >= 0) {
291 int r = ::chmod(m_path.c_str(), mode);
292 if (r < 0) {
293 r = -errno;
294 lderr(m_cct) << "AdminSocket: failed to chmod socket: "
295 << cpp_strerror(r) << dendl;
296 }
297 }
298}
299
9f95a23c 300void AdminSocket::do_accept()
7c673cae
FG
301{
302 struct sockaddr_un address;
303 socklen_t address_length = sizeof(address);
304 ldout(m_cct, 30) << "AdminSocket: calling accept" << dendl;
91327a77 305 int connection_fd = accept_cloexec(m_sock_fd, (struct sockaddr*) &address,
7c673cae 306 &address_length);
7c673cae
FG
307 if (connection_fd < 0) {
308 int err = errno;
309 lderr(m_cct) << "AdminSocket: do_accept error: '"
310 << cpp_strerror(err) << dendl;
9f95a23c 311 return;
7c673cae 312 }
91327a77 313 ldout(m_cct, 30) << "AdminSocket: finished accept" << dendl;
7c673cae
FG
314
315 char cmd[1024];
316 unsigned pos = 0;
317 string c;
318 while (1) {
319 int ret = safe_read(connection_fd, &cmd[pos], 1);
320 if (ret <= 0) {
321 if (ret < 0) {
322 lderr(m_cct) << "AdminSocket: error reading request code: "
323 << cpp_strerror(ret) << dendl;
324 }
11fdf7f2 325 retry_sys_call(::close, connection_fd);
9f95a23c 326 return;
7c673cae 327 }
7c673cae
FG
328 if (cmd[0] == '\0') {
329 // old protocol: __be32
330 if (pos == 3 && cmd[0] == '\0') {
331 switch (cmd[3]) {
332 case 0:
333 c = "0";
334 break;
335 case 1:
336 c = "perfcounters_dump";
337 break;
338 case 2:
339 c = "perfcounters_schema";
340 break;
341 default:
342 c = "foo";
343 break;
344 }
eafe8130
TL
345 //wrap command with new protocol
346 c = "{\"prefix\": \"" + c + "\"}";
7c673cae
FG
347 break;
348 }
349 } else {
350 // new protocol: null or \n terminated string
351 if (cmd[pos] == '\n' || cmd[pos] == '\0') {
352 cmd[pos] = '\0';
353 c = cmd;
354 break;
355 }
356 }
357 if (++pos >= sizeof(cmd)) {
358 lderr(m_cct) << "AdminSocket: error reading request too long" << dendl;
11fdf7f2 359 retry_sys_call(::close, connection_fd);
9f95a23c 360 return;
7c673cae
FG
361 }
362 }
363
9f95a23c
TL
364 std::vector<std::string> cmdvec = { c };
365 bufferlist empty, out;
366 ostringstream err;
367 int rval = execute_command(cmdvec, empty /* inbl */, err, &out);
368
369 // Unfortunately, the asok wire protocol does not let us pass an error code,
370 // and many asok command implementations return helpful error strings. So,
371 // let's prepend an error string to the output if there is an error code.
372 if (rval < 0) {
373 ostringstream ss;
374 ss << "ERROR: " << cpp_strerror(rval) << "\n";
375 ss << err.str() << "\n";
376 bufferlist o;
377 o.append(ss.str());
378 o.claim_append(out);
379 out.claim_append(o);
380 }
381 uint32_t len = htonl(out.length());
382 int ret = safe_write(connection_fd, &len, sizeof(len));
383 if (ret < 0) {
384 lderr(m_cct) << "AdminSocket: error writing response length "
385 << cpp_strerror(ret) << dendl;
386 } else {
387 if (out.write_fd(connection_fd) < 0) {
388 lderr(m_cct) << "AdminSocket: error writing response payload "
389 << cpp_strerror(ret) << dendl;
eafe8130
TL
390 }
391 }
eafe8130 392 retry_sys_call(::close, connection_fd);
9f95a23c
TL
393}
394
395void AdminSocket::do_tell_queue()
396{
397 ldout(m_cct,10) << __func__ << dendl;
398 std::list<cref_t<MCommand>> q;
399 std::list<cref_t<MMonCommand>> lq;
400 {
401 std::lock_guard l(tell_lock);
402 q.swap(tell_queue);
403 lq.swap(tell_legacy_queue);
404 }
405 for (auto& m : q) {
406 bufferlist outbl;
407 execute_command(
408 m->cmd,
409 m->get_data(),
410 [m](int r, const std::string& err, bufferlist& outbl) {
411 auto reply = new MCommandReply(r, err);
412 reply->set_tid(m->get_tid());
413 reply->set_data(outbl);
414#ifdef WITH_SEASTAR
415#warning "fix message send with crimson"
416#else
417 m->get_connection()->send_message(reply);
418#endif
419 });
420 }
421 for (auto& m : lq) {
422 bufferlist outbl;
423 execute_command(
424 m->cmd,
425 m->get_data(),
426 [m](int r, const std::string& err, bufferlist& outbl) {
427 auto reply = new MMonCommandAck(m->cmd, r, err, 0);
428 reply->set_tid(m->get_tid());
429 reply->set_data(outbl);
430#ifdef WITH_SEASTAR
431#warning "fix message send with crimson"
432#else
433 m->get_connection()->send_message(reply);
434#endif
435 });
436 }
437}
438
439int AdminSocket::execute_command(
440 const std::vector<std::string>& cmd,
441 const bufferlist& inbl,
442 std::ostream& errss,
443 bufferlist *outbl)
444{
445#ifdef WITH_SEASTAR
446#warning "must implement admin socket blocking execute_command() for crimson"
447 return -ENOSYS;
448#else
449 bool done = false;
450 int rval = 0;
451 ceph::mutex mylock = ceph::make_mutex("admin_socket::excute_command::mylock");
452 ceph::condition_variable mycond;
453 C_SafeCond fin(mylock, mycond, &done, &rval);
454 execute_command(
455 cmd,
456 inbl,
457 [&errss, outbl, &fin](int r, const std::string& err, bufferlist& out) {
458 errss << err;
459 outbl->claim(out);
460 fin.finish(r);
461 });
462 {
463 std::unique_lock l{mylock};
464 mycond.wait(l, [&done] { return done;});
465 }
eafe8130 466 return rval;
9f95a23c 467#endif
eafe8130
TL
468}
469
9f95a23c
TL
470void AdminSocket::execute_command(
471 const std::vector<std::string>& cmdvec,
472 const bufferlist& inbl,
473 std::function<void(int,const std::string&,bufferlist&)> on_finish)
eafe8130 474{
11fdf7f2 475 cmdmap_t cmdmap;
7c673cae 476 string format;
7c673cae 477 stringstream errss;
9f95a23c
TL
478 bufferlist empty;
479 ldout(m_cct,10) << __func__ << " cmdvec='" << cmdvec << "'" << dendl;
7c673cae 480 if (!cmdmap_from_json(cmdvec, &cmdmap, errss)) {
b32b8144 481 ldout(m_cct, 0) << "AdminSocket: " << errss.str() << dendl;
9f95a23c 482 return on_finish(-EINVAL, "invalid json", empty);
11fdf7f2 483 }
9f95a23c 484 string prefix;
11fdf7f2 485 try {
9f95a23c
TL
486 cmd_getval(cmdmap, "format", format);
487 cmd_getval(cmdmap, "prefix", prefix);
11fdf7f2 488 } catch (const bad_cmd_get& e) {
9f95a23c
TL
489 return on_finish(-EINVAL, "invalid json, missing format and/or prefix",
490 empty);
7c673cae 491 }
9f95a23c
TL
492
493 Formatter *f = Formatter::create(format, "json-pretty", "json-pretty");
7c673cae 494
11fdf7f2
TL
495 std::unique_lock l(lock);
496 decltype(hooks)::iterator p;
9f95a23c 497 p = hooks.find(prefix);
11fdf7f2 498 if (p == hooks.cend()) {
9f95a23c
TL
499 lderr(m_cct) << "AdminSocket: request '" << cmdvec
500 << "' not defined" << dendl;
501 delete f;
502 return on_finish(-EINVAL, "unknown command prefix "s + prefix, empty);
eafe8130 503 }
9f95a23c
TL
504
505 // make sure one of the registered commands with this prefix validates.
506 while (!validate_cmd(m_cct, p->second.desc, cmdmap, errss)) {
507 ++p;
508 if (p->first != prefix) {
509 delete f;
510 return on_finish(-EINVAL, "invalid command json", empty);
511 }
7c673cae 512 }
7c673cae 513
eafe8130
TL
514 // Drop lock to avoid cycles in cases where the hook takes
515 // the same lock that was held during calls to register/unregister,
516 // and set in_hook to allow unregister to wait for us before
517 // removing this hook.
518 in_hook = true;
9f95a23c 519 auto hook = p->second.hook;
eafe8130 520 l.unlock();
9f95a23c
TL
521 hook->call_async(
522 prefix, cmdmap, f, inbl,
523 [f, on_finish](int r, const std::string& err, bufferlist& out) {
524 // handle either existing output in bufferlist *or* via formatter
525 if (r >= 0 && out.length() == 0) {
526 f->flush(out);
527 }
528 delete f;
529 on_finish(r, err, out);
530 });
531
eafe8130
TL
532 l.lock();
533 in_hook = false;
534 in_hook_cond.notify_all();
7c673cae
FG
535}
536
9f95a23c 537void AdminSocket::queue_tell_command(cref_t<MCommand> m)
11fdf7f2 538{
9f95a23c
TL
539 ldout(m_cct,10) << __func__ << " " << *m << dendl;
540 std::lock_guard l(tell_lock);
541 tell_queue.push_back(std::move(m));
542 wakeup();
543}
544void AdminSocket::queue_tell_command(cref_t<MMonCommand> m)
545{
546 ldout(m_cct,10) << __func__ << " " << *m << dendl;
547 std::lock_guard l(tell_lock);
548 tell_legacy_queue.push_back(std::move(m));
549 wakeup();
11fdf7f2
TL
550}
551
9f95a23c 552int AdminSocket::register_command(std::string_view cmddesc,
11fdf7f2
TL
553 AdminSocketHook *hook,
554 std::string_view help)
7c673cae
FG
555{
556 int ret;
11fdf7f2 557 std::unique_lock l(lock);
9f95a23c
TL
558 string prefix = cmddesc_get_prefix(cmddesc);
559 auto i = hooks.find(prefix);
560 if (i != hooks.cend() &&
561 i->second.desc == cmddesc) {
562 ldout(m_cct, 5) << "register_command " << prefix
563 << " cmddesc " << cmddesc << " hook " << hook
11fdf7f2 564 << " EEXIST" << dendl;
7c673cae
FG
565 ret = -EEXIST;
566 } else {
9f95a23c 567 ldout(m_cct, 5) << "register_command " << prefix << " hook " << hook
11fdf7f2
TL
568 << dendl;
569 hooks.emplace_hint(i,
570 std::piecewise_construct,
9f95a23c 571 std::forward_as_tuple(prefix),
11fdf7f2 572 std::forward_as_tuple(hook, cmddesc, help));
7c673cae 573 ret = 0;
11fdf7f2 574 }
7c673cae
FG
575 return ret;
576}
577
11fdf7f2
TL
578void AdminSocket::unregister_commands(const AdminSocketHook *hook)
579{
580 std::unique_lock l(lock);
581 auto i = hooks.begin();
582 while (i != hooks.end()) {
583 if (i->second.hook == hook) {
584 ldout(m_cct, 5) << __func__ << " " << i->first << dendl;
585
586 // If we are currently processing a command, wait for it to
587 // complete in case it referenced the hook that we are
588 // unregistering.
589 in_hook_cond.wait(l, [this]() { return !in_hook; });
590 hooks.erase(i++);
591 } else {
592 i++;
593 }
594 }
595}
596
7c673cae
FG
597class VersionHook : public AdminSocketHook {
598public:
9f95a23c
TL
599 int call(std::string_view command, const cmdmap_t& cmdmap,
600 Formatter *f,
601 std::ostream& errss,
602 bufferlist& out) override {
11fdf7f2 603 if (command == "0"sv) {
7c673cae
FG
604 out.append(CEPH_ADMIN_SOCK_VERSION);
605 } else {
9f95a23c 606 f->open_object_section("version");
31f18b77 607 if (command == "version") {
9f95a23c
TL
608 f->dump_string("version", ceph_version_to_str());
609 f->dump_string("release", ceph_release_to_str());
610 f->dump_string("release_type", ceph_release_type());
31f18b77 611 } else if (command == "git_version") {
9f95a23c 612 f->dump_string("git_version", git_version_to_str());
31f18b77 613 }
7c673cae 614 ostringstream ss;
9f95a23c 615 f->close_section();
7c673cae 616 }
9f95a23c 617 return 0;
7c673cae
FG
618 }
619};
620
621class HelpHook : public AdminSocketHook {
622 AdminSocket *m_as;
623public:
624 explicit HelpHook(AdminSocket *as) : m_as(as) {}
9f95a23c
TL
625 int call(std::string_view command, const cmdmap_t& cmdmap,
626 Formatter *f,
627 std::ostream& errss,
628 bufferlist& out) override {
7c673cae 629 f->open_object_section("help");
11fdf7f2
TL
630 for (const auto& [command, info] : m_as->hooks) {
631 if (info.help.length())
632 f->dump_string(command.c_str(), info.help);
7c673cae
FG
633 }
634 f->close_section();
9f95a23c 635 return 0;
7c673cae
FG
636 }
637};
638
639class GetdescsHook : public AdminSocketHook {
640 AdminSocket *m_as;
641public:
642 explicit GetdescsHook(AdminSocket *as) : m_as(as) {}
9f95a23c
TL
643 int call(std::string_view command, const cmdmap_t& cmdmap,
644 Formatter *f,
645 std::ostream& errss,
646 bufferlist& out) override {
7c673cae 647 int cmdnum = 0;
9f95a23c 648 f->open_object_section("command_descriptions");
11fdf7f2
TL
649 for (const auto& [command, info] : m_as->hooks) {
650 // GCC 8 actually has [[maybe_unused]] on a structured binding
651 // do what you'd expect. GCC 7 does not.
652 (void)command;
7c673cae
FG
653 ostringstream secname;
654 secname << "cmd" << setfill('0') << std::setw(3) << cmdnum;
9f95a23c 655 dump_cmd_and_help_to_json(f,
11fdf7f2 656 CEPH_FEATURES_ALL,
7c673cae 657 secname.str().c_str(),
11fdf7f2
TL
658 info.desc,
659 info.help);
7c673cae
FG
660 cmdnum++;
661 }
9f95a23c
TL
662 f->close_section(); // command_descriptions
663 return 0;
7c673cae
FG
664 }
665};
666
11fdf7f2 667bool AdminSocket::init(const std::string& path)
7c673cae
FG
668{
669 ldout(m_cct, 5) << "init " << path << dendl;
670
671 /* Set up things for the new thread */
672 std::string err;
673 int pipe_rd = -1, pipe_wr = -1;
9f95a23c 674 err = create_wakeup_pipe(&pipe_rd, &pipe_wr);
7c673cae
FG
675 if (!err.empty()) {
676 lderr(m_cct) << "AdminSocketConfigObs::init: error: " << err << dendl;
677 return false;
678 }
679 int sock_fd;
680 err = bind_and_listen(path, &sock_fd);
681 if (!err.empty()) {
682 lderr(m_cct) << "AdminSocketConfigObs::init: failed: " << err << dendl;
683 close(pipe_rd);
684 close(pipe_wr);
685 return false;
686 }
687
688 /* Create new thread */
689 m_sock_fd = sock_fd;
9f95a23c
TL
690 m_wakeup_rd_fd = pipe_rd;
691 m_wakeup_wr_fd = pipe_wr;
7c673cae
FG
692 m_path = path;
693
11fdf7f2 694 version_hook = std::make_unique<VersionHook>();
9f95a23c
TL
695 register_command("0", version_hook.get(), "");
696 register_command("version", version_hook.get(), "get ceph version");
697 register_command("git_version", version_hook.get(),
11fdf7f2
TL
698 "get git sha1");
699 help_hook = std::make_unique<HelpHook>(this);
9f95a23c 700 register_command("help", help_hook.get(),
11fdf7f2
TL
701 "list available commands");
702 getdescs_hook = std::make_unique<GetdescsHook>(this);
9f95a23c 703 register_command("get_command_descriptions",
11fdf7f2 704 getdescs_hook.get(), "list available commands");
7c673cae 705
11fdf7f2 706 th = make_named_thread("admin_socket", &AdminSocket::entry, this);
7c673cae
FG
707 add_cleanup_file(m_path.c_str());
708 return true;
709}
710
711void AdminSocket::shutdown()
712{
7c673cae
FG
713 // Under normal operation this is unlikely to occur. However for some unit
714 // tests, some object members are not initialized and so cannot be deleted
715 // without fault.
9f95a23c 716 if (m_wakeup_wr_fd < 0)
7c673cae
FG
717 return;
718
719 ldout(m_cct, 5) << "shutdown" << dendl;
9f95a23c 720 m_shutdown = true;
7c673cae 721
9f95a23c 722 auto err = destroy_wakeup_pipe();
7c673cae
FG
723 if (!err.empty()) {
724 lderr(m_cct) << "AdminSocket::shutdown: error: " << err << dendl;
725 }
726
11fdf7f2 727 retry_sys_call(::close, m_sock_fd);
7c673cae 728
11fdf7f2
TL
729 unregister_commands(version_hook.get());
730 version_hook.reset();
7c673cae 731
9f95a23c 732 unregister_commands(help_hook.get());
11fdf7f2 733 help_hook.reset();
7c673cae 734
9f95a23c 735 unregister_commands(getdescs_hook.get());
11fdf7f2 736 getdescs_hook.reset();
7c673cae 737
11fdf7f2 738 remove_cleanup_file(m_path);
7c673cae
FG
739 m_path.clear();
740}
9f95a23c
TL
741
742void AdminSocket::wakeup()
743{
744 // Send a byte to the wakeup pipe that the thread is listening to
745 char buf[1] = { 0x0 };
746 int r = safe_write(m_wakeup_wr_fd, buf, sizeof(buf));
747 (void)r;
748}