]>
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 | ||
44 | ||
45 | using std::ostringstream; | |
9f95a23c | 46 | using 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 | |
57 | template<typename F, typename... Args> | |
58 | inline 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 | ||
67 | static std::mutex cleanup_lock; | |
68 | static std::vector<std::string> cleanup_files; | |
7c673cae FG |
69 | static bool cleanup_atexit = false; |
70 | ||
11fdf7f2 TL |
71 | static 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 |
81 | void 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 |
89 | static 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 | 98 | AdminSocket::AdminSocket(CephContext *cct) |
11fdf7f2 TL |
99 | : m_cct(cct) |
100 | {} | |
7c673cae FG |
101 | |
102 | AdminSocket::~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 | 117 | std::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 | 132 | std::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 | ||
159 | std::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 | 228 | void 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 | ||
276 | void 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 | ||
288 | void 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 | 300 | void 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 | ||
395 | void 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 | ||
439 | int 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 |
470 | void 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 | 537 | void 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 | } | |
544 | void 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 | 552 | int 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 |
578 | void 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 |
597 | class VersionHook : public AdminSocketHook { |
598 | public: | |
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 | ||
621 | class HelpHook : public AdminSocketHook { | |
622 | AdminSocket *m_as; | |
623 | public: | |
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 | ||
639 | class GetdescsHook : public AdminSocketHook { | |
640 | AdminSocket *m_as; | |
641 | public: | |
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 | 667 | bool 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 | ||
711 | void 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 | |
742 | void 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 | } |