* Low overhead.
* Compresses data in the cache to save disk space.
* Checksums data in the cache to detect corruption.
+* Optionally uses hard links avoid copies (there are caveats, though).
Limitations
See <<_using_ccache_with_other_compiler_wrappers,USING CCACHE WITH OTHER
COMPILER WRAPPERS>>.
-WARNING: Do not use a hard link, use a symbolic link. A hard link will cause
-``interesting'' problems.
+WARNING: Use a symbolic links for masquerading, not hard links.
Options
-------
that there should be little reason to turn off compression to gain performance.
One exception is if the cache is located on a compressed file system, in which
case the compression performed by ccache of course is redundant.
++
+Compression will be disabled if hard linking (the *hard_link* setting) is
+enabled.
*compression_level* (*CCACHE_COMPRESSLEVEL*)::
the hash sum that identifies the build. The list separator is semicolon on
Windows systems and colon on other systems.
+*hard_link* (*CCACHE_HARDLINK* or *CCACHE_NOHARDLINK*, see <<_boolean_values,Boolean values>> above)::
+
+ If true, ccache will attempt to use hard links to store compiler output
+ files in the cache, and similarly use hard links when retrieving files from
+ the cache. The default is false.
++
+An exception is dependency files (`.d`) which are never stored as hard links.
++
+Files stored via hard links cannot be compressed, so the cache size will likely
+be significantly larger if this option is enabled. However, performance may be
+improved depending on the use case.
++
+WARNING: Do not enable this option unless you are aware of these caveats:
++
+* If the resulting file is modified, the file in the cache will also be
+ modified since they share content, which corrupts the cache entry. As of
+ version 3.8, ccache performs a simple integrity check for cached files by
+ verifying that their sizes are correct. This means that mistakes like `strip
+ file.o` or `echo >file.o` will be detected, but a modification that doesn't
+ change the file size will not.
+* Programs that don't expect that files from two different identical
+ compilations are hard links to each other can fail.
+* Programs that rely on modification times (like ``make'') can be confused if
+ several users (or one user with several build trees) use the same cache
+ directory. The reason for this is that the object files share i-nodes and
+ therefore modification times. If *file.o* is in build tree A (hard-linked
+ from the cache) and *file.o* then is produced by ccache in build tree B by
+ hard-linking from the cache, the modification timestamp will be updated for
+ *file.o* in build tree A as well. This can retrigger relinking in build tree
+ A even though nothing really has changed.
+
*hash_dir* (*CCACHE_HASHDIR* or *CCACHE_NOHASHDIR*, see <<_boolean_values,Boolean values>> above)::
If true (which is the default), ccache will include the current working
conditions should to be met:
* Use the same cache directory.
+* Make sure that the configuration setting *hard_link* is false (which is the
+ default).
* Make sure that all users are in the same group.
* Set the configuration setting *umask* to 002. This ensures that cached files
are accessible to everyone in the group.
----
--
+The reason to avoid the hard link mode is that the hard links cause unwanted
+side effects, as all links to a cached file share the file's modification
+timestamp. This results in false dependencies to be triggered by
+timestamp-based build systems whenever another user links to an existing file.
+Typically, users will see that their libraries and binaries are relinked
+without reason.
+
You may also want to make sure that a base directory is set appropriately, as
discussed in a previous section.
environment["CCACHE_NOCOMPRESS"] = "1"
if options.compression_level:
environment["CCACHE_COMPRESSLEVEL"] = str(options.compression_level)
+ if options.hardlink:
+ environment["CCACHE_HARDLINK"] = "1"
if options.nostats:
environment["CCACHE_NOSTATS"] = "1"
" files (default: %s)" % DEFAULT_DIRECTORY
),
)
+ op.add_option("--hardlink", help="use hard links", action="store_true")
op.add_option(
"--hit-factor",
help=(
print("Compilercheck:", options.compilercheck)
print("Compression:", on_off(not options.no_compression))
print("Compression level:", options.compression_level or "default")
+ print("Hardlink:", on_off(options.hardlink))
print("Nostats:", on_off(options.nostats))
tmp_dir = "%s/perfdir.%d" % (abspath(options.directory), getpid())
// Full path to the file containing the result
// (cachedir/a/b/cdef[...]-size.result).
-static char *cached_result;
+static char *cached_result_path;
// Full path to the file containing the manifest
// (cachedir/a/b/cdef[...]-size.manifest).
return current_working_dir;
}
-// Transform a name to a full path into the cache directory, creating needed
-// sublevels if needed. Caller frees.
-static char *
-get_path_in_cache(const char *name, const char *suffix)
-{
- char *path = x_strdup(conf->cache_dir);
- for (unsigned i = 0; i < conf->cache_dir_levels; ++i) {
- char *p = format("%s/%c", path, name[i]);
- free(path);
- path = p;
- }
-
- char *result =
- format("%s/%s%s", path, name + conf->cache_dir_levels, suffix);
- free(path);
- return result;
-}
-
// This function hashes an include file and stores the path and hash in the
// global included_files variable. If the include file is a PCH, cpp_hash is
// also updated. Takes over ownership of path.
}
MTR_BEGIN("manifest", "manifest_put");
+ cc_log("Adding result name to %s", manifest_path);
if (manifest_put(manifest_path, cached_result_name, included_files)) {
- cc_log("Added result name to %s", manifest_path);
if (x_stat(manifest_path, &st) == 0) {
stats_update_size(
manifest_stats_file,
char result_name_string[DIGEST_STRING_BUFFER_SIZE];
digest_as_string(result_name, result_name_string);
cached_result_name = result_name;
- cached_result = get_path_in_cache(result_name_string, ".result");
+ cached_result_path = get_path_in_cache(result_name_string, ".result");
stats_file = format("%s/%c/stats", conf->cache_dir, result_name_string[0]);
}
args_add(args, "-o");
args_add(args, output_obj);
+ if (conf->hard_link) {
+ // Workaround for Clang bug where it overwrites an existing object file
+ // when it's compiling an assembler file, see
+ // <https://bugs.llvm.org/show_bug.cgi?id=39782>.
+ x_unlink(output_obj);
+ }
+
if (generating_diagnostics) {
args_add(args, "--serialize-diagnostics");
args_add(args, output_dia);
result_files_add(result_files, output_dwo, ".dwo");
}
struct stat orig_dest_st;
- bool orig_dest_existed = stat(cached_result, &orig_dest_st) == 0;
- result_put(cached_result, result_files);
+ bool orig_dest_existed = stat(cached_result_path, &orig_dest_st) == 0;
+ result_put(cached_result_path, result_files);
result_files_free(result_files);
- cc_log("Stored in cache: %s", cached_result);
+ cc_log("Stored in cache: %s", cached_result_path);
- if (x_stat(cached_result, &st) != 0) {
+ if (x_stat(cached_result_path, &st) != 0) {
stats_update(STATS_ERROR);
failed();
}
return NULL;
}
- char manifest_name[DIGEST_STRING_BUFFER_SIZE];
- hash_result_as_string(hash, manifest_name);
- manifest_path = get_path_in_cache(manifest_name, ".manifest");
+ char manifest_name_string[DIGEST_STRING_BUFFER_SIZE];
+ hash_result_as_string(hash, manifest_name_string);
+ manifest_path = get_path_in_cache(manifest_name_string, ".manifest");
manifest_stats_file =
- format("%s/%c/stats", conf->cache_dir, manifest_name[0]);
+ format("%s/%c/stats", conf->cache_dir, manifest_name_string[0]);
cc_log("Looking for result name in %s", manifest_path);
MTR_BEGIN("manifest", "manifest_get");
if (generating_diagnostics) {
result_files_add(result_files, output_dia, ".dia");
}
- bool ok = result_get(cached_result, result_files);
+ bool ok = result_get(cached_result_path, result_files);
result_files_free(result_files);
if (!ok) {
cc_log("Failed to get result from cache");
free(output_dia); output_dia = NULL;
free(output_dwo); output_dwo = NULL;
free(cached_result_name); cached_result_name = NULL;
- free(cached_result); cached_result = NULL;
+ free(cached_result_path); cached_result_path = NULL;
free(manifest_path); manifest_path = NULL;
time_of_compilation = 0;
for (size_t i = 0; i < ignore_headers_len; i++) {
void fatal(const char *format, ...) ATTR_FORMAT(printf, 1, 2) ATTR_NORETURN;
void warn(const char *format, ...) ATTR_FORMAT(printf, 1, 2);
+char *get_path_in_cache(const char *name, const char *suffix);
bool copy_fd(int fd_in, int fd_out);
-bool copy_file(const char *src, const char *dest);
+bool copy_file(const char *src, const char *dest, bool via_tmp_file);
bool move_file(const char *src, const char *dest);
int create_dir(const char *dir);
int create_parent_dirs(const char *path);
// consistency check for the non-compressed case. (A real checksum is used
// for compressed data.)
struct stat st;
- if (x_fstat(fileno(input), &st) != 0
- || (uint64_t)st.st_size != header->content_size) {
+ if (x_fstat(fileno(input), &st) != 0) {
+ return false;
+ }
+ if ((uint64_t)st.st_size != header->content_size) {
*errmsg = format(
- "Bad uncompressed file size (actual %lu bytes, expected %lu bytes)",
- (unsigned long)st.st_size,
- (unsigned long)header->content_size);
+ "Bad uncompressed file size (actual %llu bytes, expected %llu bytes)",
+ (unsigned long long)st.st_size,
+ (unsigned long long)header->content_size);
return false;
}
}
conf->direct_mode = true;
conf->disable = false;
conf->extra_files_to_hash = x_strdup("");
+ conf->hard_link = false;
conf->hash_dir = true;
conf->ignore_headers_in_manifest = x_strdup("");
conf->keep_comments_cpp = false;
ok &= print_item(conf, "direct_mode", printer, context);
ok &= print_item(conf, "disable", printer, context);
ok &= print_item(conf, "extra_files_to_hash", printer, context);
+ ok &= print_item(conf, "hard_link", printer, context);
ok &= print_item(conf, "hash_dir", printer, context);
ok &= print_item(conf, "ignore_headers_in_manifest", printer, context);
ok &= print_item(conf, "keep_comments_cpp", printer, context);
bool direct_mode;
bool disable;
char *extra_files_to_hash;
+ bool hard_link;
bool hash_dir;
char *ignore_headers_in_manifest;
bool keep_comments_cpp;
direct_mode, ITEM(direct_mode, bool)
disable, ITEM(disable, bool)
extra_files_to_hash, ITEM(extra_files_to_hash, env_string)
+hard_link, ITEM(hard_link, bool)
hash_dir, ITEM(hash_dir, bool)
ignore_headers_in_manifest, ITEM(ignore_headers_in_manifest, env_string)
keep_comments_cpp, ITEM(keep_comments_cpp, bool)
DISABLE, "disable"
EXTENSION, "cpp_extension"
EXTRAFILES, "extra_files_to_hash"
+HARDLINK, "hard_link"
HASHDIR, "hash_dir"
IGNOREHEADERS, "ignore_headers_in_manifest"
LIMIT_MULTIPLE, "limit_multiple"
#include "common_header.h"
#include "int_bytes_conversion.h"
#include "compression.h"
+#include "hash.h"
#include "result.h"
// Result data format
// <suffix> ::= suffix_len bytes
// <data_len> ::= uint64_t
// <data> ::= data_len bytes
-// <raw_file_entry> ::= <raw_file_marker> <key_len> <key>
+// <raw_file_entry> ::= <raw_file_marker> <suffix_len> <suffix> <file_len>
// <raw_file_marker> ::= 1 (uint8_t)
-// <key_len> ::= uint8_t
-// <key> ::= key_len bytes
+// <file_len> ::= uint64_t
// <epilogue> ::= <checksum>
// <checksum> ::= uint64_t ; XXH64 of content bytes
//
//
// 1: Introduced in ccache 3.8.
+extern const struct conf *conf;
+extern char *stats_file;
+
const char RESULT_MAGIC[4] = "cCrS";
enum {
uint64_t *sizes;
};
+typedef bool (*read_entry_fn)(
+ struct decompressor *decompressor,
+ struct decompr_state *decompr_state,
+ const char *result_path_in_cache,
+ uint32_t entry_number,
+ const struct result_files *list,
+ FILE *dump_stream);
+
+typedef bool (*write_entry_fn)(
+ struct compressor *compressor,
+ struct compr_state *compr_state,
+ const char *result_path_in_cache,
+ uint32_t entry_number,
+ const struct result_file *file);
+
struct result_files *
result_files_init(void)
{
read_embedded_file_entry(
struct decompressor *decompressor,
struct decompr_state *decompr_state,
+ const char *result_path_in_cache,
uint32_t entry_number,
- struct result_files *list,
+ const struct result_files *list,
FILE *dump_stream)
{
+ (void)result_path_in_cache;
+
bool success = false;
FILE *subfile = NULL;
READ_BYTES(suffix, suffix_len);
suffix[suffix_len] = '\0';
- uint64_t filelen;
- READ_UINT64(filelen);
-
- cc_log("Reading embedded file #%u: %s (%llu)",
- entry_number,
- suffix,
- (unsigned long long)filelen);
+ uint64_t file_len;
+ READ_UINT64(file_len);
bool found = false;
if (dump_stream) {
- fprintf(dump_stream,
- "Entry: %s (size: %" PRIu64 " bytes)\n",
- suffix,
- filelen);
+ fprintf(
+ dump_stream,
+ "Embedded file #%u: %s (%" PRIu64 " bytes)\n",
+ entry_number,
+ suffix,
+ file_len);
} else {
+ cc_log(
+ "Retrieving embedded file #%u %s (%llu bytes)",
+ entry_number,
+ suffix,
+ (unsigned long long)file_len);
+
for (uint32_t i = 0; i < list->n_files; i++) {
if (str_eq(suffix, list->files[i].suffix)) {
found = true;
goto out;
}
char buf[READ_BUFFER_SIZE];
- size_t remain = filelen;
+ size_t remain = file_len;
while (remain > 0) {
size_t n = MIN(remain, sizeof(buf));
READ_BYTES(buf, n);
}
}
}
+
if (!found) {
// Discard the file data.
char buf[READ_BUFFER_SIZE];
- size_t remain = filelen;
+ size_t remain = file_len;
while (remain > 0) {
size_t n = MIN(remain, sizeof(buf));
READ_BYTES(buf, n);
return success;
}
+static char *
+get_raw_file_path(const char *result_path_in_cache, uint32_t entry_number)
+{
+ return format(
+ "%.*s_%u.raw",
+ (int)strlen(result_path_in_cache) - 7, // .result
+ result_path_in_cache,
+ entry_number);
+}
+
+static bool
+copy_raw_file(const char *source, const char *dest, bool to_cache)
+{
+ if (conf->hard_link) {
+ x_try_unlink(dest);
+ cc_log("Hard linking %s to %s", source, dest);
+ int ret = link(source, dest);
+ if (ret == 0) {
+ return true;
+ }
+ cc_log("Failed to hard link %s to %s: %s", source, dest, strerror(errno));
+ }
+
+ cc_log("Copying %s to %s", source, dest);
+ return copy_file(source, dest, to_cache);
+}
+
+static bool
+read_raw_file_entry(
+ struct decompressor *decompressor,
+ struct decompr_state *decompr_state,
+ const char *result_path_in_cache,
+ uint32_t entry_number,
+ const struct result_files *list,
+ FILE *dump_stream)
+{
+ bool success = false;
+ char *raw_path = get_raw_file_path(result_path_in_cache, entry_number);
+
+ uint8_t suffix_len;
+ READ_BYTE(suffix_len);
+
+ char suffix[256 + 1];
+ READ_BYTES(suffix, suffix_len);
+ suffix[suffix_len] = '\0';
+
+ uint64_t file_len;
+ READ_UINT64(file_len);
+
+ if (dump_stream) {
+ fprintf(
+ dump_stream,
+ "Raw file #%u: %s (%" PRIu64 " bytes)\n",
+ entry_number,
+ suffix,
+ file_len);
+ } else {
+ cc_log(
+ "Retrieving raw file #%u %s (%llu bytes)",
+ entry_number,
+ suffix,
+ (unsigned long long)file_len);
+
+ struct stat st;
+ if (x_stat(raw_path, &st) != 0) {
+ goto out;
+ }
+ if ((uint64_t)st.st_size != file_len) {
+ cc_log(
+ "Bad file size of %s (actual %llu bytes, expected %llu bytes)",
+ raw_path,
+ (unsigned long long)st.st_size,
+ (unsigned long long)file_len);
+ goto out;
+ }
+
+ for (uint32_t i = 0; i < list->n_files; i++) {
+ if (str_eq(suffix, list->files[i].suffix)) {
+ if (!copy_raw_file(raw_path, list->files[i].path, false)) {
+ goto out;
+ }
+ // Update modification timestamp to save the file from LRU cleanup
+ // (and, if hard-linked, to make the object file newer than the source
+ // file).
+ update_mtime(raw_path);
+ break;
+ }
+ }
+ }
+
+ success = true;
+
+out:
+ free(raw_path);
+ return success;
+}
+
static bool
read_result(
const char *path,
char **errmsg)
{
*errmsg = NULL;
+ bool cache_miss = false;
bool success = false;
struct decompressor *decompressor = NULL;
struct decompr_state *decompr_state = NULL;
FILE *f = fopen(path, "rb");
if (!f) {
- // Cache miss.
- *errmsg = x_strdup("No such result file");
+ cache_miss = true;
goto out;
}
uint8_t marker;
READ_BYTE(marker);
+ read_entry_fn read_entry;
+
switch (marker) {
case EMBEDDED_FILE_MARKER:
- if (!read_embedded_file_entry(
- decompressor, decompr_state, i, list, dump_stream)) {
- goto out;
- }
+ read_entry = read_embedded_file_entry;
break;
case RAW_FILE_MARKER:
- // TODO: Implement.
- // Fall through.
+ read_entry = read_raw_file_entry;
+ break;
default:
*errmsg = format("Unknown entry type: %u", marker);
goto out;
}
+
+ if (!read_entry(decompressor, decompr_state, path, i, list, dump_stream)) {
+ goto out;
+ }
}
if (i != n_entries) {
if (checksum) {
XXH64_freeState(checksum);
}
- if (!success && !*errmsg) {
- *errmsg = x_strdup("Corrupt result file");
+ if (!success && !cache_miss && !*errmsg) {
+ *errmsg = x_strdup("Corrupt result");
}
return success;
}
WRITE_BYTES(buf_, sizeof(buf_)); \
} while (false)
+static bool
+write_embedded_file_entry(
+ struct compressor *compressor,
+ struct compr_state *compr_state,
+ const char *result_path_in_cache,
+ uint32_t entry_number,
+ const struct result_file *file)
+{
+ (void)result_path_in_cache;
+
+ bool success = false;
+
+ cc_log(
+ "Storing embedded file #%u %s (%llu bytes) from %s",
+ entry_number,
+ file->suffix,
+ (unsigned long long)file->size,
+ file->path);
+
+ WRITE_BYTE(EMBEDDED_FILE_MARKER);
+ size_t suffix_len = strlen(file->suffix);
+ WRITE_BYTE(suffix_len);
+ WRITE_BYTES(file->suffix, suffix_len);
+ WRITE_UINT64(file->size);
+
+ FILE *f = fopen(file->path, "rb");
+ if (!f) {
+ cc_log("Failed to open %s for reading", file->path);
+ goto error;
+ }
+ char buf[READ_BUFFER_SIZE];
+ size_t remain = file->size;
+ while (remain > 0) {
+ size_t n = MIN(remain, sizeof(buf));
+ if (fread(buf, 1, n, f) != n) {
+ goto error;
+ }
+ WRITE_BYTES(buf, n);
+ remain -= n;
+ }
+ fclose(f);
+
+ success = true;
+
+error:
+ return success;
+}
+
+static bool
+write_raw_file_entry(
+ struct compressor *compressor,
+ struct compr_state *compr_state,
+ const char *result_path_in_cache,
+ uint32_t entry_number,
+ const struct result_file *file)
+{
+ bool success = false;
+
+ cc_log(
+ "Storing raw file #%u %s (%llu bytes) from %s",
+ entry_number,
+ file->suffix,
+ (unsigned long long)file->size,
+ file->path);
+
+ WRITE_BYTE(RAW_FILE_MARKER);
+ size_t suffix_len = strlen(file->suffix);
+ WRITE_BYTE(suffix_len);
+ WRITE_BYTES(file->suffix, suffix_len);
+ WRITE_UINT64(file->size);
+
+ char *raw_file = get_raw_file_path(result_path_in_cache, entry_number);
+ struct stat old_stat;
+ bool old_existed = stat(raw_file, &old_stat) == 0;
+
+ success = copy_raw_file(file->path, raw_file, true);
+
+ struct stat new_stat;
+ bool new_exists = stat(raw_file, &new_stat) == 0;
+ free(raw_file);
+
+ size_t old_size = old_existed ? file_size(&old_stat) : 0;
+ size_t new_size = new_exists ? file_size(&new_stat) : 0;
+ stats_update_size(
+ stats_file,
+ new_size - old_size,
+ (new_exists ? 1 : 0) - (old_existed ? 1 : 0));
+
+error:
+ return success;
+}
+
+static bool
+should_hard_link_suffix(const char *suffix)
+{
+ // - Don't hard link stderr outputs since they:
+ // 1. Never are large.
+ // 2. Will end up in a temporary file anyway.
+ //
+ // - Don't hard link .d files since they:
+ // 1. Never are large.
+ // 2. Compress well.
+ // 3. Cause trouble for automake if hard-linked (see ccache issue 378).
+ return !str_eq(suffix, RESULT_STDERR_NAME) && !str_eq(suffix, ".d");
+}
+
static bool
write_result(
const struct result_files *list,
struct compressor *compressor,
struct compr_state *compr_state,
- XXH64_state_t *checksum)
+ XXH64_state_t *checksum,
+ const char *result_path_in_cache)
{
WRITE_BYTE(list->n_files);
for (uint32_t i = 0; i < list->n_files; i++) {
- cc_log("Writing %s (%llu bytes) to %s",
- list->files[i].suffix,
- (unsigned long long)list->files[i].size,
- list->files[i].path);
-
- WRITE_BYTE(EMBEDDED_FILE_MARKER);
- size_t suffix_len = strlen(list->files[i].suffix);
- WRITE_BYTE(suffix_len);
- WRITE_BYTES(list->files[i].suffix, suffix_len);
- WRITE_UINT64(list->files[i].size);
-
- FILE *f = fopen(list->files[i].path, "rb");
- if (!f) {
- cc_log("Failed to open %s for reading", list->files[i].path);
- goto error;
+ write_entry_fn write_entry;
+ if (conf->hard_link && should_hard_link_suffix(list->files[i].suffix)) {
+ write_entry = write_raw_file_entry;
+ } else {
+ write_entry = write_embedded_file_entry;
}
- char buf[READ_BUFFER_SIZE];
- size_t remain = list->files[i].size;
- while (remain > 0) {
- size_t n = MIN(remain, sizeof(buf));
- if (fread(buf, 1, n, f) != n) {
- goto error;
- }
- WRITE_BYTES(buf, n);
- remain -= n;
+
+ if (!write_entry(
+ compressor, compr_state, result_path_in_cache, i, &list->files[i])) {
+ goto error;
}
- fclose(f);
}
WRITE_UINT64(XXH64_digest(checksum));
char *errmsg;
bool success = read_result(path, list, NULL, &errmsg);
- if (errmsg) {
- cc_log("Error: %s", errmsg);
- free(errmsg);
- }
if (success) {
// Update modification timestamp to save files from LRU cleanup.
update_mtime(path);
+ } else if (errmsg) {
+ cc_log("Error: %s", errmsg);
+ free(errmsg);
+ } else {
+ cc_log("No such result file");
}
return success;
}
goto out;
}
- bool ok = write_result(list, compressor, compr_state, checksum)
+ bool ok = write_result(list, compressor, compr_state, checksum, path)
&& compressor->free(compr_state);
if (!ok) {
cc_log("Failed to write result file");
#include <tchar.h>
#endif
+extern const struct conf *conf;
+
// Destination for conf->log_file.
static FILE *logfile;
static bool
init_log(void)
{
- extern struct conf *conf;
-
if (debug_log_buffer || logfile || use_syslog) {
return true;
}
static void
warn_log_fail(void)
{
- extern struct conf *conf;
-
// Note: Can't call fatal() since that would lead to recursion.
fprintf(stderr, "ccache: error: Failed to write to %s: %s\n",
conf->log_file, strerror(errno));
x_exit(1);
}
+// Transform a name to a full path into the cache directory, creating needed
+// sublevels if needed. Caller frees.
+char *
+get_path_in_cache(const char *name, const char *suffix)
+{
+ char *path = x_strdup(conf->cache_dir);
+ for (unsigned i = 0; i < conf->cache_dir_levels; ++i) {
+ char *p = format("%s/%c", path, name[i]);
+ free(path);
+ path = p;
+ }
+
+ char *result =
+ format("%s/%s%s", path, name + conf->cache_dir_levels, suffix);
+ free(path);
+ return result;
+}
+
// Copy all data from fd_in to fd_out.
bool
copy_fd(int fd_in, int fd_out)
}
#endif
-// Copy a file from src to dest.
+// Copy a file from src to dest. If via_tmp_file is true, the file is copied to
+// a temporary file and then renamed to dest.
bool
-copy_file(const char *src, const char *dest)
+copy_file(const char *src, const char *dest, bool via_tmp_file)
{
bool result = false;
return false;
}
- int dest_fd = open(dest, O_WRONLY | O_CREAT | O_BINARY, 0666);
- if (dest_fd == -1) {
- close(dest_fd);
- return false;
+ int dest_fd;
+ char *tmp_file = NULL;
+ if (via_tmp_file) {
+ tmp_file = x_strdup(dest);
+ dest_fd = create_tmp_fd(&tmp_file);
+ } else {
+ dest_fd = open(dest, O_WRONLY | O_CREAT | O_BINARY, 0666);
+ if (dest_fd == -1) {
+ close(dest_fd);
+ close(src_fd);
+ return false;
+ }
}
if (copy_fd(src_fd, dest_fd)) {
close(dest_fd);
close(src_fd);
+
+ if (via_tmp_file) {
+ x_try_unlink(dest);
+ if (x_rename(tmp_file, dest) != 0) {
+ result = false;
+ }
+ free(tmp_file);
+ }
+
return result;
}
bool
move_file(const char *src, const char *dest)
{
- bool ok = copy_file(src, dest);
+ bool ok = copy_file(src, dest, false);
if (ok) {
x_unlink(src);
}
debug_prefix_map
split_dwarf
masquerading
+hardlink
direct
direct_gcc
depend
--- /dev/null
+SUITE_hardlink_PROBE() {
+ touch file1
+ if ! ln file1 file2 >/dev/null 2>&1; then
+ echo "file system doesn't support hardlinks"
+ fi
+}
+
+SUITE_hardlink() {
+ # -------------------------------------------------------------------------
+ TEST "CCACHE_HARDLINK"
+
+ generate_code 1 test1.c
+
+ $REAL_COMPILER -c -o reference_test1.o test1.c
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
+ expect_stat 'cache hit (preprocessed)' 0
+ expect_stat 'cache miss' 1
+ expect_stat 'files in cache' 2
+ expect_equal_object_files reference_test1.o test1.o
+
+ mv test1.o test1.o.saved
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
+ expect_stat 'cache hit (preprocessed)' 1
+ expect_stat 'cache miss' 1
+ expect_stat 'files in cache' 2
+ if [ ! test1.o -ef test1.o.saved ]; then
+ test_failed "Object files not hard linked"
+ fi
+
+ # -------------------------------------------------------------------------
+ TEST "Corrupted file size is detected"
+
+ generate_code 1 test1.c
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
+ expect_stat 'cache hit (preprocessed)' 0
+ expect_stat 'cache miss' 1
+ expect_stat 'files in cache' 2
+
+ mv test1.o test1.o.saved
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
+ expect_stat 'cache hit (preprocessed)' 1
+ expect_stat 'cache miss' 1
+ expect_stat 'files in cache' 2
+
+ # -------------------------------------------------------------------------
+ TEST "Overwrite assembler"
+
+ generate_code 1 test1.c
+ $REAL_COMPILER -S -o test1.s test1.c
+
+ $REAL_COMPILER -c -o reference_test1.o test1.s
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
+ expect_stat 'cache hit (preprocessed)' 0
+ expect_stat 'cache miss' 1
+ expect_stat 'files in cache' 2
+
+ generate_code 2 test1.c
+ $REAL_COMPILER -S -o test1.s test1.c
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
+ expect_stat 'cache hit (preprocessed)' 0
+ expect_stat 'cache miss' 2
+ expect_stat 'files in cache' 4
+
+ generate_code 1 test1.c
+ $REAL_COMPILER -S -o test1.s test1.c
+
+ CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
+ expect_stat 'cache hit (preprocessed)' 1
+ expect_stat 'cache miss' 2
+ expect_stat 'files in cache' 4
+ expect_equal_object_files reference_test1.o test1.o
+
+ # -------------------------------------------------------------------------
+ TEST "Automake depend move"
+
+ unset CCACHE_NODIRECT
+
+ generate_code 1 test1.c
+
+ CCACHE_HARDLINK=1 CCACHE_DEPEND=1 $CCACHE_COMPILE -c -MMD -MF test1.d.tmp test1.c
+ expect_stat 'cache hit (direct)' 0
+ mv test1.d.tmp test1.d || test_failed "first mv failed"
+
+ CCACHE_HARDLINK=1 CCACHE_DEPEND=1 $CCACHE_COMPILE -c -MMD -MF test1.d.tmp test1.c
+ expect_stat 'cache hit (direct)' 1
+ mv test1.d.tmp test1.d || test_failed "second mv failed"
+}
#include "framework.h"
#include "util.h"
-#define N_CONFIG_ITEMS 33
+#define N_CONFIG_ITEMS 34
static struct {
char *descr;
char *origin;
CHECK(conf->direct_mode);
CHECK(!conf->disable);
CHECK_STR_EQ("", conf->extra_files_to_hash);
+ CHECK(!conf->hard_link);
CHECK(conf->hash_dir);
CHECK_STR_EQ("", conf->ignore_headers_in_manifest);
CHECK(!conf->keep_comments_cpp);
"direct_mode = false\n"
"disable = true\n"
"extra_files_to_hash = a:b c:$USER\n"
+ "hard_link = true\n"
"hash_dir = false\n"
"ignore_headers_in_manifest = a:b/c\n"
"keep_comments_cpp = true\n"
CHECK(!conf->direct_mode);
CHECK(conf->disable);
CHECK_STR_EQ_FREE1(format("a:b c:%s", user), conf->extra_files_to_hash);
+ CHECK(conf->hard_link);
CHECK(!conf->hash_dir);
CHECK_STR_EQ("a:b/c", conf->ignore_headers_in_manifest);
CHECK(conf->keep_comments_cpp);
false,
true,
"efth",
+ true,
.hash_dir = false,
"ihim",
true,
CHECK_STR_EQ("direct_mode = false", received_conf_items[n++].descr);
CHECK_STR_EQ("disable = true", received_conf_items[n++].descr);
CHECK_STR_EQ("extra_files_to_hash = efth", received_conf_items[n++].descr);
+ CHECK_STR_EQ("hard_link = true", received_conf_items[n++].descr);
CHECK_STR_EQ("hash_dir = false", received_conf_items[n++].descr);
CHECK_STR_EQ("ignore_headers_in_manifest = ihim",
received_conf_items[n++].descr);