diff options
Diffstat (limited to '')
-rw-r--r-- | src/global/CMakeLists.txt | 19 | ||||
-rw-r--r-- | src/global/global_context.cc | 77 | ||||
-rw-r--r-- | src/global/global_context.h | 54 | ||||
-rw-r--r-- | src/global/global_init.cc | 635 | ||||
-rw-r--r-- | src/global/global_init.h | 101 | ||||
-rw-r--r-- | src/global/pidfile.cc | 250 | ||||
-rw-r--r-- | src/global/pidfile.h | 28 | ||||
-rw-r--r-- | src/global/signal_handler.cc | 702 | ||||
-rw-r--r-- | src/global/signal_handler.h | 65 | ||||
-rw-r--r-- | src/global/signal_handler_win32.cc | 37 |
10 files changed, 1968 insertions, 0 deletions
diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt new file mode 100644 index 000000000..a76b94699 --- /dev/null +++ b/src/global/CMakeLists.txt @@ -0,0 +1,19 @@ +set(libglobal_srcs + global_init.cc + pidfile.cc) +if (WIN32) + list(APPEND libglobal_srcs signal_handler_win32.cc) +else() + list(APPEND libglobal_srcs signal_handler.cc) +endif() + +add_library(libglobal_objs OBJECT ${libglobal_srcs}) +add_dependencies(libglobal_objs legacy-option-headers) + +add_library(global-static STATIC + $<TARGET_OBJECTS:libglobal_objs>) +target_link_libraries(global-static common) + +add_library(global STATIC + $<TARGET_OBJECTS:libglobal_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 000000000..b1e37bfbe --- /dev/null +++ b/src/global/global_context.cc @@ -0,0 +1,77 @@ +// -*- 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 "global/global_context.h" + +#include <string.h> +#include "common/ceph_context.h" +#if defined(WITH_SEASTAR) && !defined(WITH_ALIEN) +#include "crimson/common/config_proxy.h" +#endif + + +/* + * Global variables for use from process context. + */ +namespace TOPNSPC::global { +CephContext *g_ceph_context = NULL; +ConfigProxy& g_conf() { +#if defined(WITH_SEASTAR) && !defined(WITH_ALIEN) + return crimson::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) - 1); + g_eio_devname[sizeof(g_eio_devname) - 1] = '\0'; + } + if (path) { + strncpy(g_eio_path, path, sizeof(g_eio_path) - 1); + g_eio_path[sizeof(g_eio_path) - 1] = '\0'; + } + 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 000000000..9ded22054 --- /dev/null +++ b/src/global/global_context.h @@ -0,0 +1,54 @@ +// -*- 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" +#include "include/common_fwd.h" + +namespace TOPNSPC::global { +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); + +} +using namespace TOPNSPC::global; +#endif diff --git a/src/global/global_init.cc b/src/global/global_init.cc new file mode 100644 index 000000000..57ee5ee71 --- /dev/null +++ b/src/global/global_init.cc @@ -0,0 +1,635 @@ +// -*- 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 <filesystem> +#include "common/async/context_pool.h" +#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 "extblkdev/ExtBlkDevPlugin.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" + +#ifndef _WIN32 +#include <pwd.h> +#include <grp.h> +#endif +#include <errno.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_ + +namespace fs = std::filesystem; + +using std::cerr; +using std::string; + +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) +{ + #ifdef _WIN32 + return 0; + #else + + 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; + #endif +} + +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); + } + } + + if (conf.get_val<bool>("no_config_file")) { + flags |= CINIT_FLAG_NO_DEFAULT_CONFIG_FILE; + } + + 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. " + << conf.get_parse_error() << 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 (!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_error(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, 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); + if (flags & (CINIT_FLAG_NO_DEFAULT_CONFIG_FILE| + CINIT_FLAG_NO_MON_CONFIG)) { + g_conf()->no_mon_config = true; + } + } + + #ifndef _WIN32 + // signal stuff + int siglist[] = { SIGPIPE, 0 }; + block_signals(siglist, NULL); + #endif + + if (g_conf()->fatal_signal_handlers) { + install_standard_sighandlers(); + } + ceph::register_assert_context(g_ceph_context); + + if (g_conf()->log_flush_on_exit) + g_ceph_context->_log->set_flush_on_exit(); + + // drop privileges? + std::ostringstream priv_ss; + + #ifndef _WIN32 + // 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 defined(HAVE_SYS_PRCTL_H) + if (g_conf().get_val<bool>("set_keepcaps")) { + if (prctl(PR_SET_KEEPCAPS, 1) == -1) { + cerr << "warning: unable to set keepcaps flag: " << cpp_strerror(errno) << std::endl; + } + } +#endif + 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 << ")"; + } + } + #endif /* _WIN32 */ + +#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); + + ceph::async::io_context_pool cp(1); + MonClient mc_bootstrap(g_ceph_context, cp); + if (mc_bootstrap.get_monmap_and_config() < 0) { + cp.stop(); + g_ceph_context->_log->flush(); + cerr << "failed to fetch mon config (--no-mon-config to skip)" + << std::endl; + _exit(1); + } + cp.stop(); + } + + // 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)) { + + if (!fs::exists(g_conf()->run_dir.c_str())) { + std::error_code ec; + if (!fs::create_directory(g_conf()->run_dir, ec)) { + cerr << "warning: unable to create " << g_conf()->run_dir + << ec.message() << std::endl; + } + fs::permissions( + g_conf()->run_dir.c_str(), + fs::perms::owner_all | + fs::perms::group_read | fs::perms::group_exec | + fs::perms::others_read | fs::perms::others_exec); + } + } + + // 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_error(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 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->pid_file) < 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) && !defined(_WIN32) + 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 +} + +/* Make file descriptors 0, 1, and possibly 2 point to /dev/null. + * + * Instead of just closing fd, we redirect it to /dev/null with dup2(). + * 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. + */ +int reopen_as_null(CephContext *cct, int fd) +{ + int newfd = open(DEV_NULL, O_RDWR | 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(); + + reopen_as_null(cct, STDIN_FILENO); + + const auto& conf = cct->_conf; + if (pidfile_write(conf->pid_file) < 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; + } +} + +int global_init_shutdown_stderr(CephContext *cct) +{ + reopen_as_null(cct, STDERR_FILENO); + cct->_log->set_stderr_level(-2, -2); + 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 + std::list<string> plugins_list; + get_str_list(plugins, plugins_list); + for (auto 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; + } + } + + std::stringstream ss; + int r = ceph::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 000000000..5a86222bd --- /dev/null +++ b/src/global/global_init.h @@ -0,0 +1,101 @@ +// -*- 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/ceph_context.h" +#include "common/code_environment.h" +#include "common/common_init.h" + +/* + * 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, bool run_pre_init = true); + +// 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 000000000..c46ad2b30 --- /dev/null +++ b/src/global/pidfile.cc @@ -0,0 +1,250 @@ +// -*- 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" + +using std::string; + +// +// 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; + string pf_path; + dev_t pf_dev; + ino_t pf_ino; + + pidfh() { + reset(); + } + ~pidfh() { + remove(); + } + + bool is_open() const { + return !pf_path.empty() && pf_fd != -1; + } + void reset() { + pf_fd = -1; + pf_path.clear(); + pf_dev = 0; + pf_ino = 0; + } + int verify(); + int remove(); + int open(std::string_view pid_file); + 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.c_str(), &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.empty()) + 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.c_str()); + if (ret < 0) { + std::cerr << __func__ << " unlink " << pf_path.c_str() << " failed " + << cpp_strerror(errno) << std::endl; + return -errno; + } + reset(); + return 0; +} + +int pidfh::open(std::string_view pid_file) +{ + pf_path = pid_file; + + int fd; + fd = ::open(pf_path.c_str(), 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; + + // Default Windows file share flags prevent other processes from writing + // to this file. + #ifndef _WIN32 + struct flock l; + l.l_type = F_WRLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.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; + } + const auto lock_errno = errno; + ::close(pf_fd); + reset(); + return -lock_errno; + } + #endif + 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(std::string_view pid_file) +{ + if (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(pid_file); + 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 000000000..eb613e39d --- /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 <string_view> + +// Write a pidfile with the current pid, using the configuration in the +// provided conf structure. +[[nodiscard]] int pidfile_write(std::string_view pid_file); + +// 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 000000000..055763eee --- /dev/null +++ b/src/global/signal_handler.cc @@ -0,0 +1,702 @@ +// -*- 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 + +using std::ostringstream; +using std::string; + +using ceph::BackTrace; +using ceph::JSONFormatter; + +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 + signal(signum, SIG_DFL); + 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; +} + + +void generate_crash_dump(char *base, + const BackTrace& bt, + std::map<std::string,std::string> *extra) +{ + 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, PATH_MAX, "%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); + } + } + + bt.dump(&jf); + + if (extra) { + for (auto& i : *extra) { + jf.dump_string(i.first, i.second); + } + } + + 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); + } + } +} + +static void handle_oneshot_fatal_signal(int signum) +{ + constexpr static pid_t NULL_TID{0}; + static std::atomic<pid_t> handler_tid{NULL_TID}; + if (auto expected{NULL_TID}; + !handler_tid.compare_exchange_strong(expected, ceph_gettid())) { + if (expected == ceph_gettid()) { + // The handler code may itself trigger a SIGSEGV if the heap is corrupt. + // In that case, SIG_DFL followed by return specifies that the default + // signal handler -- presumably dump core -- will handle it. + signal(signum, SIG_DFL); + } else { + // Huh, another thread got into troubles while we are handling the fault. + // If this is i.e. SIGSEGV handler, returning means retrying the faulty + // instruction one more time, and thus all those extra threads will run + // into a busy-wait basically. + } + return; + } + + 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. + ClibBackTrace bt(1); + ostringstream oss; + bt.print(oss); + dout_emergency(oss.str()); + + char crash_base[PATH_MAX] = { 0 }; + + generate_crash_dump(crash_base, bt); + + // 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 (crash_base[0]) { + char fn[PATH_MAX*2]; + snprintf(fn, sizeof(fn)-1, "%s/log", crash_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_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGABRT, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGBUS, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGILL, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGFPE, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGXCPU, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGXFSZ, handle_oneshot_fatal_signal, SA_NODEFER); + install_sighandler(SIGSYS, handle_oneshot_fatal_signal, 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, 0); + 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, 0); + 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 000000000..bfdf04644 --- /dev/null +++ b/src/global/signal_handler.h @@ -0,0 +1,65 @@ +// -*- 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" +#include <map> +#include <string> + +typedef void (*signal_handler_t)(int); +namespace ceph { +struct BackTrace; +} + +#if defined(HAVE_SIGDESCR_NP) +# define sig_str(signum) sigdescr_np(signum) +#elif defined(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); + +void generate_crash_dump(char *base, + const ceph::BackTrace& bt, + std::map<std::string,std::string> *extra = 0); + +#endif diff --git a/src/global/signal_handler_win32.cc b/src/global/signal_handler_win32.cc new file mode 100644 index 000000000..af4c0cd86 --- /dev/null +++ b/src/global/signal_handler_win32.cc @@ -0,0 +1,37 @@ +// -*- 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) 2019 SUSE LINUX GmbH + * + * 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 "global/signal_handler.h" + +void install_sighandler(int signum, signal_handler_t handler, int flags) {} +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){} |