summaryrefslogtreecommitdiffstats
path: root/src/global
diff options
context:
space:
mode:
Diffstat (limited to 'src/global')
-rw-r--r--src/global/CMakeLists.txt18
-rw-r--r--src/global/global_context.cc70
-rw-r--r--src/global/global_context.h52
-rw-r--r--src/global/global_init.cc610
-rw-r--r--src/global/global_init.h108
-rw-r--r--src/global/pidfile.cc247
-rw-r--r--src/global/pidfile.h28
-rw-r--r--src/global/signal_handler.cc665
-rw-r--r--src/global/signal_handler.h56
9 files changed, 1854 insertions, 0 deletions
diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt
new file mode 100644
index 00000000..704e0c7d
--- /dev/null
+++ b/src/global/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(libglobal_srcs
+ global_init.cc
+ pidfile.cc
+ signal_handler.cc)
+set(global_common_files
+ global_context.cc)
+add_library(global_common_objs OBJECT ${global_common_files})
+add_library(libglobal_objs OBJECT ${libglobal_srcs})
+
+add_library(global-static STATIC
+ $<TARGET_OBJECTS:libglobal_objs>
+ $<TARGET_OBJECTS:global_common_objs>)
+target_link_libraries(global-static common)
+
+add_library(global STATIC
+ $<TARGET_OBJECTS:libglobal_objs>
+ $<TARGET_OBJECTS:global_common_objs>)
+target_link_libraries(global ceph-common ${EXTRALIBS})
diff --git a/src/global/global_context.cc b/src/global/global_context.cc
new file mode 100644
index 00000000..1841317f
--- /dev/null
+++ b/src/global/global_context.cc
@@ -0,0 +1,70 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "common/ceph_context.h"
+#include "global/global_context.h"
+
+#include <string.h>
+
+
+/*
+ * Global variables for use from process context.
+ */
+CephContext *g_ceph_context = NULL;
+ConfigProxy& g_conf() {
+#ifdef WITH_SEASTAR
+ return ceph::common::local_conf();
+#else
+ return g_ceph_context->_conf;
+#endif
+}
+
+const char *g_assert_file = 0;
+int g_assert_line = 0;
+const char *g_assert_func = 0;
+const char *g_assert_condition = 0;
+unsigned long long g_assert_thread = 0;
+char g_assert_thread_name[4096] = { 0 };
+char g_assert_msg[8096] = { 0 };
+char g_process_name[NAME_MAX + 1] = { 0 };
+
+bool g_eio = false;
+char g_eio_devname[1024] = { 0 };
+char g_eio_path[PATH_MAX] = { 0 };
+int g_eio_error = 0; // usually -EIO...
+int g_eio_iotype = 0; // 1 = read, 2 = write
+unsigned long long g_eio_offset = 0;
+unsigned long long g_eio_length = 0;
+
+int note_io_error_event(
+ const char *devname,
+ const char *path,
+ int error,
+ int iotype,
+ unsigned long long offset,
+ unsigned long long length)
+{
+ g_eio = true;
+ if (devname) {
+ strncpy(g_eio_devname, devname, sizeof(g_eio_devname));
+ }
+ if (path) {
+ strncpy(g_eio_path, path, sizeof(g_eio_path));
+ }
+ g_eio_error = error;
+ g_eio_iotype = iotype;
+ g_eio_offset = offset;
+ g_eio_length = length;
+ return 0;
+}
diff --git a/src/global/global_context.h b/src/global/global_context.h
new file mode 100644
index 00000000..7d32d8cf
--- /dev/null
+++ b/src/global/global_context.h
@@ -0,0 +1,52 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_GLOBAL_CONTEXT_H
+#define CEPH_GLOBAL_CONTEXT_H
+
+#include <limits.h>
+
+#include "common/config_fwd.h"
+
+class CephContext;
+
+extern CephContext *g_ceph_context;
+ConfigProxy& g_conf();
+
+extern const char *g_assert_file;
+extern int g_assert_line;
+extern const char *g_assert_func;
+extern const char *g_assert_condition;
+extern unsigned long long g_assert_thread;
+extern char g_assert_thread_name[4096];
+extern char g_assert_msg[8096];
+extern char g_process_name[NAME_MAX + 1];
+
+extern bool g_eio;
+extern char g_eio_devname[1024];
+extern char g_eio_path[PATH_MAX];
+extern int g_eio_error;
+extern int g_eio_iotype; // IOCB_CMD_* from libaio's aio_abh.io
+extern unsigned long long g_eio_offset;
+extern unsigned long long g_eio_length;
+
+extern int note_io_error_event(
+ const char *devname,
+ const char *path,
+ int error,
+ int iotype,
+ unsigned long long offset,
+ unsigned long long length);
+
+#endif
diff --git a/src/global/global_init.cc b/src/global/global_init.cc
new file mode 100644
index 00000000..f840c7d7
--- /dev/null
+++ b/src/global/global_init.cc
@@ -0,0 +1,610 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "common/ceph_argparse.h"
+#include "common/code_environment.h"
+#include "common/config.h"
+#include "common/debug.h"
+#include "common/errno.h"
+#include "common/signal.h"
+#include "common/version.h"
+#include "erasure-code/ErasureCodePlugin.h"
+#include "global/global_context.h"
+#include "global/global_init.h"
+#include "global/pidfile.h"
+#include "global/signal_handler.h"
+#include "include/compat.h"
+#include "include/str_list.h"
+#include "mon/MonClient.h"
+
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_
+
+static void global_init_set_globals(CephContext *cct)
+{
+ g_ceph_context = cct;
+ get_process_name(g_process_name, sizeof(g_process_name));
+}
+
+static void output_ceph_version()
+{
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "%s, process %s, pid %d",
+ pretty_version_to_str().c_str(),
+ get_process_name_cpp().c_str(), getpid());
+ generic_dout(0) << buf << dendl;
+}
+
+static const char* c_str_or_null(const std::string &str)
+{
+ if (str.empty())
+ return NULL;
+ return str.c_str();
+}
+
+static int chown_path(const std::string &pathname, const uid_t owner, const gid_t group,
+ const std::string &uid_str, const std::string &gid_str)
+{
+ const char *pathname_cstr = c_str_or_null(pathname);
+
+ if (!pathname_cstr) {
+ return 0;
+ }
+
+ int r = ::chown(pathname_cstr, owner, group);
+
+ if (r < 0) {
+ r = -errno;
+ cerr << "warning: unable to chown() " << pathname << " as "
+ << uid_str << ":" << gid_str << ": " << cpp_strerror(r) << std::endl;
+ }
+
+ return r;
+}
+
+void global_pre_init(
+ const std::map<std::string,std::string> *defaults,
+ std::vector < const char* >& args,
+ uint32_t module_type, code_environment_t code_env,
+ int flags)
+{
+ std::string conf_file_list;
+ std::string cluster = "";
+
+ // ensure environment arguments are included in early processing
+ env_to_vec(args);
+
+ CephInitParameters iparams = ceph_argparse_early_args(
+ args, module_type,
+ &cluster, &conf_file_list);
+
+ CephContext *cct = common_preinit(iparams, code_env, flags);
+ cct->_conf->cluster = cluster;
+ global_init_set_globals(cct);
+ auto& conf = cct->_conf;
+
+ if (flags & (CINIT_FLAG_NO_DEFAULT_CONFIG_FILE|
+ CINIT_FLAG_NO_MON_CONFIG)) {
+ conf->no_mon_config = true;
+ }
+
+ // alternate defaults
+ if (defaults) {
+ for (auto& i : *defaults) {
+ conf.set_val_default(i.first, i.second);
+ }
+ }
+
+ int ret = conf.parse_config_files(c_str_or_null(conf_file_list),
+ &cerr, flags);
+ if (ret == -EDOM) {
+ cct->_log->flush();
+ cerr << "global_init: error parsing config file." << std::endl;
+ _exit(1);
+ }
+ else if (ret == -ENOENT) {
+ if (!(flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE)) {
+ if (conf_file_list.length()) {
+ cct->_log->flush();
+ cerr << "global_init: unable to open config file from search list "
+ << conf_file_list << std::endl;
+ _exit(1);
+ } else {
+ cerr << "did not load config file, using default settings." << std::endl;
+ }
+ }
+ }
+ else if (ret) {
+ cct->_log->flush();
+ cerr << "global_init: error reading config file." << std::endl;
+ _exit(1);
+ }
+
+ // environment variables override (CEPH_ARGS, CEPH_KEYRING)
+ conf.parse_env(cct->get_module_type());
+
+ // command line (as passed by caller)
+ conf.parse_argv(args);
+
+ if (conf->log_early &&
+ !cct->_log->is_started()) {
+ cct->_log->start();
+ }
+
+ if (!cct->_log->is_started()) {
+ cct->_log->start();
+ }
+
+ // do the --show-config[-val], if present in argv
+ conf.do_argv_commands();
+
+ // Now we're ready to complain about config file parse errors
+ g_conf().complain_about_parse_errors(g_ceph_context);
+}
+
+boost::intrusive_ptr<CephContext>
+global_init(const std::map<std::string,std::string> *defaults,
+ std::vector < const char* >& args,
+ uint32_t module_type, code_environment_t code_env,
+ int flags,
+ const char *data_dir_option, bool run_pre_init)
+{
+ // Ensure we're not calling the global init functions multiple times.
+ static bool first_run = true;
+ if (run_pre_init) {
+ // We will run pre_init from here (default).
+ ceph_assert(!g_ceph_context && first_run);
+ global_pre_init(defaults, args, module_type, code_env, flags);
+ } else {
+ // Caller should have invoked pre_init manually.
+ ceph_assert(g_ceph_context && first_run);
+ }
+ first_run = false;
+
+ // Verify flags have not changed if global_pre_init() has been called
+ // manually. If they have, update them.
+ if (g_ceph_context->get_init_flags() != flags) {
+ g_ceph_context->set_init_flags(flags);
+ }
+
+ // signal stuff
+ int siglist[] = { SIGPIPE, 0 };
+ block_signals(siglist, NULL);
+
+ if (g_conf()->fatal_signal_handlers) {
+ install_standard_sighandlers();
+ }
+ register_assert_context(g_ceph_context);
+
+ if (g_conf()->log_flush_on_exit)
+ g_ceph_context->_log->set_flush_on_exit();
+
+ // drop privileges?
+ ostringstream priv_ss;
+
+ // consider --setuser root a no-op, even if we're not root
+ if (getuid() != 0) {
+ if (g_conf()->setuser.length()) {
+ cerr << "ignoring --setuser " << g_conf()->setuser << " since I am not root"
+ << std::endl;
+ }
+ if (g_conf()->setgroup.length()) {
+ cerr << "ignoring --setgroup " << g_conf()->setgroup
+ << " since I am not root" << std::endl;
+ }
+ } else if (g_conf()->setgroup.length() ||
+ g_conf()->setuser.length()) {
+ uid_t uid = 0; // zero means no change; we can only drop privs here.
+ gid_t gid = 0;
+ std::string uid_string;
+ std::string gid_string;
+ std::string home_directory;
+ if (g_conf()->setuser.length()) {
+ char buf[4096];
+ struct passwd pa;
+ struct passwd *p = 0;
+
+ uid = atoi(g_conf()->setuser.c_str());
+ if (uid) {
+ getpwuid_r(uid, &pa, buf, sizeof(buf), &p);
+ } else {
+ getpwnam_r(g_conf()->setuser.c_str(), &pa, buf, sizeof(buf), &p);
+ if (!p) {
+ cerr << "unable to look up user '" << g_conf()->setuser << "'"
+ << std::endl;
+ exit(1);
+ }
+
+ uid = p->pw_uid;
+ gid = p->pw_gid;
+ uid_string = g_conf()->setuser;
+ }
+
+ if (p && p->pw_dir != nullptr) {
+ home_directory = std::string(p->pw_dir);
+ }
+ }
+ if (g_conf()->setgroup.length() > 0) {
+ gid = atoi(g_conf()->setgroup.c_str());
+ if (!gid) {
+ char buf[4096];
+ struct group gr;
+ struct group *g = 0;
+ getgrnam_r(g_conf()->setgroup.c_str(), &gr, buf, sizeof(buf), &g);
+ if (!g) {
+ cerr << "unable to look up group '" << g_conf()->setgroup << "'"
+ << ": " << cpp_strerror(errno) << std::endl;
+ exit(1);
+ }
+ gid = g->gr_gid;
+ gid_string = g_conf()->setgroup;
+ }
+ }
+ if ((uid || gid) &&
+ g_conf()->setuser_match_path.length()) {
+ // induce early expansion of setuser_match_path config option
+ string match_path = g_conf()->setuser_match_path;
+ g_conf().early_expand_meta(match_path, &cerr);
+ struct stat st;
+ int r = ::stat(match_path.c_str(), &st);
+ if (r < 0) {
+ cerr << "unable to stat setuser_match_path "
+ << g_conf()->setuser_match_path
+ << ": " << cpp_strerror(errno) << std::endl;
+ exit(1);
+ }
+ if ((uid && uid != st.st_uid) ||
+ (gid && gid != st.st_gid)) {
+ cerr << "WARNING: will not setuid/gid: " << match_path
+ << " owned by " << st.st_uid << ":" << st.st_gid
+ << " and not requested " << uid << ":" << gid
+ << std::endl;
+ uid = 0;
+ gid = 0;
+ uid_string.erase();
+ gid_string.erase();
+ } else {
+ priv_ss << "setuser_match_path "
+ << match_path << " owned by "
+ << st.st_uid << ":" << st.st_gid << ". ";
+ }
+ }
+ g_ceph_context->set_uid_gid(uid, gid);
+ g_ceph_context->set_uid_gid_strings(uid_string, gid_string);
+ if ((flags & CINIT_FLAG_DEFER_DROP_PRIVILEGES) == 0) {
+ if (setgid(gid) != 0) {
+ cerr << "unable to setgid " << gid << ": " << cpp_strerror(errno)
+ << std::endl;
+ exit(1);
+ }
+ if (setuid(uid) != 0) {
+ cerr << "unable to setuid " << uid << ": " << cpp_strerror(errno)
+ << std::endl;
+ exit(1);
+ }
+ if (setenv("HOME", home_directory.c_str(), 1) != 0) {
+ cerr << "warning: unable to set HOME to " << home_directory << ": "
+ << cpp_strerror(errno) << std::endl;
+ }
+ priv_ss << "set uid:gid to " << uid << ":" << gid << " (" << uid_string << ":" << gid_string << ")";
+ } else {
+ priv_ss << "deferred set uid:gid to " << uid << ":" << gid << " (" << uid_string << ":" << gid_string << ")";
+ }
+ }
+
+#if defined(HAVE_SYS_PRCTL_H)
+ if (prctl(PR_SET_DUMPABLE, 1) == -1) {
+ cerr << "warning: unable to set dumpable flag: " << cpp_strerror(errno) << std::endl;
+ }
+# if defined(PR_SET_THP_DISABLE)
+ if (!g_conf().get_val<bool>("thp") && prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) == -1) {
+ cerr << "warning: unable to disable THP: " << cpp_strerror(errno) << std::endl;
+ }
+# endif
+#endif
+
+ //
+ // Utterly important to run first network connection after setuid().
+ // In case of rdma transport uverbs kernel module starts returning
+ // -EACCESS on each operation if credentials has been changed, see
+ // callers of ib_safe_file_access() for details.
+ //
+ // fork() syscall also matters, so daemonization won't work in case
+ // of rdma.
+ //
+ if (!g_conf()->no_mon_config) {
+ // make sure our mini-session gets legacy values
+ g_conf().apply_changes(nullptr);
+
+ MonClient mc_bootstrap(g_ceph_context);
+ if (mc_bootstrap.get_monmap_and_config() < 0) {
+ g_ceph_context->_log->flush();
+ cerr << "failed to fetch mon config (--no-mon-config to skip)"
+ << std::endl;
+ _exit(1);
+ }
+ }
+
+ // Expand metavariables. Invoke configuration observers. Open log file.
+ g_conf().apply_changes(nullptr);
+
+ if (g_conf()->run_dir.length() &&
+ code_env == CODE_ENVIRONMENT_DAEMON &&
+ !(flags & CINIT_FLAG_NO_DAEMON_ACTIONS)) {
+ int r = ::mkdir(g_conf()->run_dir.c_str(), 0755);
+ if (r < 0 && errno != EEXIST) {
+ cerr << "warning: unable to create " << g_conf()->run_dir << ": " << cpp_strerror(errno) << std::endl;
+ }
+ }
+
+ // call all observers now. this has the side-effect of configuring
+ // and opening the log file immediately.
+ g_conf().call_all_observers();
+
+ if (priv_ss.str().length()) {
+ dout(0) << priv_ss.str() << dendl;
+ }
+
+ if ((flags & CINIT_FLAG_DEFER_DROP_PRIVILEGES) &&
+ (g_ceph_context->get_set_uid() || g_ceph_context->get_set_gid())) {
+ // Fix ownership on log files and run directories if needed.
+ // Admin socket files are chown()'d during the common init path _after_
+ // the service thread has been started. This is sadly a bit of a hack :(
+ chown_path(g_conf()->run_dir,
+ g_ceph_context->get_set_uid(),
+ g_ceph_context->get_set_gid(),
+ g_ceph_context->get_set_uid_string(),
+ g_ceph_context->get_set_gid_string());
+ g_ceph_context->_log->chown_log_file(
+ g_ceph_context->get_set_uid(),
+ g_ceph_context->get_set_gid());
+ }
+
+ // Now we're ready to complain about config file parse errors
+ g_conf().complain_about_parse_errors(g_ceph_context);
+
+ // test leak checking
+ if (g_conf()->debug_deliberately_leak_memory) {
+ derr << "deliberately leaking some memory" << dendl;
+ char *s = new char[1234567];
+ (void)s;
+ // cppcheck-suppress memleak
+ }
+
+ if (code_env == CODE_ENVIRONMENT_DAEMON && !(flags & CINIT_FLAG_NO_DAEMON_ACTIONS))
+ output_ceph_version();
+
+ if (g_ceph_context->crush_location.init_on_startup()) {
+ cerr << " failed to init_on_startup : " << cpp_strerror(errno) << std::endl;
+ exit(1);
+ }
+
+ return boost::intrusive_ptr<CephContext>{g_ceph_context, false};
+}
+
+void intrusive_ptr_add_ref(CephContext* cct)
+{
+ cct->get();
+}
+
+void intrusive_ptr_release(CephContext* cct)
+{
+ cct->put();
+}
+
+void global_print_banner(void)
+{
+ output_ceph_version();
+}
+
+int global_init_prefork(CephContext *cct)
+{
+ if (g_code_env != CODE_ENVIRONMENT_DAEMON)
+ return -1;
+
+ const auto& conf = cct->_conf;
+ if (!conf->daemonize) {
+
+ if (pidfile_write(conf) < 0)
+ exit(1);
+
+ if ((cct->get_init_flags() & CINIT_FLAG_DEFER_DROP_PRIVILEGES) &&
+ (cct->get_set_uid() || cct->get_set_gid())) {
+ chown_path(conf->pid_file, cct->get_set_uid(), cct->get_set_gid(),
+ cct->get_set_uid_string(), cct->get_set_gid_string());
+ }
+
+ return -1;
+ }
+
+ cct->notify_pre_fork();
+ // stop log thread
+ cct->_log->flush();
+ cct->_log->stop();
+ return 0;
+}
+
+void global_init_daemonize(CephContext *cct)
+{
+ if (global_init_prefork(cct) < 0)
+ return;
+
+#if !defined(_AIX)
+ int ret = daemon(1, 1);
+ if (ret) {
+ ret = errno;
+ derr << "global_init_daemonize: BUG: daemon error: "
+ << cpp_strerror(ret) << dendl;
+ exit(1);
+ }
+
+ global_init_postfork_start(cct);
+ global_init_postfork_finish(cct);
+#else
+# warning daemon not supported on aix
+#endif
+}
+
+int reopen_as_null(CephContext *cct, int fd)
+{
+ int newfd = open("/dev/null", O_RDONLY|O_CLOEXEC);
+ if (newfd < 0) {
+ int err = errno;
+ lderr(cct) << __func__ << " failed to open /dev/null: " << cpp_strerror(err)
+ << dendl;
+ return -1;
+ }
+ // atomically dup newfd to target fd. target fd is implicitly closed if
+ // open and atomically replaced; see man dup2
+ int r = dup2(newfd, fd);
+ if (r < 0) {
+ int err = errno;
+ lderr(cct) << __func__ << " failed to dup2 " << fd << ": "
+ << cpp_strerror(err) << dendl;
+ return -1;
+ }
+ // close newfd (we cloned it to target fd)
+ VOID_TEMP_FAILURE_RETRY(close(newfd));
+ // N.B. FD_CLOEXEC is cleared on fd (see dup2(2))
+ return 0;
+}
+
+void global_init_postfork_start(CephContext *cct)
+{
+ // reexpand the meta in child process
+ cct->_conf.finalize_reexpand_meta();
+
+ // restart log thread
+ cct->_log->start();
+ cct->notify_post_fork();
+
+ /* This is the old trick where we make file descriptors 0, 1, and possibly 2
+ * point to /dev/null.
+ *
+ * We have to do this because otherwise some arbitrary call to open() later
+ * in the program might get back one of these file descriptors. It's hard to
+ * guarantee that nobody ever writes to stdout, even though they're not
+ * supposed to.
+ */
+ reopen_as_null(cct, STDIN_FILENO);
+
+ const auto& conf = cct->_conf;
+ if (pidfile_write(conf) < 0)
+ exit(1);
+
+ if ((cct->get_init_flags() & CINIT_FLAG_DEFER_DROP_PRIVILEGES) &&
+ (cct->get_set_uid() || cct->get_set_gid())) {
+ chown_path(conf->pid_file, cct->get_set_uid(), cct->get_set_gid(),
+ cct->get_set_uid_string(), cct->get_set_gid_string());
+ }
+}
+
+void global_init_postfork_finish(CephContext *cct)
+{
+ /* We only close stdout+stderr once the caller decides the daemonization
+ * process is finished. This way we can allow error or other messages to be
+ * propagated in a manner that the user is able to see.
+ */
+ if (!(cct->get_init_flags() & CINIT_FLAG_NO_CLOSE_STDERR)) {
+ int ret = global_init_shutdown_stderr(cct);
+ if (ret) {
+ derr << "global_init_daemonize: global_init_shutdown_stderr failed with "
+ << "error code " << ret << dendl;
+ exit(1);
+ }
+ }
+
+ reopen_as_null(cct, STDOUT_FILENO);
+
+ ldout(cct, 1) << "finished global_init_daemonize" << dendl;
+}
+
+
+void global_init_chdir(const CephContext *cct)
+{
+ const auto& conf = cct->_conf;
+ if (conf->chdir.empty())
+ return;
+ if (::chdir(conf->chdir.c_str())) {
+ int err = errno;
+ derr << "global_init_chdir: failed to chdir to directory: '"
+ << conf->chdir << "': " << cpp_strerror(err) << dendl;
+ }
+}
+
+/* Map stderr to /dev/null. This isn't really re-entrant; we rely on the old unix
+ * behavior that the file descriptor that gets assigned is the lowest
+ * available one.
+ */
+int global_init_shutdown_stderr(CephContext *cct)
+{
+ reopen_as_null(cct, STDERR_FILENO);
+ int l = cct->_conf->err_to_stderr ? -1 : -2;
+ cct->_log->set_stderr_level(l, l);
+ return 0;
+}
+
+int global_init_preload_erasure_code(const CephContext *cct)
+{
+ const auto& conf = cct->_conf;
+ string plugins = conf->osd_erasure_code_plugins;
+
+ // validate that this is a not a legacy plugin
+ list<string> plugins_list;
+ get_str_list(plugins, plugins_list);
+ for (list<string>::iterator i = plugins_list.begin();
+ i != plugins_list.end();
+ ++i) {
+ string plugin_name = *i;
+ string replacement = "";
+
+ if (plugin_name == "jerasure_generic" ||
+ plugin_name == "jerasure_sse3" ||
+ plugin_name == "jerasure_sse4" ||
+ plugin_name == "jerasure_neon") {
+ replacement = "jerasure";
+ }
+ else if (plugin_name == "shec_generic" ||
+ plugin_name == "shec_sse3" ||
+ plugin_name == "shec_sse4" ||
+ plugin_name == "shec_neon") {
+ replacement = "shec";
+ }
+
+ if (replacement != "") {
+ dout(0) << "WARNING: osd_erasure_code_plugins contains plugin "
+ << plugin_name << " that is now deprecated. Please modify the value "
+ << "for osd_erasure_code_plugins to use " << replacement << " instead." << dendl;
+ }
+ }
+
+ stringstream ss;
+ int r = ErasureCodePluginRegistry::instance().preload(
+ plugins,
+ conf.get_val<std::string>("erasure_code_dir"),
+ &ss);
+ if (r)
+ derr << ss.str() << dendl;
+ else
+ dout(0) << ss.str() << dendl;
+ return r;
+}
diff --git a/src/global/global_init.h b/src/global/global_init.h
new file mode 100644
index 00000000..fe6f557b
--- /dev/null
+++ b/src/global/global_init.h
@@ -0,0 +1,108 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_COMMON_GLOBAL_INIT_H
+#define CEPH_COMMON_GLOBAL_INIT_H
+
+#include <stdint.h>
+#include <vector>
+#include <map>
+#include <boost/intrusive_ptr.hpp>
+#include "include/ceph_assert.h"
+#include "common/code_environment.h"
+#include "common/common_init.h"
+
+class CephContext;
+
+/*
+ * global_init is the first initialization function that
+ * daemons and utility programs need to call. It takes care of a lot of
+ * initialization, including setting up g_ceph_context.
+ */
+boost::intrusive_ptr<CephContext>
+global_init(
+ const std::map<std::string,std::string> *defaults,
+ std::vector < const char* >& args,
+ uint32_t module_type,
+ code_environment_t code_env,
+ int flags,
+ const char *data_dir_option = 0,
+ bool run_pre_init = true);
+
+void intrusive_ptr_add_ref(CephContext* cct);
+void intrusive_ptr_release(CephContext* cct);
+
+// just the first half; enough to get config parsed but doesn't start up the
+// cct or log.
+void global_pre_init(const std::map<std::string,std::string> *defaults,
+ std::vector < const char* >& args,
+ uint32_t module_type, code_environment_t code_env,
+ int flags);
+
+/*
+ * perform all of the steps that global_init_daemonize performs just prior
+ * to actually forking (via daemon(3)). return 0 if we are going to proceed
+ * with the fork, or -1 otherwise.
+ */
+int global_init_prefork(CephContext *cct);
+
+/*
+ * perform all the steps that global_init_daemonize performs just after
+ * the fork, except closing stderr, which we'll do later on.
+ */
+void global_init_postfork_start(CephContext *cct);
+
+/*
+ * close stderr, thus completing the postfork.
+ */
+void global_init_postfork_finish(CephContext *cct);
+
+
+/*
+ * global_init_daemonize handles daemonizing a process.
+ *
+ * If this is called, it *must* be called before common_init_finish.
+ * Note that this is equivalent to calling _prefork(), daemon(), and
+ * _postfork.
+ */
+void global_init_daemonize(CephContext *cct);
+
+/*
+ * global_init_chdir changes the process directory.
+ *
+ * If this is called, it *must* be called before common_init_finish
+ */
+void global_init_chdir(const CephContext *cct);
+
+/*
+ * Explicitly shut down stderr. Usually, you don't need to do
+ * this, because global_init_daemonize will do it for you. However, in some
+ * rare cases you need to call this explicitly.
+ *
+ * If this is called, it *must* be called before common_init_finish
+ */
+int global_init_shutdown_stderr(CephContext *cct);
+
+/*
+ * Preload the erasure coding libraries to detect early issues with
+ * configuration.
+ */
+int global_init_preload_erasure_code(const CephContext *cct);
+
+/**
+ * print daemon startup banner/warning
+ */
+void global_print_banner(void);
+
+#endif
diff --git a/src/global/pidfile.cc b/src/global/pidfile.cc
new file mode 100644
index 00000000..76621e0f
--- /dev/null
+++ b/src/global/pidfile.cc
@@ -0,0 +1,247 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/errno.h"
+#include "common/safe_io.h"
+#include "global/pidfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if defined(__FreeBSD__)
+#include <sys/param.h>
+#endif
+
+#include "include/compat.h"
+
+//
+// derr can be used for functions exclusively called from pidfile_write
+//
+// cerr must be used for functions called by pidfile_remove because
+// logging is not functional when it is called. cerr output is lost
+// when the caller is daemonized but it will show if not (-f)
+//
+#define dout_context g_ceph_context
+#define dout_prefix *_dout
+#define dout_subsys ceph_subsys_
+
+struct pidfh {
+ int pf_fd;
+ char pf_path[PATH_MAX + 1];
+ dev_t pf_dev;
+ ino_t pf_ino;
+
+ pidfh() {
+ reset();
+ }
+ ~pidfh() {
+ remove();
+ }
+
+ bool is_open() {
+ return pf_path[0] != '\0' && pf_fd != -1;
+ }
+ void reset() {
+ pf_fd = -1;
+ memset(pf_path, 0, sizeof(pf_path));
+ pf_dev = 0;
+ pf_ino = 0;
+ }
+ int verify();
+ int remove();
+ int open(const ConfigProxy& conf);
+ int write();
+};
+
+static pidfh *pfh = nullptr;
+
+int pidfh::verify() {
+ // check that the file we opened still is the same
+ if (pf_fd == -1)
+ return -EINVAL;
+ struct stat st;
+ if (stat(pf_path, &st) == -1)
+ return -errno;
+ if (st.st_dev != pf_dev || st.st_ino != pf_ino)
+ return -ESTALE;
+ return 0;
+}
+
+int pidfh::remove()
+{
+ if (!pf_path[0])
+ return 0;
+
+ int ret;
+ if ((ret = verify()) < 0) {
+ if (pf_fd != -1) {
+ ::close(pf_fd);
+ reset();
+ }
+ return ret;
+ }
+
+ // seek to the beginning of the file before reading
+ ret = ::lseek(pf_fd, 0, SEEK_SET);
+ if (ret < 0) {
+ std::cerr << __func__ << " lseek failed "
+ << cpp_strerror(errno) << std::endl;
+ return -errno;
+ }
+
+ // check that the pid file still has our pid in it
+ char buf[32];
+ memset(buf, 0, sizeof(buf));
+ ssize_t res = safe_read(pf_fd, buf, sizeof(buf));
+ ::close(pf_fd);
+ if (res < 0) {
+ std::cerr << __func__ << " safe_read failed "
+ << cpp_strerror(-res) << std::endl;
+ return res;
+ }
+
+ int a = atoi(buf);
+ if (a != getpid()) {
+ std::cerr << __func__ << " the pid found in the file is "
+ << a << " which is different from getpid() "
+ << getpid() << std::endl;
+ return -EDOM;
+ }
+ ret = ::unlink(pf_path);
+ if (ret < 0) {
+ std::cerr << __func__ << " unlink " << pf_path << " failed "
+ << cpp_strerror(errno) << std::endl;
+ return -errno;
+ }
+ reset();
+ return 0;
+}
+
+int pidfh::open(const ConfigProxy& conf)
+{
+ int len = snprintf(pf_path, sizeof(pf_path),
+ "%s", conf->pid_file.c_str());
+
+ if (len >= (int)sizeof(pf_path))
+ return -ENAMETOOLONG;
+
+ int fd;
+ fd = ::open(pf_path, O_CREAT|O_RDWR|O_CLOEXEC, 0644);
+ if (fd < 0) {
+ int err = errno;
+ derr << __func__ << ": failed to open pid file '"
+ << pf_path << "': " << cpp_strerror(err) << dendl;
+ reset();
+ return -err;
+ }
+ struct stat st;
+ if (fstat(fd, &st) == -1) {
+ int err = errno;
+ derr << __func__ << ": failed to fstat pid file '"
+ << pf_path << "': " << cpp_strerror(err) << dendl;
+ ::close(fd);
+ reset();
+ return -err;
+ }
+
+ pf_fd = fd;
+ pf_dev = st.st_dev;
+ pf_ino = st.st_ino;
+
+ struct flock l = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0
+ };
+ int r = ::fcntl(pf_fd, F_SETLK, &l);
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EACCES) {
+ derr << __func__ << ": failed to lock pidfile "
+ << pf_path << " because another process locked it"
+ << "': " << cpp_strerror(errno) << dendl;
+ } else {
+ derr << __func__ << ": failed to lock pidfile "
+ << pf_path << "': " << cpp_strerror(errno) << dendl;
+ }
+ ::close(pf_fd);
+ reset();
+ return -errno;
+ }
+ return 0;
+}
+
+int pidfh::write()
+{
+ if (!is_open())
+ return 0;
+
+ char buf[32];
+ int len = snprintf(buf, sizeof(buf), "%d\n", getpid());
+ if (::ftruncate(pf_fd, 0) < 0) {
+ int err = errno;
+ derr << __func__ << ": failed to ftruncate the pid file '"
+ << pf_path << "': " << cpp_strerror(err) << dendl;
+ return -err;
+ }
+ ssize_t res = safe_write(pf_fd, buf, len);
+ if (res < 0) {
+ derr << __func__ << ": failed to write to pid file '"
+ << pf_path << "': " << cpp_strerror(-res) << dendl;
+ return res;
+ }
+ return 0;
+}
+
+void pidfile_remove()
+{
+ if (pfh != nullptr)
+ delete pfh;
+ pfh = nullptr;
+}
+
+int pidfile_write(const ConfigProxy& conf)
+{
+ if (conf->pid_file.empty()) {
+ dout(0) << __func__ << ": ignore empty --pid-file" << dendl;
+ return 0;
+ }
+
+ ceph_assert(pfh == nullptr);
+
+ pfh = new pidfh();
+ if (atexit(pidfile_remove)) {
+ derr << __func__ << ": failed to set pidfile_remove function "
+ << "to run at exit." << dendl;
+ return -EINVAL;
+ }
+
+ int r = pfh->open(conf);
+ if (r != 0) {
+ pidfile_remove();
+ return r;
+ }
+
+ r = pfh->write();
+ if (r != 0) {
+ pidfile_remove();
+ return r;
+ }
+
+ return 0;
+}
diff --git a/src/global/pidfile.h b/src/global/pidfile.h
new file mode 100644
index 00000000..1780263f
--- /dev/null
+++ b/src/global/pidfile.h
@@ -0,0 +1,28 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_COMMON_PIDFILE_H
+#define CEPH_COMMON_PIDFILE_H
+
+#include "common/config_fwd.h"
+
+// Write a pidfile with the current pid, using the configuration in the
+// provided conf structure.
+int pidfile_write(const ConfigProxy& conf);
+
+// Remove the pid file that was previously written by pidfile_write.
+// This is safe to call in a signal handler context.
+void pidfile_remove();
+
+#endif
diff --git a/src/global/signal_handler.cc b/src/global/signal_handler.cc
new file mode 100644
index 00000000..56a586d7
--- /dev/null
+++ b/src/global/signal_handler.cc
@@ -0,0 +1,665 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include <sys/utsname.h>
+
+#include "include/compat.h"
+#include "pthread.h"
+
+#include "common/ceph_mutex.h"
+#include "common/BackTrace.h"
+#include "common/debug.h"
+#include "common/safe_io.h"
+#include "common/version.h"
+
+#include "include/uuid.h"
+#include "global/pidfile.h"
+#include "global/signal_handler.h"
+
+#include <poll.h>
+#include <signal.h>
+#include <sstream>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "common/errno.h"
+#if defined(_AIX)
+extern char *sys_siglist[];
+#endif
+
+#define dout_context g_ceph_context
+
+void install_sighandler(int signum, signal_handler_t handler, int flags)
+{
+ int ret;
+ struct sigaction oldact;
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+
+ act.sa_handler = handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = flags;
+
+ ret = sigaction(signum, &act, &oldact);
+ if (ret != 0) {
+ char buf[1024];
+#if defined(__sun)
+ char message[SIG2STR_MAX];
+ sig2str(signum,message);
+ snprintf(buf, sizeof(buf), "install_sighandler: sigaction returned "
+ "%d when trying to install a signal handler for %s\n",
+ ret, message);
+#else
+ snprintf(buf, sizeof(buf), "install_sighandler: sigaction returned "
+ "%d when trying to install a signal handler for %s\n",
+ ret, sig_str(signum));
+#endif
+ dout_emergency(buf);
+ exit(1);
+ }
+}
+
+void sighup_handler(int signum)
+{
+ g_ceph_context->reopen_logs();
+}
+
+static void reraise_fatal(int signum)
+{
+ // Use default handler to dump core
+ int ret = raise(signum);
+
+ // Normally, we won't get here. If we do, something is very weird.
+ char buf[1024];
+ if (ret) {
+ snprintf(buf, sizeof(buf), "reraise_fatal: failed to re-raise "
+ "signal %d\n", signum);
+ dout_emergency(buf);
+ }
+ else {
+ snprintf(buf, sizeof(buf), "reraise_fatal: default handler for "
+ "signal %d didn't terminate the process?\n", signum);
+ dout_emergency(buf);
+ }
+ exit(1);
+}
+
+
+// /etc/os-release looks like
+//
+// NAME=Fedora
+// VERSION="28 (Server Edition)"
+// ID=fedora
+// VERSION_ID=28
+//
+// or
+//
+// NAME="Ubuntu"
+// VERSION="16.04.3 LTS (Xenial Xerus)"
+// ID=ubuntu
+// ID_LIKE=debian
+//
+// get_from_os_release("FOO=bar\nTHIS=\"that\"\n", "FOO=", ...) will
+// write "bar\0" to out buffer, which is assumed to be as large as the input
+// file.
+static int parse_from_os_release(
+ const char *file, const char *key,
+ char *out)
+{
+ const char *p = strstr(file, key);
+ if (!p) {
+ return -1;
+ }
+ const char *start = p + strlen(key);
+ const char *end = strchr(start, '\n');
+ if (!end) {
+ return -1;
+ }
+ if (*start == '"' && *(end - 1) == '"') {
+ ++start;
+ --end;
+ }
+ if (start >= end) {
+ return -1;
+ }
+ memcpy(out, start, end - start);
+ out[end - start] = 0;
+ return 0;
+}
+
+static void handle_fatal_signal(int signum)
+{
+ // This code may itself trigger a SIGSEGV if the heap is corrupt. In that
+ // case, SA_RESETHAND specifies that the default signal handler--
+ // presumably dump core-- will handle it.
+ char buf[1024];
+ char pthread_name[16] = {0}; //limited by 16B include terminating null byte.
+ int r = ceph_pthread_getname(pthread_self(), pthread_name, sizeof(pthread_name));
+ (void)r;
+#if defined(__sun)
+ char message[SIG2STR_MAX];
+ sig2str(signum,message);
+ snprintf(buf, sizeof(buf), "*** Caught signal (%s) **\n "
+ "in thread %llx thread_name:%s\n", message, (unsigned long long)pthread_self(),
+ pthread_name);
+#else
+ snprintf(buf, sizeof(buf), "*** Caught signal (%s) **\n "
+ "in thread %llx thread_name:%s\n", sig_str(signum), (unsigned long long)pthread_self(),
+ pthread_name);
+#endif
+ dout_emergency(buf);
+ pidfile_remove();
+
+ // TODO: don't use an ostringstream here. It could call malloc(), which we
+ // don't want inside a signal handler.
+ // Also fix the backtrace code not to allocate memory.
+ BackTrace bt(1);
+ ostringstream oss;
+ bt.print(oss);
+ dout_emergency(oss.str());
+
+ char base[PATH_MAX] = { 0 };
+ if (g_ceph_context &&
+ g_ceph_context->_conf->crash_dir.size()) {
+ // -- crash dump --
+ // id
+ ostringstream idss;
+ utime_t now = ceph_clock_now();
+ now.gmtime(idss);
+ uuid_d uuid;
+ uuid.generate_random();
+ idss << "_" << uuid;
+ string id = idss.str();
+ std::replace(id.begin(), id.end(), ' ', '_');
+
+ snprintf(base, sizeof(base), "%s/%s",
+ g_ceph_context->_conf->crash_dir.c_str(),
+ id.c_str());
+ int r = ::mkdir(base, 0700);
+ if (r >= 0) {
+ char fn[PATH_MAX*2];
+ snprintf(fn, sizeof(fn)-1, "%s/meta", base);
+ int fd = ::open(fn, O_CREAT|O_WRONLY|O_CLOEXEC, 0600);
+ if (fd >= 0) {
+ JSONFormatter jf(true);
+ jf.open_object_section("crash");
+ jf.dump_string("crash_id", id);
+ now.gmtime(jf.dump_stream("timestamp"));
+ jf.dump_string("process_name", g_process_name);
+ jf.dump_string("entity_name", g_ceph_context->_conf->name.to_str());
+ jf.dump_string("ceph_version", ceph_version_to_str());
+
+ struct utsname u;
+ r = uname(&u);
+ if (r >= 0) {
+ jf.dump_string("utsname_hostname", u.nodename);
+ jf.dump_string("utsname_sysname", u.sysname);
+ jf.dump_string("utsname_release", u.release);
+ jf.dump_string("utsname_version", u.version);
+ jf.dump_string("utsname_machine", u.machine);
+ }
+#if defined(__linux__)
+ // os-release
+ int in = ::open("/etc/os-release", O_RDONLY|O_CLOEXEC);
+ if (in >= 0) {
+ char buf[4096];
+ r = safe_read(in, buf, sizeof(buf)-1);
+ if (r >= 0) {
+ buf[r] = 0;
+ char v[4096];
+ if (parse_from_os_release(buf, "NAME=", v) >= 0) {
+ jf.dump_string("os_name", v);
+ }
+ if (parse_from_os_release(buf, "ID=", v) >= 0) {
+ jf.dump_string("os_id", v);
+ }
+ if (parse_from_os_release(buf, "VERSION_ID=", v) >= 0) {
+ jf.dump_string("os_version_id", v);
+ }
+ if (parse_from_os_release(buf, "VERSION=", v) >= 0) {
+ jf.dump_string("os_version", v);
+ }
+ }
+ ::close(in);
+ }
+#endif
+
+ // assert?
+ if (g_assert_condition) {
+ jf.dump_string("assert_condition", g_assert_condition);
+ }
+ if (g_assert_func) {
+ jf.dump_string("assert_func", g_assert_func);
+ }
+ if (g_assert_file) {
+ jf.dump_string("assert_file", g_assert_file);
+ }
+ if (g_assert_line) {
+ jf.dump_unsigned("assert_line", g_assert_line);
+ }
+ if (g_assert_thread_name[0]) {
+ jf.dump_string("assert_thread_name", g_assert_thread_name);
+ }
+ if (g_assert_msg[0]) {
+ jf.dump_string("assert_msg", g_assert_msg);
+ }
+
+ // eio?
+ if (g_eio) {
+ jf.dump_bool("io_error", true);
+ if (g_eio_devname[0]) {
+ jf.dump_string("io_error_devname", g_eio_devname);
+ }
+ if (g_eio_path[0]) {
+ jf.dump_string("io_error_path", g_eio_path);
+ }
+ if (g_eio_error) {
+ jf.dump_int("io_error_code", g_eio_error);
+ }
+ if (g_eio_iotype) {
+ jf.dump_int("io_error_optype", g_eio_iotype);
+ }
+ if (g_eio_offset) {
+ jf.dump_unsigned("io_error_offset", g_eio_offset);
+ }
+ if (g_eio_length) {
+ jf.dump_unsigned("io_error_length", g_eio_length);
+ }
+ }
+
+ // backtrace
+ bt.dump(&jf);
+
+ jf.close_section();
+ ostringstream oss;
+ jf.flush(oss);
+ string s = oss.str();
+ r = safe_write(fd, s.c_str(), s.size());
+ (void)r;
+ ::close(fd);
+ }
+ snprintf(fn, sizeof(fn)-1, "%s/done", base);
+ ::creat(fn, 0444);
+ }
+ }
+
+ // avoid recursion back into logging code if that is where
+ // we got the SEGV.
+ if (g_ceph_context &&
+ g_ceph_context->_log &&
+ !g_ceph_context->_log->is_inside_log_lock()) {
+ // dump to log. this uses the heap extensively, but we're better
+ // off trying than not.
+ derr << buf << std::endl;
+ bt.print(*_dout);
+ *_dout << " NOTE: a copy of the executable, or `objdump -rdS <executable>` "
+ << "is needed to interpret this.\n"
+ << dendl;
+
+ g_ceph_context->_log->dump_recent();
+
+ if (base[0]) {
+ char fn[PATH_MAX*2];
+ snprintf(fn, sizeof(fn)-1, "%s/log", base);
+ g_ceph_context->_log->set_log_file(fn);
+ g_ceph_context->_log->reopen_log_file();
+ g_ceph_context->_log->dump_recent();
+ }
+ }
+
+ if (g_eio) {
+ // if this was an EIO crash, we don't need to trigger a core dump,
+ // since the problem is hardware, or some layer beneath us.
+ _exit(EIO);
+ } else {
+ reraise_fatal(signum);
+ }
+}
+
+void install_standard_sighandlers(void)
+{
+ install_sighandler(SIGSEGV, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGABRT, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGBUS, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGILL, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGFPE, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGXCPU, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGXFSZ, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+ install_sighandler(SIGSYS, handle_fatal_signal, SA_RESETHAND | SA_NODEFER);
+}
+
+
+
+/// --- safe handler ---
+
+#include "common/Thread.h"
+#include <errno.h>
+
+#ifdef __APPLE__
+#include <libproc.h>
+
+string get_name_by_pid(pid_t pid)
+{
+ char buf[PROC_PIDPATHINFO_MAXSIZE];
+ int ret = proc_pidpath(pid, buf, sizeof(buf));
+ if (ret == 0) {
+ derr << "Fail to proc_pidpath(" << pid << ")"
+ << " error = " << cpp_strerror(ret)
+ << dendl;
+ return "<unknown>";
+ }
+ return string(buf, ret);
+}
+#else
+string get_name_by_pid(pid_t pid)
+{
+ // If the PID is 0, its means the sender is the Kernel itself
+ if (pid == 0) {
+ return "Kernel";
+ }
+ char proc_pid_path[PATH_MAX] = {0};
+ snprintf(proc_pid_path, PATH_MAX, PROCPREFIX "/proc/%d/cmdline", pid);
+ int fd = open(proc_pid_path, O_RDONLY);
+
+ if (fd < 0) {
+ fd = -errno;
+ derr << "Fail to open '" << proc_pid_path
+ << "' error = " << cpp_strerror(fd)
+ << dendl;
+ return "<unknown>";
+ }
+ // assuming the cmdline length does not exceed PATH_MAX. if it
+ // really does, it's fine to return a truncated version.
+ char buf[PATH_MAX] = {0};
+ int ret = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (ret < 0) {
+ ret = -errno;
+ derr << "Fail to read '" << proc_pid_path
+ << "' error = " << cpp_strerror(ret)
+ << dendl;
+ return "<unknown>";
+ }
+ std::replace(buf, buf + ret, '\0', ' ');
+ return string(buf, ret);
+}
+#endif
+
+/**
+ * safe async signal handler / dispatcher
+ *
+ * This is an async unix signal handler based on the design from
+ *
+ * http://evbergen.home.xs4all.nl/unix-signals.html
+ *
+ * Features:
+ * - no unsafe work is done in the signal handler itself
+ * - callbacks are called from a regular thread
+ * - signals are not lost, unless multiple instances of the same signal
+ * are sent twice in quick succession.
+ */
+struct SignalHandler : public Thread {
+ /// to kick the thread, for shutdown, new handlers, etc.
+ int pipefd[2]; // write to [1], read from [0]
+
+ /// to signal shutdown
+ bool stop = false;
+
+ /// for an individual signal
+ struct safe_handler {
+
+ safe_handler() {
+ memset(pipefd, 0, sizeof(pipefd));
+ memset(&handler, 0, sizeof(handler));
+ memset(&info_t, 0, sizeof(info_t));
+ }
+
+ siginfo_t info_t;
+ int pipefd[2]; // write to [1], read from [0]
+ signal_handler_t handler;
+ };
+
+ /// all handlers
+ safe_handler *handlers[32] = {nullptr};
+
+ /// to protect the handlers array
+ ceph::mutex lock = ceph::make_mutex("SignalHandler::lock");
+
+ SignalHandler() {
+ // create signal pipe
+ int r = pipe_cloexec(pipefd);
+ ceph_assert(r == 0);
+ r = fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
+ ceph_assert(r == 0);
+
+ // create thread
+ create("signal_handler");
+ }
+
+ ~SignalHandler() override {
+ shutdown();
+ }
+
+ void signal_thread() {
+ int r = write(pipefd[1], "\0", 1);
+ ceph_assert(r == 1);
+ }
+
+ void shutdown() {
+ stop = true;
+ signal_thread();
+ join();
+ }
+
+ // thread entry point
+ void *entry() override {
+ while (!stop) {
+ // build fd list
+ struct pollfd fds[33];
+
+ lock.lock();
+ int num_fds = 0;
+ fds[num_fds].fd = pipefd[0];
+ fds[num_fds].events = POLLIN | POLLERR;
+ fds[num_fds].revents = 0;
+ ++num_fds;
+ for (unsigned i=0; i<32; i++) {
+ if (handlers[i]) {
+ fds[num_fds].fd = handlers[i]->pipefd[0];
+ fds[num_fds].events = POLLIN | POLLERR;
+ fds[num_fds].revents = 0;
+ ++num_fds;
+ }
+ }
+ lock.unlock();
+
+ // wait for data on any of those pipes
+ int r = poll(fds, num_fds, -1);
+ if (stop)
+ break;
+ if (r > 0) {
+ char v;
+
+ // consume byte from signal socket, if any.
+ TEMP_FAILURE_RETRY(read(pipefd[0], &v, 1));
+
+ lock.lock();
+ for (unsigned signum=0; signum<32; signum++) {
+ if (handlers[signum]) {
+ r = read(handlers[signum]->pipefd[0], &v, 1);
+ if (r == 1) {
+ siginfo_t * siginfo = &handlers[signum]->info_t;
+ ostringstream message;
+ message << "received signal: " << sig_str(signum);
+ switch (siginfo->si_code) {
+ case SI_USER:
+ message << " from " << get_name_by_pid(siginfo->si_pid);
+ // If PID is undefined, it doesn't have a meaning to be displayed
+ if (siginfo->si_pid) {
+ message << " (PID: " << siginfo->si_pid << ")";
+ } else {
+ message << " ( Could be generated by pthread_kill(), raise(), abort(), alarm() )";
+ }
+ message << " UID: " << siginfo->si_uid;
+ break;
+ default:
+ /* As we have a not expected signal, let's report the structure to help debugging */
+ message << ", si_code : " << siginfo->si_code;
+ message << ", si_value (int): " << siginfo->si_value.sival_int;
+ message << ", si_value (ptr): " << siginfo->si_value.sival_ptr;
+ message << ", si_errno: " << siginfo->si_errno;
+ message << ", si_pid : " << siginfo->si_pid;
+ message << ", si_uid : " << siginfo->si_uid;
+ message << ", si_addr" << siginfo->si_addr;
+ message << ", si_status" << siginfo->si_status;
+ break;
+ }
+ derr << message.str() << dendl;
+ handlers[signum]->handler(signum);
+ }
+ }
+ }
+ lock.unlock();
+ }
+ }
+ return NULL;
+ }
+
+ void queue_signal(int signum) {
+ // If this signal handler is registered, the callback must be
+ // defined. We can do this without the lock because we will never
+ // have the signal handler defined without the handlers entry also
+ // being filled in.
+ ceph_assert(handlers[signum]);
+ int r = write(handlers[signum]->pipefd[1], " ", 1);
+ ceph_assert(r == 1);
+ }
+
+ void queue_signal_info(int signum, siginfo_t *siginfo, void * content) {
+ // If this signal handler is registered, the callback must be
+ // defined. We can do this without the lock because we will never
+ // have the signal handler defined without the handlers entry also
+ // being filled in.
+ ceph_assert(handlers[signum]);
+ memcpy(&handlers[signum]->info_t, siginfo, sizeof(siginfo_t));
+ int r = write(handlers[signum]->pipefd[1], " ", 1);
+ ceph_assert(r == 1);
+ }
+
+ void register_handler(int signum, signal_handler_t handler, bool oneshot);
+ void unregister_handler(int signum, signal_handler_t handler);
+};
+
+static SignalHandler *g_signal_handler = NULL;
+
+static void handler_signal_hook(int signum, siginfo_t * siginfo, void * content) {
+ g_signal_handler->queue_signal_info(signum, siginfo, content);
+}
+
+void SignalHandler::register_handler(int signum, signal_handler_t handler, bool oneshot)
+{
+ int r;
+
+ ceph_assert(signum >= 0 && signum < 32);
+
+ safe_handler *h = new safe_handler;
+
+ r = pipe_cloexec(h->pipefd);
+ ceph_assert(r == 0);
+ r = fcntl(h->pipefd[0], F_SETFL, O_NONBLOCK);
+ ceph_assert(r == 0);
+
+ h->handler = handler;
+ lock.lock();
+ handlers[signum] = h;
+ lock.unlock();
+
+ // signal thread so that it sees our new handler
+ signal_thread();
+
+ // install our handler
+ struct sigaction oldact;
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+
+ act.sa_handler = (signal_handler_t)handler_signal_hook;
+ sigfillset(&act.sa_mask); // mask all signals in the handler
+ act.sa_flags = SA_SIGINFO | (oneshot ? SA_RESETHAND : 0);
+ int ret = sigaction(signum, &act, &oldact);
+ ceph_assert(ret == 0);
+}
+
+void SignalHandler::unregister_handler(int signum, signal_handler_t handler)
+{
+ ceph_assert(signum >= 0 && signum < 32);
+ safe_handler *h = handlers[signum];
+ ceph_assert(h);
+ ceph_assert(h->handler == handler);
+
+ // restore to default
+ signal(signum, SIG_DFL);
+
+ // _then_ remove our handlers entry
+ lock.lock();
+ handlers[signum] = NULL;
+ lock.unlock();
+
+ // this will wake up select() so that worker thread sees our handler is gone
+ close(h->pipefd[0]);
+ close(h->pipefd[1]);
+ delete h;
+}
+
+
+// -------
+
+void init_async_signal_handler()
+{
+ ceph_assert(!g_signal_handler);
+ g_signal_handler = new SignalHandler;
+}
+
+void shutdown_async_signal_handler()
+{
+ ceph_assert(g_signal_handler);
+ delete g_signal_handler;
+ g_signal_handler = NULL;
+}
+
+void queue_async_signal(int signum)
+{
+ ceph_assert(g_signal_handler);
+ g_signal_handler->queue_signal(signum);
+}
+
+void register_async_signal_handler(int signum, signal_handler_t handler)
+{
+ ceph_assert(g_signal_handler);
+ g_signal_handler->register_handler(signum, handler, false);
+}
+
+void register_async_signal_handler_oneshot(int signum, signal_handler_t handler)
+{
+ ceph_assert(g_signal_handler);
+ g_signal_handler->register_handler(signum, handler, true);
+}
+
+void unregister_async_signal_handler(int signum, signal_handler_t handler)
+{
+ ceph_assert(g_signal_handler);
+ g_signal_handler->unregister_handler(signum, handler);
+}
+
+
+
diff --git a/src/global/signal_handler.h b/src/global/signal_handler.h
new file mode 100644
index 00000000..c101b2e2
--- /dev/null
+++ b/src/global/signal_handler.h
@@ -0,0 +1,56 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2011 New Dream Network
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_GLOBAL_SIGNAL_HANDLER_H
+#define CEPH_GLOBAL_SIGNAL_HANDLER_H
+
+#include <signal.h>
+#include "acconfig.h"
+
+typedef void (*signal_handler_t)(int);
+
+#ifdef HAVE_SIGDESCR_NP
+# define sig_str(signum) sigdescr_np(signum)
+#elif HAVE_REENTRANT_STRSIGNAL
+# define sig_str(signum) strsignal(signum)
+#else
+# define sig_str(signum) sys_siglist[signum]
+#endif
+
+void install_sighandler(int signum, signal_handler_t handler, int flags);
+
+// handles SIGHUP
+void sighup_handler(int signum);
+
+// Install the standard Ceph signal handlers
+void install_standard_sighandlers(void);
+
+
+/// initialize async signal handler framework
+void init_async_signal_handler();
+
+/// shutdown async signal handler framework
+void shutdown_async_signal_handler();
+
+/// queue an async signal
+void queue_async_signal(int signum);
+
+/// install a safe, async, callback for the given signal
+void register_async_signal_handler(int signum, signal_handler_t handler);
+void register_async_signal_handler_oneshot(int signum, signal_handler_t handler);
+
+/// uninstall a safe async signal callback
+void unregister_async_signal_handler(int signum, signal_handler_t handler);
+
+#endif