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