--- /dev/null
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "SignalHandler.hpp"
+
+#ifndef _WIN32
+
+# include "Context.hpp"
+
+namespace {
+
+SignalHandler* g_the_signal_handler = nullptr;
+sigset_t g_fatal_signal_set;
+
+void
+register_signal_handler(int signum)
+{
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SignalHandler::on_signal;
+ act.sa_mask = g_fatal_signal_set;
+# ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+# endif
+ sigaction(signum, &act, nullptr);
+}
+
+} // namespace
+
+SignalHandler::SignalHandler(Context& ctx) : m_ctx(ctx)
+{
+ assert(!g_the_signal_handler);
+ g_the_signal_handler = this;
+
+ sigemptyset(&g_fatal_signal_set);
+ sigaddset(&g_fatal_signal_set, SIGINT);
+ sigaddset(&g_fatal_signal_set, SIGTERM);
+# ifdef SIGHUP
+ sigaddset(&g_fatal_signal_set, SIGHUP);
+# endif
+# ifdef SIGQUIT
+ sigaddset(&g_fatal_signal_set, SIGQUIT);
+# endif
+
+ register_signal_handler(SIGINT);
+ register_signal_handler(SIGTERM);
+# ifdef SIGHUP
+ register_signal_handler(SIGHUP);
+# endif
+# ifdef SIGQUIT
+ register_signal_handler(SIGQUIT);
+# endif
+}
+
+SignalHandler::~SignalHandler()
+{
+ assert(g_the_signal_handler);
+ g_the_signal_handler = nullptr;
+}
+
+void
+SignalHandler::on_signal(int signum)
+{
+ assert(g_the_signal_handler);
+ Context& ctx = g_the_signal_handler->m_ctx;
+
+ // Unregister handler for this signal so that we can send the signal to
+ // ourselves at the end of the handler.
+ signal(signum, SIG_DFL);
+
+ // If ccache was killed explicitly, then bring the compiler subprocess (if
+ // any) with us as well.
+ if (signum == SIGTERM && ctx.compiler_pid != 0
+ && waitpid(ctx.compiler_pid, nullptr, WNOHANG) == 0) {
+ kill(ctx.compiler_pid, signum);
+ }
+
+ ctx.unlink_pending_tmp_files_signal_safe();
+
+ if (ctx.compiler_pid != 0) {
+ // Wait for compiler subprocess to exit before we snuff it.
+ waitpid(ctx.compiler_pid, nullptr, 0);
+ }
+
+ // Resend signal to ourselves to exit properly after returning from the
+ // handler.
+ kill(getpid(), signum);
+}
+
+#else // !_WIN32
+
+SignalHandler::SignalHandler(Context& ctx) : m_ctx(ctx)
+{
+}
+
+SignalHandler::~SignalHandler()
+{
+}
+
+#endif // !_WIN32
+
+void
+SignalHandler::block_signals()
+{
+#ifndef _WIN32
+ sigprocmask(SIG_BLOCK, &g_fatal_signal_set, nullptr);
+#endif
+}
+
+void
+SignalHandler::unblock_signals()
+{
+#ifndef _WIN32
+ sigset_t empty;
+ sigemptyset(&empty);
+ sigprocmask(SIG_SETMASK, &empty, nullptr);
+#endif
+}
+
+SignalHandlerBlocker::SignalHandlerBlocker()
+{
+ SignalHandler::block_signals();
+}
+
+SignalHandlerBlocker::~SignalHandlerBlocker()
+{
+ SignalHandler::unblock_signals();
+}
#include "FormatNonstdStringView.hpp"
#include "MiniTrace.hpp"
#include "ProgressBar.hpp"
+#include "SignalHandler.hpp"
#include "StdMakeUnique.hpp"
#include "Util.hpp"
#include "argprocessing.hpp"
#include "compress.hpp"
#include "exceptions.hpp"
#include "execute.hpp"
-#include "exitfn.hpp"
#include "hash.hpp"
#include "hashutil.hpp"
#include "language.hpp"
struct pending_tmp_file* next;
};
-// Temporary files to remove at program exit.
-static struct pending_tmp_file* pending_tmp_files = nullptr;
-
// How often (in seconds) to scan $CCACHE_DIR/tmp for left-over temporary
// files.
static const int k_tempdir_cleanup_interval = 2 * 24 * 60 * 60; // 2 days
-#ifndef _WIN32
-static sigset_t fatal_signal_set;
-#endif
-
-// PID of currently executing compiler that we have started, if any. 0 means no
-// ongoing compilation. Not used in the _WIN32 case.
-static pid_t compiler_pid = 0;
-
// This is a string that identifies the current "version" of the hash sum
// computed by ccache. If, for any reason, we want to force the hash sum to be
// different for the same input in a new ccache version, we can just change
throw Failure(stat, exit_code);
}
-void
-block_signals()
-{
-#ifndef _WIN32
- sigprocmask(SIG_BLOCK, &fatal_signal_set, nullptr);
-#endif
-}
-
-void
-unblock_signals()
-{
-#ifndef _WIN32
- sigset_t empty;
- sigemptyset(&empty);
- sigprocmask(SIG_SETMASK, &empty, nullptr);
-#endif
-}
-
-static void
-add_pending_tmp_file(const char* path)
-{
- block_signals();
- auto e = static_cast<pending_tmp_file*>(x_malloc(sizeof(pending_tmp_file)));
- e->path = x_strdup(path);
- e->next = pending_tmp_files;
- pending_tmp_files = e;
- unblock_signals();
-}
-
-static void
-do_clean_up_pending_tmp_files()
-{
- struct pending_tmp_file* p = pending_tmp_files;
- while (p) {
- // Can't call tmp_unlink here since its cc_log calls aren't signal safe.
- unlink(p->path);
- p = p->next;
- // Leak p->path and p here because clean_up_pending_tmp_files needs to be
- // signal safe.
- }
-}
-
-static void
-clean_up_pending_tmp_files()
-{
- block_signals();
- do_clean_up_pending_tmp_files();
- unblock_signals();
-}
-
-#ifndef _WIN32
-static void
-signal_handler(int signum)
-{
- // Unregister handler for this signal so that we can send the signal to
- // ourselves at the end of the handler.
- signal(signum, SIG_DFL);
-
- // If ccache was killed explicitly, then bring the compiler subprocess (if
- // any) with us as well.
- if (signum == SIGTERM && compiler_pid != 0
- && waitpid(compiler_pid, nullptr, WNOHANG) == 0) {
- kill(compiler_pid, signum);
- }
-
- do_clean_up_pending_tmp_files();
-
- if (compiler_pid != 0) {
- // Wait for compiler subprocess to exit before we snuff it.
- waitpid(compiler_pid, nullptr, 0);
- }
-
- // Resend signal to ourselves to exit properly after returning from the
- // handler.
- kill(getpid(), signum);
-}
-
-static void
-register_signal_handler(int signum)
-{
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- act.sa_handler = signal_handler;
- act.sa_mask = fatal_signal_set;
-# ifdef SA_RESTART
- act.sa_flags = SA_RESTART;
-# endif
- sigaction(signum, &act, nullptr);
-}
-
-static void
-set_up_signal_handlers()
-{
- sigemptyset(&fatal_signal_set);
- sigaddset(&fatal_signal_set, SIGINT);
- sigaddset(&fatal_signal_set, SIGTERM);
-# ifdef SIGHUP
- sigaddset(&fatal_signal_set, SIGHUP);
-# endif
-# ifdef SIGQUIT
- sigaddset(&fatal_signal_set, SIGQUIT);
-# endif
-
- register_signal_handler(SIGINT);
- register_signal_handler(SIGTERM);
-# ifdef SIGHUP
- register_signal_handler(SIGHUP);
-# endif
-# ifdef SIGQUIT
- register_signal_handler(SIGQUIT);
-# endif
-}
-#endif // _WIN32
-
static void
clean_up_internal_tempdir(const Config& config)
{
}
static void
-fclose_exitfn(void* context)
-{
- fclose((FILE*)context);
-}
-
-static void
-dump_debug_log_buffer_exitfn(void* context)
-{
- Context& ctx = *static_cast<Context*>(context);
- if (!ctx.config.debug()) {
- return;
- }
-
- std::string path = fmt::format("{}.ccache-log", ctx.args_info.output_obj);
- cc_dump_debug_log_buffer(path.c_str());
-}
-
-static void
-init_hash_debug(const Context& ctx,
+init_hash_debug(Context& ctx,
struct hash* hash,
const char* obj_path,
char type,
return;
}
- char* path = format("%s.ccache-input-%c", obj_path, type);
- FILE* debug_binary_file = fopen(path, "wb");
+ std::string path = fmt::format("{}.ccache-input-{}", obj_path, type);
+ File debug_binary_file(path, "wb");
if (debug_binary_file) {
- hash_enable_debug(hash, section_name, debug_binary_file, debug_text_file);
- exitfn_add(fclose_exitfn, debug_binary_file);
+ hash_enable_debug(
+ hash, section_name, debug_binary_file.get(), debug_text_file);
+ ctx.hash_debug_files.push_back(std::move(debug_binary_file));
} else {
- cc_log("Failed to open %s: %s", path, strerror(errno));
+ cc_log("Failed to open %s: %s", path.c_str(), strerror(errno));
}
- free(path);
}
static GuessedCompiler
tmp_stderr = format("%s/tmp.stderr", ctx.config.temporary_dir().c_str());
tmp_stderr_fd = create_tmp_fd(&tmp_stderr);
status = execute(
- args.to_argv().data(), tmp_stdout_fd, tmp_stderr_fd, &compiler_pid);
+ args.to_argv().data(), tmp_stdout_fd, tmp_stderr_fd, &ctx.compiler_pid);
args.pop_back(3);
} else {
// The cached result path is not known yet, use temporary files.
status = execute(depend_mode_args.to_argv().data(),
tmp_stdout_fd,
tmp_stderr_fd,
- &compiler_pid);
+ &ctx.compiler_pid);
}
MTR_END("execute", "compiler");
fmt::format("{}/{}.stdout", ctx.config.temporary_dir(), input_base));
int stdout_fd = stdout_fd_and_path.first;
stdout_path = stdout_fd_and_path.second;
- add_pending_tmp_file(stdout_path.c_str());
+ ctx.register_pending_tmp_file(stdout_path);
auto stderr_fd_and_path = Util::create_temp_fd(
fmt::format("{}/tmp.cpp_stderr", ctx.config.temporary_dir()));
int stderr_fd = stderr_fd_and_path.first;
stderr_path = stderr_fd_and_path.second;
- add_pending_tmp_file(stderr_path.c_str());
+ ctx.register_pending_tmp_file(stderr_path);
size_t args_added = 2;
args.push_back("-E");
cc_log("Running preprocessor");
MTR_BEGIN("execute", "preprocessor");
status =
- execute(args.to_argv().data(), stdout_fd, stderr_fd, &compiler_pid);
+ execute(args.to_argv().data(), stdout_fd, stderr_fd, &ctx.compiler_pid);
MTR_END("execute", "preprocessor");
args.pop_back(args_added);
}
ctx.i_tmpfile =
fmt::format("{}.{}", stdout_path, ctx.config.cpp_extension());
x_rename(stdout_path.c_str(), ctx.i_tmpfile.c_str());
- add_pending_tmp_file(ctx.i_tmpfile.c_str());
+ ctx.register_pending_tmp_file(ctx.i_tmpfile);
}
if (!ctx.config.run_second_cpp()) {
}
// Initialize ccache, must be called once before anything else is run.
-static Context&
-initialize(int argc, const char* const* argv)
+static void
+initialize(Context& ctx, int argc, const char* const* argv)
{
- // This object is placed onto the heap so it is available in exit functions
- // which run after main(). It is cleaned up by the last exit function.
- Context* ctx = new Context;
-
- set_up_config(ctx->config);
- set_up_context(*ctx, argc, argv);
- init_log(ctx->config);
-
- exitfn_init();
- exitfn_delete_context(ctx);
- exitfn_add(stats_flush, ctx);
- exitfn_add_nullary(clean_up_pending_tmp_files);
+ set_up_config(ctx.config);
+ set_up_context(ctx, argc, argv);
+ init_log(ctx.config);
cc_log("=== CCACHE %s STARTED =========================================",
CCACHE_VERSION);
if (getenv("CCACHE_INTERNAL_TRACE")) {
#ifdef MTR_ENABLED
- ctx->mini_trace = std::make_unique<MiniTrace>(ctx->args_info);
+ ctx.mini_trace = std::make_unique<MiniTrace>(ctx->args_info);
#else
cc_log("Error: tracing is not enabled!");
#endif
}
-
- return *ctx;
}
// Make a copy of stderr that will not be cached, so things like distcc can
static int
cache_compilation(int argc, const char* const* argv)
{
-#ifndef _WIN32
- set_up_signal_handlers();
-#endif
-
// Needed for portability when using localtime_r.
tzset();
- Context& ctx = initialize(argc, argv);
+ auto ctx = std::make_unique<Context>();
+ SignalHandler signal_handler(*ctx);
+
+ initialize(*ctx, argc, argv);
MTR_BEGIN("main", "find_compiler");
- find_compiler(ctx, argv);
+ find_compiler(*ctx, argv);
MTR_END("main", "find_compiler");
try {
- enum stats stat = do_cache_compilation(ctx, argv);
- stats_update(ctx, stat);
+ enum stats stat = do_cache_compilation(*ctx, argv);
+ stats_update(*ctx, stat);
return EXIT_SUCCESS;
} catch (const Failure& e) {
if (e.stat() != STATS_NONE) {
- stats_update(ctx, e.stat());
+ stats_update(*ctx, e.stat());
}
if (e.exit_code()) {
}
// Else: Fall back to running the real compiler.
- assert(!ctx.orig_args.empty());
+ assert(!ctx->orig_args.empty());
- ctx.orig_args.erase_with_prefix("--ccache-");
- add_prefix(ctx, ctx.orig_args, ctx.config.prefix_command());
+ ctx->orig_args.erase_with_prefix("--ccache-");
+ add_prefix(*ctx, ctx->orig_args, ctx->config.prefix_command());
cc_log("Failed; falling back to running the real compiler");
- // exitfn_call deletes ctx and thereby ctx.orig_orgs, so save it.
- Args saved_orig_args(std::move(ctx.orig_args));
+ Args saved_orig_args(std::move(ctx->orig_args));
auto execv_argv = saved_orig_args.to_argv();
cc_log_argv("Executing ", execv_argv.data());
- exitfn_call();
+ ctx.reset(); // Dump debug logs last thing before executing.
execv(execv_argv[0], const_cast<char* const*>(execv_argv.data()));
fatal("execv of %s failed: %s", execv_argv[0], strerror(errno));
}
cc_log("Object file: %s", ctx.args_info.output_obj.c_str());
MTR_META_THREAD_NAME(ctx.args_info.output_obj.c_str());
- // Need to dump log buffer as the last exit function to not lose any logs.
- exitfn_add_last(dump_debug_log_buffer_exitfn, &ctx);
-
- FILE* debug_text_file = nullptr;
if (ctx.config.debug()) {
std::string path =
fmt::format("{}.ccache-input-text", ctx.args_info.output_obj);
- debug_text_file = fopen(path.c_str(), "w");
+ File debug_text_file(path, "w");
if (debug_text_file) {
- exitfn_add(fclose_exitfn, debug_text_file);
+ ctx.hash_debug_files.push_back(std::move(debug_text_file));
} else {
cc_log("Failed to open %s: %s", path.c_str(), strerror(errno));
}
}
+ FILE* debug_text_file = !ctx.hash_debug_files.empty()
+ ? ctx.hash_debug_files.front().get()
+ : nullptr;
+
struct hash* common_hash = hash_init();
init_hash_debug(ctx,
common_hash,
{"zero-stats", no_argument, nullptr, 'z'},
{nullptr, 0, nullptr, 0}};
- Context& ctx = initialize(argc, argv);
+ Context ctx;
+ initialize(ctx, argc, argv);
int c;
while ((c = getopt_long(argc,
+++ /dev/null
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#include "exitfn.hpp"
-
-#include "Context.hpp"
-#include "legacy_util.hpp"
-#include "logging.hpp"
-
-struct exit_function
-{
- void (*function)(void*);
- void* context;
- struct exit_function* next;
-};
-
-struct nullary_exit_function
-{
- void (*function)();
-};
-
-static struct exit_function* exit_functions;
-static class Context* context_to_clean_up;
-
-static void
-call_nullary_exit_function(void* context)
-{
- struct nullary_exit_function* p = (struct nullary_exit_function*)context;
- p->function();
- free(p);
-}
-
-// Initialize exit functions. Must be called once before exitfn_add* are used.
-void
-exitfn_init()
-{
- if (atexit(exitfn_call) != 0) {
- fatal("atexit failed: %s", strerror(errno));
- }
-}
-
-// Add a nullary function to be called when ccache exits. Functions are called
-// in reverse order.
-void
-exitfn_add_nullary(void (*function)())
-{
- auto p = static_cast<exit_function*>(x_malloc(sizeof(exit_function)));
- p->function = reinterpret_cast<void (*)(void*)>(function);
- exitfn_add(call_nullary_exit_function, p);
-}
-
-// Add a function to be called with a context parameter when ccache exits.
-// Functions are called in LIFO order except when added via exitfn_add_last.
-void
-exitfn_add(void (*function)(void*), void* context)
-{
- auto p = static_cast<exit_function*>(x_malloc(sizeof(exit_function)));
- p->function = function;
- p->context = context;
- p->next = exit_functions;
- exit_functions = p;
-}
-
-// Add a function to be called with a context parameter when ccache exits. In
-// contrast to exitfn_add, exitfn_add_last sets up the function to be called
-// last.
-void
-exitfn_add_last(void (*function)(void*), void* context)
-{
- auto p = static_cast<exit_function*>(x_malloc(sizeof(exit_function)));
- p->function = function;
- p->context = context;
- p->next = nullptr;
-
- struct exit_function** q = &exit_functions;
- while (*q) {
- q = &(*q)->next;
- }
- *q = p;
-}
-
-// Remember a Context pointer to delete after all exit functions have run.
-// This function can only be called once.
-void
-exitfn_delete_context(Context* ctx)
-{
- assert(context_to_clean_up == nullptr);
- context_to_clean_up = ctx;
-}
-
-// Call added functions.
-void
-exitfn_call()
-{
- struct exit_function* p = exit_functions;
- exit_functions = nullptr;
- while (p) {
- p->function(p->context);
- struct exit_function* q = p;
- p = p->next;
- free(q);
- }
-
- delete context_to_clean_up;
-}