]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/admin_socket.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / 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
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
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14 #include <poll.h>
15 #include <sys/un.h>
16
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"
24
25
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"
30
31 #define dout_subsys ceph_subsys_asok
32 #undef dout_prefix
33 #define dout_prefix *_dout << "asok(" << (void*)m_cct << ") "
34
35
36 using std::ostringstream;
37
38 /*
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.
42 *
43 * This code makes things a little nicer by unlinking those dead sockets when
44 * the application exits normally.
45 */
46
47 template<typename F, typename... Args>
48 inline int retry_sys_call(F f, Args... args) {
49 int r;
50 do {
51 r = f(args...);
52 } while (r < 0 && errno == EINTR);
53 return r;
54 };
55
56
57 static std::mutex cleanup_lock;
58 static std::vector<std::string> cleanup_files;
59 static bool cleanup_atexit = false;
60
61 static void remove_cleanup_file(std::string_view file) {
62 std::unique_lock l(cleanup_lock);
63
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);
68 }
69 }
70
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());
75 }
76 cleanup_files.clear();
77 }
78
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;
85 }
86 }
87
88 AdminSocket::AdminSocket(CephContext *cct)
89 : m_cct(cct)
90 {}
91
92 AdminSocket::~AdminSocket()
93 {
94 shutdown();
95 }
96
97 /*
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]
101 *
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.
105 */
106
107 std::string AdminSocket::create_shutdown_pipe(int *pipe_rd, int *pipe_wr)
108 {
109 int pipefd[2];
110 if (pipe_cloexec(pipefd) < 0) {
111 int e = errno;
112 ostringstream oss;
113 oss << "AdminSocket::create_shutdown_pipe error: " << cpp_strerror(e);
114 return oss.str();
115 }
116
117 *pipe_rd = pipefd[0];
118 *pipe_wr = pipefd[1];
119 return "";
120 }
121
122 std::string AdminSocket::destroy_shutdown_pipe()
123 {
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));
127
128 // Close write end
129 retry_sys_call(::close, m_shutdown_wr_fd);
130 m_shutdown_wr_fd = -1;
131
132 if (ret != 0) {
133 ostringstream oss;
134 oss << "AdminSocket::destroy_shutdown_pipe error: failed to write"
135 "to thread shutdown pipe: error " << ret;
136 return oss.str();
137 }
138
139 th.join();
140
141 // Close read end. Doing this before join() blocks the listenter and prevents
142 // joining.
143 retry_sys_call(::close, m_shutdown_rd_fd);
144 m_shutdown_rd_fd = -1;
145
146 return "";
147 }
148
149 std::string AdminSocket::bind_and_listen(const std::string &sock_path, int *fd)
150 {
151 ldout(m_cct, 5) << "bind_and_listen " << sock_path << dendl;
152
153 struct sockaddr_un address;
154 if (sock_path.size() > sizeof(address.sun_path) - 1) {
155 ostringstream oss;
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);
160 return oss.str();
161 }
162 int sock_fd = socket_cloexec(PF_UNIX, SOCK_STREAM, 0);
163 if (sock_fd < 0) {
164 int err = errno;
165 ostringstream oss;
166 oss << "AdminSocket::bind_and_listen: "
167 << "failed to create socket: " << cpp_strerror(err);
168 return oss.str();
169 }
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) {
176 int err = errno;
177 if (err == EADDRINUSE) {
178 AdminSocketClient client(sock_path);
179 bool ok;
180 client.ping(&ok);
181 if (ok) {
182 ldout(m_cct, 20) << "socket " << sock_path << " is in use" << dendl;
183 err = EEXIST;
184 } else {
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) {
189 err = 0;
190 } else {
191 err = errno;
192 }
193 }
194 }
195 if (err != 0) {
196 ostringstream oss;
197 oss << "AdminSocket::bind_and_listen: "
198 << "failed to bind the UNIX domain socket to '" << sock_path
199 << "': " << cpp_strerror(err);
200 close(sock_fd);
201 return oss.str();
202 }
203 }
204 if (listen(sock_fd, 5) != 0) {
205 int err = errno;
206 ostringstream oss;
207 oss << "AdminSocket::bind_and_listen: "
208 << "failed to listen to socket: " << cpp_strerror(err);
209 close(sock_fd);
210 retry_sys_call(::unlink, sock_path.c_str());
211 return oss.str();
212 }
213 *fd = sock_fd;
214 return "";
215 }
216
217 void AdminSocket::entry() noexcept
218 {
219 ldout(m_cct, 5) << "entry start" << dendl;
220 while (true) {
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;
227
228 int ret = poll(fds, 2, -1);
229 if (ret < 0) {
230 int err = errno;
231 if (err == EINTR) {
232 continue;
233 }
234 lderr(m_cct) << "AdminSocket: poll(2) error: '"
235 << cpp_strerror(err) << dendl;
236 return;
237 }
238
239 if (fds[0].revents & POLLIN) {
240 // Send out some data
241 do_accept();
242 }
243 if (fds[1].revents & POLLIN) {
244 // Parent wants us to shut down
245 return;
246 }
247 }
248 ldout(m_cct, 5) << "entry exit" << dendl;
249 }
250
251 void AdminSocket::chown(uid_t uid, gid_t gid)
252 {
253 if (m_sock_fd >= 0) {
254 int r = ::chown(m_path.c_str(), uid, gid);
255 if (r < 0) {
256 r = -errno;
257 lderr(m_cct) << "AdminSocket: failed to chown socket: "
258 << cpp_strerror(r) << dendl;
259 }
260 }
261 }
262
263 void AdminSocket::chmod(mode_t mode)
264 {
265 if (m_sock_fd >= 0) {
266 int r = ::chmod(m_path.c_str(), mode);
267 if (r < 0) {
268 r = -errno;
269 lderr(m_cct) << "AdminSocket: failed to chmod socket: "
270 << cpp_strerror(r) << dendl;
271 }
272 }
273 }
274
275 bool AdminSocket::do_accept()
276 {
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,
281 &address_length);
282 if (connection_fd < 0) {
283 int err = errno;
284 lderr(m_cct) << "AdminSocket: do_accept error: '"
285 << cpp_strerror(err) << dendl;
286 return false;
287 }
288 ldout(m_cct, 30) << "AdminSocket: finished accept" << dendl;
289
290 char cmd[1024];
291 unsigned pos = 0;
292 string c;
293 while (1) {
294 int ret = safe_read(connection_fd, &cmd[pos], 1);
295 if (ret <= 0) {
296 if (ret < 0) {
297 lderr(m_cct) << "AdminSocket: error reading request code: "
298 << cpp_strerror(ret) << dendl;
299 }
300 retry_sys_call(::close, connection_fd);
301 return false;
302 }
303 if (cmd[0] == '\0') {
304 // old protocol: __be32
305 if (pos == 3 && cmd[0] == '\0') {
306 switch (cmd[3]) {
307 case 0:
308 c = "0";
309 break;
310 case 1:
311 c = "perfcounters_dump";
312 break;
313 case 2:
314 c = "perfcounters_schema";
315 break;
316 default:
317 c = "foo";
318 break;
319 }
320 break;
321 }
322 } else {
323 // new protocol: null or \n terminated string
324 if (cmd[pos] == '\n' || cmd[pos] == '\0') {
325 cmd[pos] = '\0';
326 c = cmd;
327 break;
328 }
329 }
330 if (++pos >= sizeof(cmd)) {
331 lderr(m_cct) << "AdminSocket: error reading request too long" << dendl;
332 retry_sys_call(::close, connection_fd);
333 return false;
334 }
335 }
336
337 bool rval = false;
338
339 cmdmap_t cmdmap;
340 string format;
341 vector<string> cmdvec;
342 stringstream errss;
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);
347 return false;
348 }
349 try {
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);
354 return false;
355 }
356 if (format != "json" && format != "json-pretty" &&
357 format != "xml" && format != "xml-pretty")
358 format = "json-pretty";
359
360 std::unique_lock l(lock);
361 decltype(hooks)::iterator p;
362 string match = c;
363 while (match.size()) {
364 p = hooks.find(match);
365 if (p != hooks.cend())
366 break;
367
368 // drop right-most word
369 size_t pos = match.rfind(' ');
370 if (pos == std::string::npos) {
371 match.clear(); // we fail
372 break;
373 } else {
374 match.resize(pos);
375 }
376 }
377
378 bufferlist out;
379 if (p == hooks.cend()) {
380 lderr(m_cct) << "AdminSocket: request '" << c << "' not defined" << dendl;
381 } else {
382 string args;
383 if (match != c) {
384 args = c.substr(match.length() + 1);
385 }
386
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.
391 in_hook = true;
392 auto match_hook = p->second.hook;
393 l.unlock();
394 bool success = (validate(match, cmdmap, out) &&
395 match_hook->call(match, cmdmap, format, out));
396 l.lock();
397 in_hook = false;
398 in_hook_cond.notify_all();
399
400 if (!success) {
401 ldout(m_cct, 0) << "AdminSocket: request '" << match << "' args '" << args
402 << "' to " << match_hook << " failed" << dendl;
403 out.append("failed");
404 } else {
405 ldout(m_cct, 5) << "AdminSocket: request '" << match << "' '" << args
406 << "' to " << match_hook
407 << " returned " << out.length() << " bytes" << dendl;
408 }
409 uint32_t len = htonl(out.length());
410 int ret = safe_write(connection_fd, &len, sizeof(len));
411 if (ret < 0) {
412 lderr(m_cct) << "AdminSocket: error writing response length "
413 << cpp_strerror(ret) << dendl;
414 } else {
415 if (out.write_fd(connection_fd) >= 0)
416 rval = true;
417 }
418 }
419 l.unlock();
420
421 retry_sys_call(::close, connection_fd);
422 return rval;
423 }
424
425 bool AdminSocket::validate(const std::string& command,
426 const cmdmap_t& cmdmap,
427 bufferlist& out) const
428 {
429 stringstream os;
430 if (validate_cmd(m_cct, hooks.at(command).desc, cmdmap, os)) {
431 return true;
432 } else {
433 out.append(os);
434 return false;
435 }
436 }
437
438 int AdminSocket::register_command(std::string_view command,
439 std::string_view cmddesc,
440 AdminSocketHook *hook,
441 std::string_view help)
442 {
443 int ret;
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;
449 ret = -EEXIST;
450 } else {
451 ldout(m_cct, 5) << "register_command " << command << " hook " << hook
452 << dendl;
453 hooks.emplace_hint(i,
454 std::piecewise_construct,
455 std::forward_as_tuple(command),
456 std::forward_as_tuple(hook, cmddesc, help));
457 ret = 0;
458 }
459 return ret;
460 }
461
462 int AdminSocket::unregister_command(std::string_view command)
463 {
464 int ret;
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;
469
470 // If we are currently processing a command, wait for it to
471 // complete in case it referenced the hook that we are
472 // unregistering.
473 in_hook_cond.wait(l, [this]() { return !in_hook; });
474
475 hooks.erase(i);
476
477
478 ret = 0;
479 } else {
480 ldout(m_cct, 5) << "unregister_command " << command << " ENOENT" << dendl;
481 ret = -ENOENT;
482 }
483 return ret;
484 }
485
486 void AdminSocket::unregister_commands(const AdminSocketHook *hook)
487 {
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;
493
494 // If we are currently processing a command, wait for it to
495 // complete in case it referenced the hook that we are
496 // unregistering.
497 in_hook_cond.wait(l, [this]() { return !in_hook; });
498 hooks.erase(i++);
499 } else {
500 i++;
501 }
502 }
503 }
504
505 class VersionHook : public AdminSocketHook {
506 public:
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);
511 } else {
512 JSONFormatter jf;
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());
520 }
521 ostringstream ss;
522 jf.close_section();
523 jf.enable_line_break();
524 jf.flush(ss);
525 out.append(ss.str());
526 }
527 return true;
528 }
529 };
530
531 class HelpHook : public AdminSocketHook {
532 AdminSocket *m_as;
533 public:
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,
539 "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);
544 }
545 f->close_section();
546 ostringstream ss;
547 f->flush(ss);
548 out.append(ss.str());
549 return true;
550 }
551 };
552
553 class GetdescsHook : public AdminSocketHook {
554 AdminSocket *m_as;
555 public:
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 {
559 int cmdnum = 0;
560 JSONFormatter jf;
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.
565 (void)command;
566 ostringstream secname;
567 secname << "cmd" << setfill('0') << std::setw(3) << cmdnum;
568 dump_cmd_and_help_to_json(&jf,
569 CEPH_FEATURES_ALL,
570 secname.str().c_str(),
571 info.desc,
572 info.help);
573 cmdnum++;
574 }
575 jf.close_section(); // command_descriptions
576 jf.enable_line_break();
577 ostringstream ss;
578 jf.flush(ss);
579 out.append(ss.str());
580 return true;
581 }
582 };
583
584 bool AdminSocket::init(const std::string& path)
585 {
586 ldout(m_cct, 5) << "init " << path << dendl;
587
588 /* Set up things for the new thread */
589 std::string err;
590 int pipe_rd = -1, pipe_wr = -1;
591 err = create_shutdown_pipe(&pipe_rd, &pipe_wr);
592 if (!err.empty()) {
593 lderr(m_cct) << "AdminSocketConfigObs::init: error: " << err << dendl;
594 return false;
595 }
596 int sock_fd;
597 err = bind_and_listen(path, &sock_fd);
598 if (!err.empty()) {
599 lderr(m_cct) << "AdminSocketConfigObs::init: failed: " << err << dendl;
600 close(pipe_rd);
601 close(pipe_wr);
602 return false;
603 }
604
605 /* Create new thread */
606 m_sock_fd = sock_fd;
607 m_shutdown_rd_fd = pipe_rd;
608 m_shutdown_wr_fd = pipe_wr;
609 m_path = path;
610
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(),
615 "get git sha1");
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");
622
623 th = make_named_thread("admin_socket", &AdminSocket::entry, this);
624 add_cleanup_file(m_path.c_str());
625 return true;
626 }
627
628 void AdminSocket::shutdown()
629 {
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
632 // without fault.
633 if (m_shutdown_wr_fd < 0)
634 return;
635
636 ldout(m_cct, 5) << "shutdown" << dendl;
637
638 auto err = destroy_shutdown_pipe();
639 if (!err.empty()) {
640 lderr(m_cct) << "AdminSocket::shutdown: error: " << err << dendl;
641 }
642
643 retry_sys_call(::close, m_sock_fd);
644
645 unregister_commands(version_hook.get());
646 version_hook.reset();
647
648 unregister_command("help");
649 help_hook.reset();
650
651 unregister_command("get_command_descriptions");
652 getdescs_hook.reset();
653
654 remove_cleanup_file(m_path);
655 m_path.clear();
656 }