]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/admin_socket.cc
update sources to 12.2.10
[ceph.git] / ceph / src / common / admin_socket.cc
CommitLineData
7c673cae
FG
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
7c673cae
FG
15#include "common/admin_socket.h"
16#include "common/admin_socket_client.h"
7c673cae 17#include "common/errno.h"
7c673cae
FG
18#include "common/safe_io.h"
19#include "common/version.h"
31f18b77 20#include "include/compat.h"
7c673cae 21
7c673cae 22#include <poll.h>
7c673cae 23#include <sys/un.h>
7c673cae 24
31f18b77
FG
25// re-include our assert to clobber the system one; fix dout:
26#include "include/assert.h"
91327a77
AA
27#include "include/compat.h"
28#include "include/sock_compat.h"
7c673cae
FG
29
30#define dout_subsys ceph_subsys_asok
31#undef dout_prefix
32#define dout_prefix *_dout << "asok(" << (void*)m_cct << ") "
33
34
35using std::ostringstream;
36
37/*
38 * UNIX domain sockets created by an application persist even after that
39 * application closes, unless they're explicitly unlinked. This is because the
40 * directory containing the socket keeps a reference to the socket.
41 *
42 * This code makes things a little nicer by unlinking those dead sockets when
43 * the application exits normally.
44 */
45static pthread_mutex_t cleanup_lock = PTHREAD_MUTEX_INITIALIZER;
46static std::vector <const char*> cleanup_files;
47static bool cleanup_atexit = false;
48
49static void remove_cleanup_file(const char *file)
50{
51 pthread_mutex_lock(&cleanup_lock);
52 VOID_TEMP_FAILURE_RETRY(unlink(file));
53 for (std::vector <const char*>::iterator i = cleanup_files.begin();
54 i != cleanup_files.end(); ++i) {
55 if (strcmp(file, *i) == 0) {
56 free((void*)*i);
57 cleanup_files.erase(i);
58 break;
59 }
60 }
61 pthread_mutex_unlock(&cleanup_lock);
62}
63
64static void remove_all_cleanup_files()
65{
66 pthread_mutex_lock(&cleanup_lock);
67 for (std::vector <const char*>::iterator i = cleanup_files.begin();
68 i != cleanup_files.end(); ++i) {
69 VOID_TEMP_FAILURE_RETRY(unlink(*i));
70 free((void*)*i);
71 }
72 cleanup_files.clear();
73 pthread_mutex_unlock(&cleanup_lock);
74}
75
76static void add_cleanup_file(const char *file)
77{
78 char *fname = strdup(file);
79 if (!fname)
80 return;
81 pthread_mutex_lock(&cleanup_lock);
82 cleanup_files.push_back(fname);
83 if (!cleanup_atexit) {
84 atexit(remove_all_cleanup_files);
85 cleanup_atexit = true;
86 }
87 pthread_mutex_unlock(&cleanup_lock);
88}
89
90
91AdminSocket::AdminSocket(CephContext *cct)
92 : m_cct(cct),
93 m_sock_fd(-1),
94 m_shutdown_rd_fd(-1),
95 m_shutdown_wr_fd(-1),
96 in_hook(false),
97 m_lock("AdminSocket::m_lock"),
98 m_version_hook(NULL),
99 m_help_hook(NULL),
100 m_getdescs_hook(NULL)
101{
102}
103
104AdminSocket::~AdminSocket()
105{
106 shutdown();
107}
108
109/*
110 * This thread listens on the UNIX domain socket for incoming connections.
111 * It only handles one connection at a time at the moment. All I/O is nonblocking,
112 * so that we can implement sensible timeouts. [TODO: make all I/O nonblocking]
113 *
114 * This thread also listens to m_shutdown_rd_fd. If there is any data sent to this
115 * pipe, the thread terminates itself gracefully, allowing the
116 * AdminSocketConfigObs class to join() it.
117 */
118
119#define PFL_SUCCESS ((void*)(intptr_t)0)
120#define PFL_FAIL ((void*)(intptr_t)1)
121
122std::string AdminSocket::create_shutdown_pipe(int *pipe_rd, int *pipe_wr)
123{
124 int pipefd[2];
91327a77
AA
125 if (pipe_cloexec(pipefd) < 0) {
126 int e = errno;
7c673cae 127 ostringstream oss;
91327a77 128 oss << "AdminSocket::create_shutdown_pipe error: " << cpp_strerror(e);
7c673cae
FG
129 return oss.str();
130 }
131
132 *pipe_rd = pipefd[0];
133 *pipe_wr = pipefd[1];
134 return "";
135}
136
137std::string AdminSocket::destroy_shutdown_pipe()
138{
139 // Send a byte to the shutdown pipe that the thread is listening to
140 char buf[1] = { 0x0 };
141 int ret = safe_write(m_shutdown_wr_fd, buf, sizeof(buf));
142
143 // Close write end
144 VOID_TEMP_FAILURE_RETRY(close(m_shutdown_wr_fd));
145 m_shutdown_wr_fd = -1;
146
147 if (ret != 0) {
148 ostringstream oss;
149 oss << "AdminSocket::destroy_shutdown_pipe error: failed to write"
150 "to thread shutdown pipe: error " << ret;
151 return oss.str();
152 }
153
154 join();
155
156 // Close read end. Doing this before join() blocks the listenter and prevents
157 // joining.
158 VOID_TEMP_FAILURE_RETRY(close(m_shutdown_rd_fd));
159 m_shutdown_rd_fd = -1;
160
161 return "";
162}
163
164std::string AdminSocket::bind_and_listen(const std::string &sock_path, int *fd)
165{
166 ldout(m_cct, 5) << "bind_and_listen " << sock_path << dendl;
167
168 struct sockaddr_un address;
169 if (sock_path.size() > sizeof(address.sun_path) - 1) {
170 ostringstream oss;
171 oss << "AdminSocket::bind_and_listen: "
172 << "The UNIX domain socket path " << sock_path << " is too long! The "
173 << "maximum length on this system is "
174 << (sizeof(address.sun_path) - 1);
175 return oss.str();
176 }
91327a77 177 int sock_fd = socket_cloexec(PF_UNIX, SOCK_STREAM, 0);
7c673cae
FG
178 if (sock_fd < 0) {
179 int err = errno;
180 ostringstream oss;
181 oss << "AdminSocket::bind_and_listen: "
182 << "failed to create socket: " << cpp_strerror(err);
183 return oss.str();
184 }
7c673cae
FG
185 memset(&address, 0, sizeof(struct sockaddr_un));
186 address.sun_family = AF_UNIX;
187 snprintf(address.sun_path, sizeof(address.sun_path),
188 "%s", sock_path.c_str());
189 if (::bind(sock_fd, (struct sockaddr*)&address,
190 sizeof(struct sockaddr_un)) != 0) {
191 int err = errno;
192 if (err == EADDRINUSE) {
193 AdminSocketClient client(sock_path);
194 bool ok;
195 client.ping(&ok);
196 if (ok) {
197 ldout(m_cct, 20) << "socket " << sock_path << " is in use" << dendl;
198 err = EEXIST;
199 } else {
200 ldout(m_cct, 20) << "unlink stale file " << sock_path << dendl;
201 VOID_TEMP_FAILURE_RETRY(unlink(sock_path.c_str()));
202 if (::bind(sock_fd, (struct sockaddr*)&address,
203 sizeof(struct sockaddr_un)) == 0) {
204 err = 0;
205 } else {
206 err = errno;
207 }
208 }
209 }
210 if (err != 0) {
211 ostringstream oss;
212 oss << "AdminSocket::bind_and_listen: "
213 << "failed to bind the UNIX domain socket to '" << sock_path
214 << "': " << cpp_strerror(err);
215 close(sock_fd);
216 return oss.str();
217 }
218 }
219 if (listen(sock_fd, 5) != 0) {
220 int err = errno;
221 ostringstream oss;
222 oss << "AdminSocket::bind_and_listen: "
223 << "failed to listen to socket: " << cpp_strerror(err);
224 close(sock_fd);
225 VOID_TEMP_FAILURE_RETRY(unlink(sock_path.c_str()));
226 return oss.str();
227 }
228 *fd = sock_fd;
229 return "";
230}
231
232void* AdminSocket::entry()
233{
234 ldout(m_cct, 5) << "entry start" << dendl;
235 while (true) {
236 struct pollfd fds[2];
237 memset(fds, 0, sizeof(fds));
238 fds[0].fd = m_sock_fd;
239 fds[0].events = POLLIN | POLLRDBAND;
240 fds[1].fd = m_shutdown_rd_fd;
241 fds[1].events = POLLIN | POLLRDBAND;
242
243 int ret = poll(fds, 2, -1);
244 if (ret < 0) {
245 int err = errno;
246 if (err == EINTR) {
247 continue;
248 }
249 lderr(m_cct) << "AdminSocket: poll(2) error: '"
250 << cpp_strerror(err) << dendl;
251 return PFL_FAIL;
252 }
253
254 if (fds[0].revents & POLLIN) {
255 // Send out some data
256 do_accept();
257 }
258 if (fds[1].revents & POLLIN) {
259 // Parent wants us to shut down
260 return PFL_SUCCESS;
261 }
262 }
263 ldout(m_cct, 5) << "entry exit" << dendl;
264}
265
266void AdminSocket::chown(uid_t uid, gid_t gid)
267{
268 if (m_sock_fd >= 0) {
269 int r = ::chown(m_path.c_str(), uid, gid);
270 if (r < 0) {
271 r = -errno;
272 lderr(m_cct) << "AdminSocket: failed to chown socket: "
273 << cpp_strerror(r) << dendl;
274 }
275 }
276}
277
278void AdminSocket::chmod(mode_t mode)
279{
280 if (m_sock_fd >= 0) {
281 int r = ::chmod(m_path.c_str(), mode);
282 if (r < 0) {
283 r = -errno;
284 lderr(m_cct) << "AdminSocket: failed to chmod socket: "
285 << cpp_strerror(r) << dendl;
286 }
287 }
288}
289
290bool AdminSocket::do_accept()
291{
292 struct sockaddr_un address;
293 socklen_t address_length = sizeof(address);
294 ldout(m_cct, 30) << "AdminSocket: calling accept" << dendl;
91327a77 295 int connection_fd = accept_cloexec(m_sock_fd, (struct sockaddr*) &address,
7c673cae 296 &address_length);
7c673cae
FG
297 if (connection_fd < 0) {
298 int err = errno;
299 lderr(m_cct) << "AdminSocket: do_accept error: '"
300 << cpp_strerror(err) << dendl;
301 return false;
302 }
91327a77 303 ldout(m_cct, 30) << "AdminSocket: finished accept" << dendl;
7c673cae
FG
304
305 char cmd[1024];
306 unsigned pos = 0;
307 string c;
308 while (1) {
309 int ret = safe_read(connection_fd, &cmd[pos], 1);
310 if (ret <= 0) {
311 if (ret < 0) {
312 lderr(m_cct) << "AdminSocket: error reading request code: "
313 << cpp_strerror(ret) << dendl;
314 }
315 VOID_TEMP_FAILURE_RETRY(close(connection_fd));
316 return false;
317 }
318 //ldout(m_cct, 0) << "AdminSocket read byte " << (int)cmd[pos] << " pos " << pos << dendl;
319 if (cmd[0] == '\0') {
320 // old protocol: __be32
321 if (pos == 3 && cmd[0] == '\0') {
322 switch (cmd[3]) {
323 case 0:
324 c = "0";
325 break;
326 case 1:
327 c = "perfcounters_dump";
328 break;
329 case 2:
330 c = "perfcounters_schema";
331 break;
332 default:
333 c = "foo";
334 break;
335 }
336 break;
337 }
338 } else {
339 // new protocol: null or \n terminated string
340 if (cmd[pos] == '\n' || cmd[pos] == '\0') {
341 cmd[pos] = '\0';
342 c = cmd;
343 break;
344 }
345 }
346 if (++pos >= sizeof(cmd)) {
347 lderr(m_cct) << "AdminSocket: error reading request too long" << dendl;
348 VOID_TEMP_FAILURE_RETRY(close(connection_fd));
349 return false;
350 }
351 }
352
353 bool rval = false;
354
355 map<string, cmd_vartype> cmdmap;
356 string format;
357 vector<string> cmdvec;
358 stringstream errss;
359 cmdvec.push_back(cmd);
360 if (!cmdmap_from_json(cmdvec, &cmdmap, errss)) {
b32b8144 361 ldout(m_cct, 0) << "AdminSocket: " << errss.str() << dendl;
7c673cae
FG
362 VOID_TEMP_FAILURE_RETRY(close(connection_fd));
363 return false;
364 }
365 cmd_getval(m_cct, cmdmap, "format", format);
366 if (format != "json" && format != "json-pretty" &&
367 format != "xml" && format != "xml-pretty")
368 format = "json-pretty";
369 cmd_getval(m_cct, cmdmap, "prefix", c);
370
371 m_lock.Lock();
372 map<string,AdminSocketHook*>::iterator p;
373 string match = c;
374 while (match.size()) {
375 p = m_hooks.find(match);
376 if (p != m_hooks.end())
377 break;
378
379 // drop right-most word
380 size_t pos = match.rfind(' ');
381 if (pos == std::string::npos) {
382 match.clear(); // we fail
383 break;
384 } else {
385 match.resize(pos);
386 }
387 }
388
389 bufferlist out;
390 if (p == m_hooks.end()) {
391 lderr(m_cct) << "AdminSocket: request '" << c << "' not defined" << dendl;
392 } else {
393 string args;
394 if (match != c) {
395 args = c.substr(match.length() + 1);
396 }
397
398 // Drop lock to avoid cycles in cases where the hook takes
399 // the same lock that was held during calls to register/unregister,
400 // and set in_hook to allow unregister to wait for us before
401 // removing this hook.
402 in_hook = true;
403 auto match_hook = p->second;
404 m_lock.Unlock();
405 bool success = match_hook->call(match, cmdmap, format, out);
406 m_lock.Lock();
407 in_hook = false;
408 in_hook_cond.Signal();
409
410 if (!success) {
411 ldout(m_cct, 0) << "AdminSocket: request '" << match << "' args '" << args
412 << "' to " << p->second << " failed" << dendl;
413 out.append("failed");
414 } else {
415 ldout(m_cct, 5) << "AdminSocket: request '" << match << "' '" << args
416 << "' to " << p->second
417 << " returned " << out.length() << " bytes" << dendl;
418 }
419 uint32_t len = htonl(out.length());
420 int ret = safe_write(connection_fd, &len, sizeof(len));
421 if (ret < 0) {
422 lderr(m_cct) << "AdminSocket: error writing response length "
423 << cpp_strerror(ret) << dendl;
424 } else {
425 if (out.write_fd(connection_fd) >= 0)
426 rval = true;
427 }
428 }
429 m_lock.Unlock();
430
431 VOID_TEMP_FAILURE_RETRY(close(connection_fd));
432 return rval;
433}
434
435int AdminSocket::register_command(std::string command, std::string cmddesc, AdminSocketHook *hook, std::string help)
436{
437 int ret;
438 m_lock.Lock();
439 if (m_hooks.count(command)) {
440 ldout(m_cct, 5) << "register_command " << command << " hook " << hook << " EEXIST" << dendl;
441 ret = -EEXIST;
442 } else {
443 ldout(m_cct, 5) << "register_command " << command << " hook " << hook << dendl;
444 m_hooks[command] = hook;
445 m_descs[command] = cmddesc;
446 m_help[command] = help;
447 ret = 0;
448 }
449 m_lock.Unlock();
450 return ret;
451}
452
453int AdminSocket::unregister_command(std::string command)
454{
455 int ret;
456 m_lock.Lock();
457 if (m_hooks.count(command)) {
458 ldout(m_cct, 5) << "unregister_command " << command << dendl;
459 m_hooks.erase(command);
460 m_descs.erase(command);
461 m_help.erase(command);
462
463 // If we are currently processing a command, wait for it to
464 // complete in case it referenced the hook that we are
465 // unregistering.
466 if (in_hook) {
467 in_hook_cond.Wait(m_lock);
468 }
469
470 ret = 0;
471 } else {
472 ldout(m_cct, 5) << "unregister_command " << command << " ENOENT" << dendl;
473 ret = -ENOENT;
474 }
475 m_lock.Unlock();
476 return ret;
477}
478
479class VersionHook : public AdminSocketHook {
480public:
481 bool call(std::string command, cmdmap_t &cmdmap, std::string format,
482 bufferlist& out) override {
483 if (command == "0") {
484 out.append(CEPH_ADMIN_SOCK_VERSION);
485 } else {
486 JSONFormatter jf;
487 jf.open_object_section("version");
31f18b77 488 if (command == "version") {
7c673cae 489 jf.dump_string("version", ceph_version_to_str());
31f18b77
FG
490 jf.dump_string("release", ceph_release_name(ceph_release()));
491 jf.dump_string("release_type", ceph_release_type());
492 } else if (command == "git_version") {
7c673cae 493 jf.dump_string("git_version", git_version_to_str());
31f18b77 494 }
7c673cae
FG
495 ostringstream ss;
496 jf.close_section();
497 jf.flush(ss);
498 out.append(ss.str());
499 }
500 return true;
501 }
502};
503
504class HelpHook : public AdminSocketHook {
505 AdminSocket *m_as;
506public:
507 explicit HelpHook(AdminSocket *as) : m_as(as) {}
508 bool call(string command, cmdmap_t &cmdmap, string format, bufferlist& out) override {
509 Formatter *f = Formatter::create(format, "json-pretty", "json-pretty");
510 f->open_object_section("help");
511 for (map<string,string>::iterator p = m_as->m_help.begin();
512 p != m_as->m_help.end();
513 ++p) {
514 if (p->second.length())
515 f->dump_string(p->first.c_str(), p->second);
516 }
517 f->close_section();
518 ostringstream ss;
519 f->flush(ss);
520 out.append(ss.str());
521 delete f;
522 return true;
523 }
524};
525
526class GetdescsHook : public AdminSocketHook {
527 AdminSocket *m_as;
528public:
529 explicit GetdescsHook(AdminSocket *as) : m_as(as) {}
530 bool call(string command, cmdmap_t &cmdmap, string format, bufferlist& out) override {
531 int cmdnum = 0;
181888fb 532 JSONFormatter jf;
7c673cae
FG
533 jf.open_object_section("command_descriptions");
534 for (map<string,string>::iterator p = m_as->m_descs.begin();
535 p != m_as->m_descs.end();
536 ++p) {
537 ostringstream secname;
538 secname << "cmd" << setfill('0') << std::setw(3) << cmdnum;
539 dump_cmd_and_help_to_json(&jf,
540 secname.str().c_str(),
541 p->second.c_str(),
542 m_as->m_help[p->first]);
543 cmdnum++;
544 }
545 jf.close_section(); // command_descriptions
181888fb 546 jf.enable_line_break();
7c673cae
FG
547 ostringstream ss;
548 jf.flush(ss);
549 out.append(ss.str());
550 return true;
551 }
552};
553
554bool AdminSocket::init(const std::string &path)
555{
556 ldout(m_cct, 5) << "init " << path << dendl;
557
558 /* Set up things for the new thread */
559 std::string err;
560 int pipe_rd = -1, pipe_wr = -1;
561 err = create_shutdown_pipe(&pipe_rd, &pipe_wr);
562 if (!err.empty()) {
563 lderr(m_cct) << "AdminSocketConfigObs::init: error: " << err << dendl;
564 return false;
565 }
566 int sock_fd;
567 err = bind_and_listen(path, &sock_fd);
568 if (!err.empty()) {
569 lderr(m_cct) << "AdminSocketConfigObs::init: failed: " << err << dendl;
570 close(pipe_rd);
571 close(pipe_wr);
572 return false;
573 }
574
575 /* Create new thread */
576 m_sock_fd = sock_fd;
577 m_shutdown_rd_fd = pipe_rd;
578 m_shutdown_wr_fd = pipe_wr;
579 m_path = path;
580
581 m_version_hook = new VersionHook;
582 register_command("0", "0", m_version_hook, "");
583 register_command("version", "version", m_version_hook, "get ceph version");
584 register_command("git_version", "git_version", m_version_hook, "get git sha1");
585 m_help_hook = new HelpHook(this);
586 register_command("help", "help", m_help_hook, "list available commands");
587 m_getdescs_hook = new GetdescsHook(this);
588 register_command("get_command_descriptions", "get_command_descriptions",
589 m_getdescs_hook, "list available commands");
590
591 create("admin_socket");
592 add_cleanup_file(m_path.c_str());
593 return true;
594}
595
596void AdminSocket::shutdown()
597{
598 std::string err;
599
600 // Under normal operation this is unlikely to occur. However for some unit
601 // tests, some object members are not initialized and so cannot be deleted
602 // without fault.
603 if (m_shutdown_wr_fd < 0)
604 return;
605
606 ldout(m_cct, 5) << "shutdown" << dendl;
607
608 err = destroy_shutdown_pipe();
609 if (!err.empty()) {
610 lderr(m_cct) << "AdminSocket::shutdown: error: " << err << dendl;
611 }
612
613 VOID_TEMP_FAILURE_RETRY(close(m_sock_fd));
614
615 unregister_command("version");
616 unregister_command("git_version");
617 unregister_command("0");
618 delete m_version_hook;
619
620 unregister_command("help");
621 delete m_help_hook;
622
623 unregister_command("get_command_descriptions");
624 delete m_getdescs_hook;
625
626 remove_cleanup_file(m_path.c_str());
627 m_path.clear();
628}