#include "AtomicFile.hpp"
+#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "exceptions.hpp"
AtomicFile::AtomicFile(const std::string& path, Mode mode) : m_path(path)
{
- auto fd_and_path = Util::create_temp_fd(path + ".tmp");
- m_stream = fdopen(fd_and_path.first, mode == Mode::binary ? "w+b" : "w+");
- m_tmp_path = std::move(fd_and_path.second);
+ TemporaryFile tmp_file(path + ".tmp");
+ m_stream = fdopen(tmp_file.fd.release(), mode == Mode::binary ? "w+b" : "w+");
+ m_tmp_path = std::move(tmp_file.path);
}
AtomicFile::~AtomicFile()
ResultRetriever.cpp
SignalHandler.cpp
Stat.cpp
+ TemporaryFile.cpp
ThreadPool.cpp
Util.cpp
ZstdCompressor.cpp
#include "Finalizer.hpp"
#include "Hash.hpp"
#include "Stat.hpp"
+#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "ccache.hpp"
#include "logging.hpp"
// Create the new file to a temporary name to prevent other processes from
// mapping it before it is fully initialized.
- auto temp_fd_and_path = Util::create_temp_fd(filename);
- const auto& temp_path = temp_fd_and_path.second;
+ TemporaryFile tmp_file(filename);
- Fd temp_fd(temp_fd_and_path.first);
- Finalizer temp_file_remover([=] { unlink(temp_path.c_str()); });
+ Finalizer temp_file_remover([&] { unlink(tmp_file.path.c_str()); });
bool is_nfs;
- if (Util::is_nfs_fd(*temp_fd, &is_nfs) == 0 && is_nfs) {
+ if (Util::is_nfs_fd(*tmp_file.fd, &is_nfs) == 0 && is_nfs) {
cc_log(
"Inode cache not supported because the cache file would be located on"
" nfs: %s",
filename.c_str());
return false;
}
- int err = Util::fallocate(*temp_fd, sizeof(SharedRegion));
+ int err = Util::fallocate(*tmp_file.fd, sizeof(SharedRegion));
if (err) {
cc_log("Failed to allocate file space for inode cache: %s", strerror(err));
return false;
sizeof(SharedRegion),
PROT_READ | PROT_WRITE,
MAP_SHARED,
- *temp_fd,
+ *tmp_file.fd,
0));
if (sr == reinterpret_cast<void*>(-1)) {
cc_log("Failed to mmap new inode cache: %s", strerror(errno));
}
munmap(sr, sizeof(SharedRegion));
- temp_fd.close();
+ tmp_file.fd.close();
// link() will fail silently if a file with the same name already exists.
// This will be the case if two processes try to create a new file
// simultaneously. Thus close the current file handle and reopen a new one,
// which will make us use the first created file even if we didn't win the
// race.
- if (link(temp_path.c_str(), filename.c_str()) != 0) {
+ if (link(tmp_file.path.c_str(), filename.c_str()) != 0) {
cc_log("Failed to link new inode cache: %s", strerror(errno));
return false;
}
# include "ArgsInfo.hpp"
# include "MiniTrace.hpp"
+# include "TemporaryFile.hpp"
# include "Util.hpp"
# include "legacy_util.hpp"
MiniTrace::MiniTrace(const ArgsInfo& args_info)
: m_args_info(args_info), m_trace_id(reinterpret_cast<void*>(getpid()))
{
- auto fd_and_path =
- Util::create_temp_fd(get_system_tmp_dir() + "/ccache-trace");
- m_tmp_trace_file = fd_and_path.second;
- close(fd_and_path.first);
+ TemporaryFile tmp_file(get_system_tmp_dir() + "/ccache-trace");
+ m_tmp_trace_file = tmp_file.path;
mtr_init(m_tmp_trace_file.c_str());
MTR_INSTANT_C("", "", "time", fmt::format("{:f}", time_seconds()).c_str());
--- /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 "TemporaryFile.hpp"
+
+#include "Util.hpp"
+#include "legacy_util.hpp"
+
+using nonstd::string_view;
+
+namespace {
+
+#ifndef _WIN32
+mode_t
+get_umask()
+{
+ static bool mask_retrieved = false;
+ static mode_t mask;
+ if (!mask_retrieved) {
+ mask = umask(0);
+ umask(mask);
+ mask_retrieved = true;
+ }
+ return mask;
+}
+#endif
+
+#ifndef HAVE_MKSTEMP
+// Cheap and nasty mkstemp replacement.
+int
+mkstemp(char* name_template)
+{
+# ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+# endif
+ mktemp(name_template);
+# ifdef __GNUC__
+# pragma GCC diagnostic pop
+# endif
+ return open(name_template, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
+}
+#endif
+
+} // namespace
+
+TemporaryFile::TemporaryFile(string_view path_prefix)
+{
+ if (!initialize(path_prefix) && errno == ENOENT) {
+ auto dir = Util::dir_name(path);
+ if (!Util::create_dir(dir)) {
+ fatal("Failed to create directory %s: %s",
+ std::string(dir).c_str(),
+ strerror(errno));
+ }
+ initialize(path_prefix);
+ }
+ if (!fd) {
+ fatal("Failed to create temporary file for %s: %s",
+ path.c_str(),
+ strerror(errno));
+ }
+
+ set_cloexec_flag(*fd);
+#ifndef _WIN32
+ fchmod(*fd, 0666 & ~get_umask());
+#endif
+}
+
+bool
+TemporaryFile::initialize(string_view path_prefix)
+{
+ path = std::string(path_prefix);
+ path += ".XXXXXX";
+ fd = Fd(mkstemp(const_cast<char*>(path.data()))); // cast needed before C++17
+ return bool(fd);
+}
--- /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
+
+#pragma once
+
+#include "Fd.hpp"
+#include "NonCopyable.hpp"
+
+#include "third_party/nonstd/string_view.hpp"
+
+// This class represents a unique temporary file created by mkstemp. The file is
+// not deleted by the destructor.
+class TemporaryFile : NonCopyable
+{
+public:
+ // `path_prefix` is the base path. The resulting filename will be this path
+ // plus a unique suffix. If `path_prefix` refers to a nonexistent directory
+ // the directory will be created if possible.`
+ TemporaryFile(nonstd::string_view path_prefix);
+
+ TemporaryFile(TemporaryFile&& other) = default;
+
+ TemporaryFile& operator=(TemporaryFile&& other) = default;
+
+ // The resulting open file descriptor in read/write mode. Unset on error.
+ Fd fd;
+
+ // The actual filename. Empty on error.
+ std::string path;
+
+private:
+ bool initialize(nonstd::string_view path_prefix);
+};
#include "ResultRetriever.hpp"
#include "SignalHandler.hpp"
#include "StdMakeUnique.hpp"
+#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "argprocessing.hpp"
#include "cleanup.hpp"
return;
}
- std::string tmp_file = fmt::format("{}.tmp{}", output_dep, tmp_string());
-
+ std::string tmp_file = output_dep + ".tmp";
try {
Util::write_file(tmp_file, adjusted_file_content);
} catch (const Error& e) {
// Execute the compiler/preprocessor, with logic to retry without requesting
// colored diagnostics messages if that fails.
static int
-execute(Context& ctx,
- Args& args,
- const std::string& stdout_path,
- int stdout_fd,
- const std::string& stderr_path,
- int stderr_fd)
+do_execute(Context& ctx,
+ Args& args,
+ TemporaryFile&& tmp_stdout,
+ TemporaryFile&& tmp_stderr)
{
if (ctx.diagnostics_color_failed
&& ctx.guessed_compiler == GuessedCompiler::gcc) {
args.erase_with_prefix("-fdiagnostics-color");
}
- int status =
- execute(args.to_argv().data(), stdout_fd, stderr_fd, &ctx.compiler_pid);
+ int status = execute(args.to_argv().data(),
+ std::move(tmp_stdout.fd),
+ std::move(tmp_stderr.fd),
+ &ctx.compiler_pid);
if (status != 0 && !ctx.diagnostics_color_failed
&& ctx.guessed_compiler == GuessedCompiler::gcc) {
- auto errors = Util::read_file(stderr_path);
+ auto errors = Util::read_file(tmp_stderr.path);
if (errors.find("unrecognized command line option") != std::string::npos
&& errors.find("-fdiagnostics-color") != std::string::npos) {
- // Old versions of GCC did not support colored diagnostics.
+ // Old versions of GCC do not support colored diagnostics.
cc_log("-fdiagnostics-color is unsupported; trying again without it");
- if (ftruncate(stdout_fd, 0) < 0 || lseek(stdout_fd, 0, SEEK_SET) < 0) {
- cc_log(
- "Failed to truncate %s: %s", stdout_path.c_str(), strerror(errno));
+
+ tmp_stdout.fd = Fd(open(
+ tmp_stdout.path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600));
+ if (!tmp_stdout.fd) {
+ cc_log("Failed to truncate %s: %s",
+ tmp_stdout.path.c_str(),
+ strerror(errno));
failed(STATS_ERROR);
}
- if (ftruncate(stderr_fd, 0) < 0 || lseek(stderr_fd, 0, SEEK_SET) < 0) {
- cc_log(
- "Failed to truncate %s: %s", stderr_path.c_str(), strerror(errno));
+
+ tmp_stderr.fd = Fd(open(
+ tmp_stderr.path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600));
+ if (!tmp_stderr.fd) {
+ cc_log("Failed to truncate %s: %s",
+ tmp_stderr.path.c_str(),
+ strerror(errno));
failed(STATS_ERROR);
}
+
ctx.diagnostics_color_failed = true;
- return execute(ctx, args, stdout_path, stdout_fd, stderr_path, stderr_fd);
+ return do_execute(
+ ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
}
}
return status;
cc_log("Running real compiler");
MTR_BEGIN("execute", "compiler");
- const auto tmp_stdout_fd_and_path = Util::create_temp_fd(
+ TemporaryFile tmp_stdout(
fmt::format("{}/tmp.stdout", ctx.config.temporary_dir()));
- int tmp_stdout_fd = tmp_stdout_fd_and_path.first;
- const std::string& tmp_stdout = tmp_stdout_fd_and_path.second;
- ctx.register_pending_tmp_file(tmp_stdout);
+ ctx.register_pending_tmp_file(tmp_stdout.path);
+ std::string tmp_stdout_path = tmp_stdout.path;
- const auto tmp_stderr_fd_and_path = Util::create_temp_fd(
+ TemporaryFile tmp_stderr(
fmt::format("{}/tmp.stderr", ctx.config.temporary_dir()));
- int tmp_stderr_fd = tmp_stderr_fd_and_path.first;
- const std::string& tmp_stderr = tmp_stderr_fd_and_path.second;
- ctx.register_pending_tmp_file(tmp_stderr);
+ ctx.register_pending_tmp_file(tmp_stderr.path);
+ std::string tmp_stderr_path = tmp_stderr.path;
int status;
if (!ctx.config.depend_mode()) {
status =
- execute(ctx, args, tmp_stdout, tmp_stdout_fd, tmp_stderr, tmp_stderr_fd);
+ do_execute(ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
args.pop_back(3);
} else {
// Use the original arguments (including dependency options) in depend
add_prefix(ctx, depend_mode_args, ctx.config.prefix_command());
ctx.time_of_compilation = time(nullptr);
- status = execute(ctx,
- depend_mode_args,
- tmp_stdout,
- tmp_stdout_fd,
- tmp_stderr,
- tmp_stderr_fd);
+ status = do_execute(
+ ctx, depend_mode_args, std::move(tmp_stdout), std::move(tmp_stderr));
}
MTR_END("execute", "compiler");
- auto st = Stat::stat(tmp_stdout, Stat::OnError::log);
+ auto st = Stat::stat(tmp_stdout_path, Stat::OnError::log);
if (!st) {
// The stdout file was removed - cleanup in progress? Better bail out.
failed(STATS_MISSING);
// compiler into tmp_stderr.
if (!ctx.cpp_stderr.empty()) {
std::string combined_stderr =
- Util::read_file(ctx.cpp_stderr) + Util::read_file(tmp_stderr);
- Util::write_file(tmp_stderr, combined_stderr);
+ Util::read_file(ctx.cpp_stderr) + Util::read_file(tmp_stderr_path);
+ Util::write_file(tmp_stderr_path, combined_stderr);
}
if (status != 0) {
cc_log("Compiler gave exit status %d", status);
// We can output stderr immediately instead of rerunning the compiler.
- Util::send_to_stderr(Util::read_file(tmp_stderr),
+ Util::send_to_stderr(Util::read_file(tmp_stderr_path),
ctx.args_info.strip_diagnostics_colors);
failed(STATS_STATUS, status);
failed(STATS_EMPTYOUTPUT);
}
- st = Stat::stat(tmp_stderr, Stat::OnError::log);
+ st = Stat::stat(tmp_stderr_path, Stat::OnError::log);
if (!st) {
failed(STATS_ERROR);
}
Result::Writer result_writer(ctx, ctx.result_path());
if (st.size() > 0) {
- result_writer.write(Result::FileType::stderr_output, tmp_stderr);
+ result_writer.write(Result::FileType::stderr_output, tmp_stderr_path);
}
result_writer.write(Result::FileType::object, ctx.args_info.output_obj);
if (ctx.args_info.generating_dependencies) {
}
// Everything OK.
- Util::send_to_stderr(Util::read_file(tmp_stderr),
+ Util::send_to_stderr(Util::read_file(tmp_stderr_path),
ctx.args_info.strip_diagnostics_colors);
}
} else {
// Run cpp on the input file to obtain the .i.
- // Limit the basename to 10 characters in order to cope with filesystem with
- // small maximum filename length limits.
- string_view input_base =
- Util::get_truncated_base_name(ctx.args_info.input_file, 10);
- auto stdout_fd_and_path = Util::create_temp_fd(
- fmt::format("{}/{}.stdout", ctx.config.temporary_dir(), input_base));
- int stdout_fd = stdout_fd_and_path.first;
- stdout_path = stdout_fd_and_path.second;
+ TemporaryFile tmp_stdout(
+ fmt::format("{}/tmp.cpp_stdout", ctx.config.temporary_dir()));
+ stdout_path = tmp_stdout.path;
ctx.register_pending_tmp_file(stdout_path);
- auto stderr_fd_and_path = Util::create_temp_fd(
+ TemporaryFile tmp_stderr(
fmt::format("{}/tmp.cpp_stderr", ctx.config.temporary_dir()));
- int stderr_fd = stderr_fd_and_path.first;
- stderr_path = stderr_fd_and_path.second;
+ stderr_path = tmp_stderr.path;
ctx.register_pending_tmp_file(stderr_path);
size_t args_added = 2;
add_prefix(ctx, args, ctx.config.prefix_command_cpp());
cc_log("Running preprocessor");
MTR_BEGIN("execute", "preprocessor");
- status = execute(ctx, args, stdout_path, stdout_fd, stderr_path, stderr_fd);
+ status =
+ do_execute(ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
MTR_END("execute", "preprocessor");
args.pop_back(args_added);
}
#include "Fd.hpp"
#include "SignalHandler.hpp"
#include "Stat.hpp"
+#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "ccache.hpp"
#include "logging.hpp"
#ifdef _WIN32
int
-execute(const char* const* argv, int fd_out, int fd_err, pid_t* /*pid*/)
+execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* /*pid*/)
{
- return win32execute(argv[0], argv, 1, fd_out, fd_err);
+ return win32execute(argv[0], argv, 1, fd_out.release(), fd_err.release());
}
// Re-create a win32 command line string based on **argv.
add_exe_ext_if_no_to_fullpath(full_path_win_ext, MAX_PATH, ext, path);
BOOL ret = FALSE;
if (length > 8192) {
- auto fd_and_path = Util::create_temp_fd(path);
- Fd fd(fd_and_path.first);
- const char* tmp_file = fd_and_path.second.c_str();
- if (!write_fd(*fd, args, length)) {
+ TemporaryFile tmp_file(path);
+ if (!write_fd(*tmp_file.fd, args, length)) {
cc_log("Error writing @file; this command will probably fail: %s", args);
}
- std::string atfile = fmt::format("\"@{}\"", tmp_file);
+ std::string atfile = fmt::format("\"@{}\"", tmp_file.path);
ret = CreateProcess(nullptr,
const_cast<char*>(atfile.c_str()),
nullptr,
nullptr,
&si,
&pi);
- Util::unlink_tmp(tmp_file);
+ Util::unlink_tmp(tmp_file.path);
}
if (!ret) {
ret = CreateProcess(full_path_win_ext,
// Execute a compiler backend, capturing all output to the given paths the full
// path to the compiler to run is in argv[0].
int
-execute(const char* const* argv, int fd_out, int fd_err, pid_t* pid)
+execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
{
cc_log_argv("Executing ", argv);
if (*pid == 0) {
// Child.
- dup2(fd_out, STDOUT_FILENO);
- close(fd_out);
- dup2(fd_err, STDERR_FILENO);
- close(fd_err);
+ dup2(*fd_out, STDOUT_FILENO);
+ fd_out.close();
+ dup2(*fd_err, STDERR_FILENO);
+ fd_err.close();
x_exit(execv(argv[0], const_cast<char* const*>(argv)));
}
- close(fd_out);
- close(fd_err);
+ fd_out.close();
+ fd_err.close();
int status;
if (waitpid(*pid, &status, 0) != *pid) {
#include "system.hpp"
+#include "Fd.hpp"
+
#include <string>
class Context;
-int execute(const char* const* argv, int fd_out, int fd_err, pid_t* pid);
+int execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid);
std::string
find_executable(const Context& ctx, const char* name, const char* exclude_name);
std::string find_executable_in_path(const char* name,
#include "legacy_util.hpp"
#include "Fd.hpp"
+#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "exceptions.hpp"
#include "logging.hpp"
Fd dest_fd;
char* tmp_file = nullptr;
if (via_tmp_file) {
- tmp_file = x_strdup(dest);
- dest_fd = Fd(create_tmp_fd(&tmp_file));
+ TemporaryFile temp_file(dest);
+ dest_fd = std::move(temp_file.fd);
+ tmp_file = x_strdup(temp_file.path.c_str());
} else {
dest_fd = Fd(open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
if (!dest_fd) {
Fd dest_fd;
char* tmp_file = nullptr;
if (via_tmp_file) {
- tmp_file = x_strdup(dest);
- dest_fd = Fd(create_tmp_fd(&tmp_file));
+ TemporaryFile temp_file(dest);
+ dest_fd = std::move(temp_file.fd);
+ tmp_file = x_strdup(temp_file.path.c_str());
} else {
dest_fd = Fd(open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
if (!dest_fd) {