]>
Commit | Line | Data |
---|---|---|
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 | 44 | using namespace std::literals; |
7c673cae FG |
45 | |
46 | using std::ostringstream; | |
f67539c2 TL |
47 | using std::string; |
48 | using std::stringstream; | |
49 | ||
9f95a23c | 50 | using namespace TOPNSPC::common; |
7c673cae | 51 | |
f67539c2 TL |
52 | using ceph::bufferlist; |
53 | using ceph::cref_t; | |
54 | using 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 | |
66 | template<typename F, typename... Args> | |
67 | inline 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 | ||
76 | static std::mutex cleanup_lock; | |
77 | static std::vector<std::string> cleanup_files; | |
7c673cae FG |
78 | static bool cleanup_atexit = false; |
79 | ||
11fdf7f2 TL |
80 | static 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 |
90 | void 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 |
98 | static 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 | 107 | AdminSocket::AdminSocket(CephContext *cct) |
11fdf7f2 TL |
108 | : m_cct(cct) |
109 | {} | |
7c673cae FG |
110 | |
111 | AdminSocket::~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 | 126 | std::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 | 145 | std::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 | ||
172 | std::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 | 241 | void 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 | ||
289 | void 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 | ||
301 | void 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 | 313 | void 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 | ||
409 | void 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 | ||
453 | int 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 |
484 | void 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 |
539 | std::pair<int, AdminSocketHook*> |
540 | AdminSocket::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 | 563 | void 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 | } | |
570 | void 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 | 578 | int 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 |
604 | void 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 |
623 | class VersionHook : public AdminSocketHook { |
624 | public: | |
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 | ||
647 | class HelpHook : public AdminSocketHook { | |
648 | AdminSocket *m_as; | |
649 | public: | |
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 | ||
665 | class GetdescsHook : public AdminSocketHook { | |
666 | AdminSocket *m_as; | |
667 | public: | |
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 | 693 | bool 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 | ||
750 | void 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 | |
781 | void 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 | } |