ports/devel/ccache/files/extra-patch-memcached
Bryan Drewery b408ef56b8 Add a patch for memcached to ccache along with a slave devel/ccache-memcached port.
This patch is not safe for WITH_CCACHE_BUILD support yet as that causes all
ports to depend on devel/ccache.  Enabling that patch would then cause the
new devel/libmemcached dependency to require devel/ccache which is a cyclic
dependency.  The autoconf dependency also causes issues.

Add a devel/ccache-memcached slave port that would allow a user to use
the ccache+memcached package manually with ports without WITH_CCACHE_BUILD.

This patch comes from https://github.com/ccache/ccache/pull/58 and has been
an ongoing effort over a few years to be merged into the mainline of ccache.
Documenation for it can be found in the MANUAL file at:

  /usr/local/share/doc/ccache/MANUAL.txt

Sponsored by:	Dell EMC Isilon
2017-02-17 23:23:47 +00:00

2396 lines
74 KiB
Text

https://github.com/ccache/ccache/pull/58
Retrieved on February 13th 2017.
Changes to .travis.yml removed since it is not in the release image.
diff --git a/MANUAL.txt b/MANUAL.txt
index ab01886..c78bb6e 100644
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -418,6 +418,20 @@ WRAPPERS>>.
The default value is 5G. Available suffixes: k, M, G, T (decimal) and Ki,
Mi, Gi, Ti (binary). The default suffix is "G".
+*memcached_conf* (*CCACHE_MEMCACHED_CONF*)::
+
+ The memcached_conf option sets the memcached(3) configuration to use for
+ storing and getting cache values, if any. Example configuration:
++
+-------------------------------------------------------------------------------
+CCACHE_MEMCACHED_CONF=--SERVER=localhost:11211
+-------------------------------------------------------------------------------
+
+*memcached_only* (*CCACHE_MEMCACHED_ONLY*)::
+
+ Only store files in memcached, don't store them in the local filesystems.
+ The manifests (for direct mode) and stats are still being stored locally.
+
*path* (*CCACHE_PATH*)::
If set, ccache will search directories in this list when looking for the
@@ -451,6 +465,11 @@ WRAPPERS>>.
from the cache using the direct mode, not the preprocessor mode. See
documentation for *read_only* regarding using a read-only ccache directory.
+*read_only_memcached* (*CCACHE_READONLY_MEMCACHED* or *CCACHE_NOREADONLY_MEMCACHED*), see <<_boolean_values,Boolean values>> above)::
+
+ If true, ccache will attempt to get previously cached values from memcached,
+ but will not try to store any new values in memcached.
+
*recache* (*CCACHE_RECACHE* or *CCACHE_NORECACHE*, see <<_boolean_values,Boolean values>> above)::
If true, ccache will not use any previously stored result. New results will
@@ -769,6 +788,29 @@ A tip is to set *temporary_dir* to a directory on the local host to avoid NFS
traffic for temporary files.
+Sharing a cache with memcached
+------------------------------
+
+When using the *memcached* (<http://memcached.org>) feature, the most recently
+used cache entries are also available from the configured memcached servers.
+
+The local cache directory will be searched first, but then it will still be
+possible to get cache hits (over the network) before having to run the
+compiler.
+
+Using a local *moxi* (memcached proxy) will enable multiple ccache invocations
+to share memcached connections and thus avoid some of the network overhead.
+
+It will also allow you to fine-tune connection timeouts and other settings. You
+can optionally replace your memcached servers with Couchbase servers.
+
+Example:
+
+-------------------------------------------------------------------------------
+moxi -z 11211=mc_server1:11211,mc_server2:11211
+-------------------------------------------------------------------------------
+
+
Using ccache with other compiler wrappers
-----------------------------------------
diff --git a/Makefile.in b/Makefile.in
index 5aee02d..08b3633 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -37,6 +37,7 @@ non_3pp_sources = \
lockfile.c \
manifest.c \
mdfour.c \
+ memccached.c \
stats.c \
unify.c \
util.c \
@@ -101,7 +102,7 @@ perf: ccache$(EXEEXT)
.PHONY: test
test: ccache$(EXEEXT) test/main$(EXEEXT)
test/main$(EXEEXT)
- CC='$(CC)' $(srcdir)/test.sh
+ CC='$(CC)' @ccache_memcached@$(srcdir)/test.sh
.PHONY: quicktest
quicktest: test/main$(EXEEXT)
diff --git a/ccache.c b/ccache.c
index 88e0ec5..12026c7 100644
--- a/ccache.c
+++ b/ccache.c
@@ -102,6 +102,9 @@ static char *output_dia = NULL;
// Split dwarf information (GCC 4.8 andup). Contains pathname if not NULL.
static char *output_dwo = NULL;
+// The cached key.
+static char *cached_key;
+
// Array for storing -arch options.
#define MAX_ARCH_ARGS 10
static size_t arch_args_size = 0;
@@ -123,6 +126,9 @@ static char *cached_stderr;
// (cachedir/a/b/cdef[...]-size.d).
static char *cached_dep;
+// The manifest key.
+static char *manifest_name;
+
// Full path to the file containing the coverage information
// (cachedir/a/b/cdef[...]-size.gcno).
static char *cached_cov;
@@ -239,6 +245,18 @@ static pid_t compiler_pid = 0;
// stored in the cache changes in a backwards-incompatible way.
static const char HASH_PREFIX[] = "3";
+static void from_fscache(enum fromcache_call_mode mode,
+ bool put_object_in_manifest);
+static void to_fscache(struct args *args);
+#ifdef HAVE_LIBMEMCACHED
+static void from_memcached(enum fromcache_call_mode mode,
+ bool put_object_in_manifest);
+static void to_memcached(struct args *args);
+#endif
+static void (*from_cache)(enum fromcache_call_mode mode,
+ bool put_object_in_manifest);
+static void (*to_cache)(struct args *args);
+
static void
add_prefix(struct args *args, char *prefix_command)
{
@@ -952,6 +970,28 @@ put_file_in_cache(const char *source, const char *dest)
stats_update_size(file_size(&st), 1);
}
+#ifdef HAVE_LIBMEMCACHED
+// Copy data to the cache.
+static void
+put_data_in_cache(void *data, size_t size, const char *dest)
+{
+ int ret;
+
+ assert(!conf->read_only);
+ assert(!conf->read_only_direct);
+
+ /* already compressed (in cache) */
+ ret = write_file(data, dest, size);
+ if (ret != 0) {
+ cc_log("Failed to write to %s: %s", dest, strerror(errno));
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ cc_log("Stored in cache: %zu bytes -> %s", size, dest);
+ stats_update_size(size, 1);
+}
+#endif
+
// Copy or link a file from the cache.
static void
get_file_from_cache(const char *source, const char *dest)
@@ -1006,6 +1046,11 @@ send_cached_stderr(void)
// Create or update the manifest file.
void update_manifest_file(void)
{
+#ifdef HAVE_LIBMEMCACHED
+ char *data;
+ size_t size;
+#endif
+
if (!conf->direct_mode
|| !included_files
|| conf->read_only
@@ -1023,6 +1068,14 @@ void update_manifest_file(void)
update_mtime(manifest_path);
if (x_stat(manifest_path, &st) == 0) {
stats_update_size(file_size(&st) - old_size, old_size == 0 ? 1 : 0);
+#if HAVE_LIBMEMCACHED
+ if (strlen(conf->memcached_conf) > 0 && !conf->read_only_memcached &&
+ read_file(manifest_path, st.st_size, &data, &size)) {
+ cc_log("Storing %s in memcached", manifest_name);
+ memccached_raw_set(manifest_name, data, size);
+ free(data);
+ }
+#endif
}
} else {
cc_log("Failed to add object file hash to %s", manifest_path);
@@ -1031,8 +1084,12 @@ void update_manifest_file(void)
// Run the real compiler and put the result in cache.
static void
-to_cache(struct args *args)
+to_fscache(struct args *args)
{
+#ifdef HAVE_LIBMEMCACHED
+ char *data_obj, *data_stderr, *data_dia, *data_dep;
+ size_t size_obj, size_stderr, size_dia, size_dep;
+#endif
char *tmp_stdout = format("%s.tmp.stdout", cached_obj);
int tmp_stdout_fd = create_tmp_fd(&tmp_stdout);
char *tmp_stderr = format("%s.tmp.stderr", cached_obj);
@@ -1288,6 +1345,40 @@ to_cache(struct args *args)
}
}
+#ifdef HAVE_LIBMEMCACHED
+ if (strlen(conf->memcached_conf) > 0 && !conf->read_only_memcached &&
+ !using_split_dwarf && /* no support for the dwo files just yet */
+ !generating_coverage) { /* coverage refers to local paths anyway */
+ cc_log("Storing %s in memcached", cached_key);
+ if (!read_file(cached_obj, 0, &data_obj, &size_obj)) {
+ data_obj = NULL;
+ size_obj = 0;
+ }
+ if (!read_file(cached_stderr, 0, &data_stderr, &size_stderr)) {
+ data_stderr = NULL;
+ size_stderr = 0;
+ }
+ if (!read_file(cached_dia, 0, &data_dia, &size_dia)) {
+ data_dia = NULL;
+ size_dia = 0;
+ }
+ if (!read_file(cached_dep, 0, &data_dep, &size_dep)) {
+ data_dep = NULL;
+ size_dep = 0;
+ }
+
+ if (data_obj) {
+ memccached_set(cached_key,
+ data_obj, data_stderr, data_dia, data_dep,
+ size_obj, size_stderr, size_dia, size_dep);
+ }
+
+ free(data_obj);
+ free(data_stderr);
+ free(data_dia);
+ free(data_dep);
+ }
+#endif
// Everything OK.
send_cached_stderr();
update_manifest_file();
@@ -1298,6 +1389,226 @@ to_cache(struct args *args)
free(tmp_dwo);
}
+#ifdef HAVE_LIBMEMCACHED
+// Run the real compiler and put the result in cache.
+static void
+to_memcached(struct args *args)
+{
+ const char *tmp_dir = temp_dir();
+ char *tmp_stdout, *tmp_stderr;
+ char *stderr_d, *obj_d, *dia_d = NULL, *dep_d = NULL;
+ size_t stderr_l = 0, obj_l = 0, dia_l = 0, dep_l = 0;
+ struct stat st;
+ int status, tmp_stdout_fd, tmp_stderr_fd;
+
+ tmp_stdout = format("%s/%s.tmp.stdout.%s", tmp_dir, cached_obj, tmp_string());
+ tmp_stdout_fd = create_tmp_fd(&tmp_stdout);
+ tmp_stderr = format("%s/%s.tmp.stderr.%s", tmp_dir, cached_obj, tmp_string());
+ tmp_stderr_fd = create_tmp_fd(&tmp_stderr);
+
+ if (generating_coverage) {
+ cc_log("No memcached support for coverage yet");
+ failed();
+ }
+ if (using_split_dwarf) {
+ cc_log("No memcached support for split dwarf yet");
+ failed();
+ }
+
+ if (create_parent_dirs(tmp_stdout) != 0) {
+ fatal("Failed to create parent directory for %s: %s",
+ tmp_stdout, strerror(errno));
+ }
+
+ args_add(args, "-o");
+ args_add(args, output_obj);
+
+ if (output_dia) {
+ args_add(args, "--serialize-diagnostics");
+ args_add(args, output_dia);
+ }
+
+ /* Turn off DEPENDENCIES_OUTPUT when running cc1, because
+ * otherwise it will emit a line like
+ *
+ * tmp.stdout.vexed.732.o: /home/mbp/.ccache/tmp.stdout.vexed.732.i
+ */
+ x_unsetenv("DEPENDENCIES_OUTPUT");
+
+ if (conf->run_second_cpp) {
+ args_add(args, input_file);
+ } else {
+ args_add(args, i_tmpfile);
+ }
+
+ cc_log("Running real compiler");
+ status = execute(args->argv, tmp_stdout_fd, tmp_stderr_fd, &compiler_pid);
+ args_pop(args, 3);
+
+ if (x_stat(tmp_stdout, &st) != 0) {
+ /* The stdout file was removed - cleanup in progress? Better bail out. */
+ stats_update(STATS_MISSING);
+ tmp_unlink(tmp_stdout);
+ tmp_unlink(tmp_stderr);
+ failed();
+ }
+ if (st.st_size != 0) {
+ cc_log("Compiler produced stdout");
+ stats_update(STATS_STDOUT);
+ tmp_unlink(tmp_stdout);
+ tmp_unlink(tmp_stderr);
+ failed();
+ }
+ tmp_unlink(tmp_stdout);
+
+ /*
+ * Merge stderr from the preprocessor (if any) and stderr from the real
+ * compiler into tmp_stderr.
+ */
+ if (cpp_stderr) {
+ int fd_cpp_stderr;
+ int fd_real_stderr;
+ int fd_result;
+ char *tmp_stderr2;
+
+ tmp_stderr2 = format("%s.2", tmp_stderr);
+ if (x_rename(tmp_stderr, tmp_stderr2)) {
+ cc_log("Failed to rename %s to %s: %s", tmp_stderr, tmp_stderr2,
+ strerror(errno));
+ failed();
+ }
+ fd_cpp_stderr = open(cpp_stderr, O_RDONLY | O_BINARY);
+ if (fd_cpp_stderr == -1) {
+ cc_log("Failed opening %s: %s", cpp_stderr, strerror(errno));
+ failed();
+ }
+ fd_real_stderr = open(tmp_stderr2, O_RDONLY | O_BINARY);
+ if (fd_real_stderr == -1) {
+ cc_log("Failed opening %s: %s", tmp_stderr2, strerror(errno));
+ failed();
+ }
+ fd_result = open(tmp_stderr, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+ if (fd_result == -1) {
+ cc_log("Failed opening %s: %s", tmp_stderr, strerror(errno));
+ failed();
+ }
+ copy_fd(fd_cpp_stderr, fd_result);
+ copy_fd(fd_real_stderr, fd_result);
+ close(fd_cpp_stderr);
+ close(fd_real_stderr);
+ close(fd_result);
+ tmp_unlink(tmp_stderr2);
+ free(tmp_stderr2);
+ }
+
+ if (status != 0) {
+ int fd;
+ cc_log("Compiler gave exit status %d", status);
+ stats_update(STATS_STATUS);
+
+ fd = open(tmp_stderr, O_RDONLY | O_BINARY);
+ if (fd != -1) {
+ /* We can output stderr immediately instead of rerunning the compiler. */
+ copy_fd(fd, 2);
+ close(fd);
+ tmp_unlink(tmp_stderr);
+
+ x_exit(status);
+ }
+
+ tmp_unlink(tmp_stderr);
+ failed();
+ }
+
+ if (stat(output_obj, &st) != 0) {
+ cc_log("Compiler didn't produce an object file");
+ stats_update(STATS_NOOUTPUT);
+ failed();
+ }
+ if (st.st_size == 0) {
+ cc_log("Compiler produced an empty object file");
+ stats_update(STATS_EMPTYOUTPUT);
+ failed();
+ }
+
+ if (x_stat(tmp_stderr, &st) != 0) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ /* cache stderr */
+ if (!read_file(tmp_stderr, 0, &stderr_d, &stderr_l)) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ tmp_unlink(tmp_stderr);
+
+ if (output_dia) {
+ if (x_stat(output_dia, &st) != 0) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ /* cache dia */
+ if (!read_file(output_dia, 0, &dia_d, &dia_l)) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ }
+
+ /* cache output */
+ if (!read_file(output_obj, 0, &obj_d, &obj_l)) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+
+ if (generating_dependencies) {
+ if (!read_file(output_dep, 0, &dep_d, &dep_l)) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ }
+
+ if (memccached_set(cached_key, obj_d, stderr_d, dia_d, dep_d,
+ obj_l, stderr_l, dia_l, dep_l) < 0) {
+ stats_update(STATS_ERROR);
+ failed();
+ }
+
+ cc_log("Storing %s in memcached", cached_key);
+
+ stats_update(STATS_TOCACHE);
+
+ /* Make sure we have a CACHEDIR.TAG in the cache part of cache_dir. This can
+ * be done almost anywhere, but we might as well do it near the end as we
+ * save the stat call if we exit early.
+ */
+ {
+ char *first_level_dir = dirname(stats_file);
+ if (create_cachedirtag(first_level_dir) != 0) {
+ cc_log("Failed to create %s/CACHEDIR.TAG (%s)\n",
+ first_level_dir, strerror(errno));
+ stats_update(STATS_ERROR);
+ failed();
+ }
+ free(first_level_dir);
+
+ /* Remove any CACHEDIR.TAG on the cache_dir level where it was located in
+ * previous ccache versions. */
+ if (getpid() % 1000 == 0) {
+ char *path = format("%s/CACHEDIR.TAG", conf->cache_dir);
+ x_unlink(path);
+ free(path);
+ }
+ }
+
+ /* Everything OK. */
+ send_cached_stderr();
+ update_manifest_file();
+
+ free(tmp_stderr);
+ free(tmp_stdout);
+}
+#endif
+
// Find the object file name by running the compiler in preprocessor mode.
// Returns the hash as a heap-allocated hex string.
static struct file_hash *
@@ -1408,6 +1719,7 @@ static void
update_cached_result_globals(struct file_hash *hash)
{
char *object_name = format_hash_as_string(hash->hash, hash->size);
+ cached_key = strdup(object_name);
cached_obj_hash = hash;
cached_obj = get_path_in_cache(object_name, ".o");
cached_stderr = get_path_in_cache(object_name, ".stderr");
@@ -1599,6 +1911,11 @@ calculate_common_hash(struct args *args, struct mdfour *hash)
static struct file_hash *
calculate_object_hash(struct args *args, struct mdfour *hash, int direct_mode)
{
+#if HAVE_LIBMEMCACHED
+ char *data;
+ size_t size;
+#endif
+
if (direct_mode) {
hash_delimiter(hash, "manifest version");
hash_int(hash, MANIFEST_VERSION);
@@ -1791,7 +2108,27 @@ calculate_object_hash(struct args *args, struct mdfour *hash, int direct_mode)
}
char *manifest_name = hash_result(hash);
manifest_path = get_path_in_cache(manifest_name, ".manifest");
- free(manifest_name);
+ /* Check if the manifest file is there. */
+ struct stat st;
+ if (stat(manifest_path, &st) != 0) {
+#if HAVE_LIBMEMCACHED
+ void *cache = NULL;
+#endif
+ cc_log("Manifest file %s not in cache", manifest_path);
+#if HAVE_LIBMEMCACHED
+ if (strlen(conf->memcached_conf) > 0) {
+ cc_log("Getting %s from memcached", manifest_name);
+ cache = memccached_raw_get(manifest_name, &data, &size);
+ }
+ if (cache) {
+ cc_log("Added object file hash to %s", manifest_path);
+ write_file(data, manifest_path, size);
+ stats_update_size(size, 1);
+ free(cache);
+ } else
+#endif
+ return NULL;
+ }
cc_log("Looking for object file hash in %s", manifest_path);
object_hash = manifest_get(conf, manifest_path);
if (object_hash) {
@@ -1828,8 +2165,13 @@ calculate_object_hash(struct args *args, struct mdfour *hash, int direct_mode)
// Try to return the compile result from cache. If we can return from cache
// then this function exits with the correct status code, otherwise it returns.
static void
-from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest)
+from_fscache(enum fromcache_call_mode mode, bool put_object_in_manifest)
{
+#if HAVE_LIBMEMCACHED
+ char *data_obj, *data_stderr, *data_dia, *data_dep;
+ size_t size_obj, size_stderr, size_dia, size_dep;
+#endif
+
// The user might be disabling cache hits.
if (conf->recache) {
return;
@@ -1837,7 +2179,33 @@ from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest)
struct stat st;
if (stat(cached_obj, &st) != 0) {
+#if HAVE_LIBMEMCACHED
+ void *cache = NULL;
+#endif
cc_log("Object file %s not in cache", cached_obj);
+#if HAVE_LIBMEMCACHED
+ if (strlen(conf->memcached_conf) > 0 &&
+ !using_split_dwarf &&
+ !generating_coverage) {
+ cc_log("Getting %s from memcached", cached_key);
+ cache = memccached_get(cached_key,
+ &data_obj, &data_stderr, &data_dia, &data_dep,
+ &size_obj, &size_stderr, &size_dia, &size_dep);
+ }
+ if (cache) {
+ put_data_in_cache(data_obj, size_obj, cached_obj);
+ if (size_stderr > 0) {
+ put_data_in_cache(data_stderr, size_stderr, cached_stderr);
+ }
+ if (size_dia > 0) {
+ put_data_in_cache(data_dia, size_dia, cached_dia);
+ }
+ if (size_dep > 0) {
+ put_data_in_cache(data_dep, size_dep, cached_dep);
+ }
+ memccached_free(cache);
+ } else
+#endif
return;
}
@@ -1947,6 +2315,97 @@ from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest)
x_exit(0);
}
+#ifdef HAVE_LIBMEMCACHED
+/*
+ * Try to return the compile result from cache. If we can return from cache
+ * then this function exits with the correct status code, otherwise it returns.
+ */
+static void
+from_memcached(enum fromcache_call_mode mode, bool put_object_in_manifest)
+{
+ bool produce_dep_file = false;
+ int ret;
+ void *cache;
+ char *data_obj, *data_stderr, *data_dia, *data_dep;
+ size_t size_obj, size_stderr, size_dia, size_dep;
+
+ /* the user might be disabling cache hits */
+ if (conf->recache || using_split_dwarf || generating_coverage) {
+ return;
+ }
+
+ cc_log("Getting %s from memcached", cached_key);
+ cache = memccached_get(cached_key,
+ &data_obj, &data_stderr, &data_dia, &data_dep,
+ &size_obj, &size_stderr, &size_dia, &size_dep);
+ if (!cache) {
+ return;
+ }
+
+ /*
+ * (If mode != FROMCACHE_DIRECT_MODE, the dependency file is created by
+ * gcc.)
+ */
+ produce_dep_file = generating_dependencies && mode == FROMCACHE_DIRECT_MODE;
+
+ if (!str_eq(output_obj, "/dev/null")) {
+ x_unlink(output_obj);
+ ret = write_file(data_obj, output_obj, size_obj);
+ } else {
+ ret = 0;
+ }
+ if (ret < 0) {
+ cc_log("Problem creating %s from %s", output_obj, cached_key);
+ failed();
+ }
+
+ if (produce_dep_file) {
+ x_unlink(output_dep);
+ ret = write_file(data_dep, output_dep, size_dep);
+ if (ret < 0) {
+ cc_log("Problem creating %s from %s", output_dep, cached_key);
+ failed();
+ }
+ }
+ if (output_dia) {
+ x_unlink(output_dia);
+ ret = write_file(data_dia, output_dia, size_dia);
+ if (ret < 0) {
+ cc_log("Problem creating %s from %s", output_dia, cached_key);
+ failed();
+ }
+ }
+
+ if (generating_dependencies && mode == FROMCACHE_CPP_MODE) {
+ /* Store the dependency file in the cache. */
+ cc_log("Does not support non direct mode");
+ }
+
+ /* Send the stderr, if any. */
+ safe_write(2, data_stderr, size_stderr);
+
+ if (put_object_in_manifest) {
+ update_manifest_file();
+ }
+
+ /* log the cache hit */
+ switch (mode) {
+ case FROMCACHE_DIRECT_MODE:
+ cc_log("Succeeded getting cached result");
+ stats_update(STATS_CACHEHIT_DIR);
+ break;
+
+ case FROMCACHE_CPP_MODE:
+ cc_log("Succeeded getting cached result");
+ stats_update(STATS_CACHEHIT_CPP);
+ break;
+ }
+
+ /* and exit with the right status code */
+ x_exit(0);
+}
+#endif
+
// Find the real compiler. We just search the PATH to find an executable of the
// same name that isn't a link to ourselves.
static void
@@ -3059,6 +3518,19 @@ initialize(void)
create_initial_config_file(conf, primary_config_path);
}
+ from_cache = from_fscache;
+ to_cache = to_fscache;
+
+#ifdef HAVE_LIBMEMCACHED
+ if (strlen(conf->memcached_conf) > 0) {
+ memccached_init(conf->memcached_conf);
+ }
+
+ if (conf->memcached_only) {
+ from_cache = from_memcached;
+ to_cache = to_memcached;
+ }
+#endif
exitfn_init();
exitfn_add_nullary(stats_flush);
exitfn_add_nullary(clean_up_pending_tmp_files);
@@ -3089,6 +3561,7 @@ cc_reset(void)
free(output_dep); output_dep = NULL;
free(output_cov); output_cov = NULL;
free(output_dia); output_dia = NULL;
+ free(cached_key); cached_key = NULL;
free(cached_obj_hash); cached_obj_hash = NULL;
free(cached_obj); cached_obj = NULL;
free(cached_dwo); cached_dwo = NULL;
@@ -3096,6 +3569,7 @@ cc_reset(void)
free(cached_dep); cached_dep = NULL;
free(cached_cov); cached_cov = NULL;
free(cached_dia); cached_dia = NULL;
+ free(manifest_name); manifest_name = NULL;
free(manifest_path); manifest_path = NULL;
time_of_compilation = 0;
for (size_t i = 0; i < ignore_headers_len; i++) {
@@ -3119,6 +3593,10 @@ cc_reset(void)
free(stats_file); stats_file = NULL;
output_is_precompiled_header = false;
+#ifdef HAVE_LIBMEMCACHED
+ memccached_release();
+#endif
+
conf = conf_create();
using_split_dwarf = false;
}
@@ -3285,8 +3763,14 @@ ccache(int argc, char *argv[])
put_object_in_manifest = true;
}
- // If we can return from cache at this point then do.
- from_cache(FROMCACHE_CPP_MODE, put_object_in_manifest);
+ /* don't hit memcached twice */
+ if (conf->memcached_only && object_hash_from_manifest
+ && file_hashes_equal(object_hash_from_manifest, object_hash)) {
+ cc_log("Already searched for %s", cached_key);
+ } else {
+ // If we can return from cache at this point then do.
+ from_cache(FROMCACHE_CPP_MODE, put_object_in_manifest);
+ }
if (conf->read_only) {
cc_log("Read-only mode; running real compiler");
diff --git a/ccache.h b/ccache.h
index 7b29bb8..1c1e38d 100644
--- a/ccache.h
+++ b/ccache.h
@@ -126,6 +126,8 @@ void cc_log_argv(const char *prefix, char **argv);
void fatal(const char *format, ...) ATTR_FORMAT(printf, 1, 2) ATTR_NORETURN;
void copy_fd(int fd_in, int fd_out);
+int safe_write(int fd_out, const char *data, size_t length);
+int write_file(const char *data, const char *dest, size_t length);
int copy_file(const char *src, const char *dest, int compress_level);
int move_file(const char *src, const char *dest, int compress_level);
int move_uncompressed_file(const char *src, const char *dest,
@@ -185,6 +187,23 @@ char *read_text_file(const char *path, size_t size_hint);
char *subst_env_in_string(const char *str, char **errmsg);
// ----------------------------------------------------------------------------
+// memccached.c
+
+int memccached_init(char *conf);
+int memccached_raw_set(const char *key, const char* data, size_t len);
+int memccached_set(
+ const char *key,
+ const char *out, const char *err, const char *dia, const char *dep,
+ size_t out_len, size_t err_len, size_t dia_len, size_t dep_len);
+void *memccached_raw_get(const char *key, char **data, size_t *len);
+void* memccached_get(
+ const char *key,
+ char **out, char **err, char **dia, char **dep,
+ size_t *out_len, size_t *err_len, size_t *dia_len, size_t *dep_len);
+void memccached_free(void *blob);
+int memccached_release(void);
+
+// ----------------------------------------------------------------------------
// stats.c
void stats_update(enum stats stat);
diff --git a/conf.c b/conf.c
index cfa2874..bf4e365 100644
--- a/conf.c
+++ b/conf.c
@@ -329,11 +329,14 @@ conf_create(void)
conf->log_file = x_strdup("");
conf->max_files = 0;
conf->max_size = (uint64_t)5 * 1000 * 1000 * 1000;
+ conf->memcached_conf = x_strdup("");
+ conf->memcached_only = false;
conf->path = x_strdup("");
conf->prefix_command = x_strdup("");
conf->prefix_command_cpp = x_strdup("");
conf->read_only = false;
conf->read_only_direct = false;
+ conf->read_only_memcached = false;
conf->recache = false;
conf->run_second_cpp = true;
conf->sloppiness = 0;
@@ -362,6 +365,7 @@ conf_free(struct conf *conf)
free(conf->extra_files_to_hash);
free(conf->ignore_headers_in_manifest);
free(conf->log_file);
+ free(conf->memcached_conf);
free(conf->path);
free(conf->prefix_command);
free(conf->prefix_command_cpp);
@@ -594,6 +598,12 @@ conf_print_items(struct conf *conf,
printer(s, conf->item_origins[find_conf("max_size")->number], context);
free(s2);
+ reformat(&s, "memcached_conf = %s", conf->memcached_conf);
+ printer(s, conf->item_origins[find_conf("memcached_conf")->number], context);
+
+ reformat(&s, "memcached_only = %s", bool_to_string(conf->memcached_only));
+ printer(s, conf->item_origins[find_conf("memcached_only")->number], context);
+
reformat(&s, "path = %s", conf->path);
printer(s, conf->item_origins[find_conf("path")->number], context);
@@ -611,6 +621,11 @@ conf_print_items(struct conf *conf,
printer(s, conf->item_origins[find_conf("read_only_direct")->number],
context);
+ reformat(&s, "read_only_memcached = %s",
+ bool_to_string(conf->read_only_memcached));
+ printer(s, conf->item_origins[find_conf("read_only_memcached")->number],
+ context);
+
reformat(&s, "recache = %s", bool_to_string(conf->recache));
printer(s, conf->item_origins[find_conf("recache")->number], context);
diff --git a/conf.h b/conf.h
index 232dcfd..1e22016 100644
--- a/conf.h
+++ b/conf.h
@@ -23,11 +23,14 @@ struct conf {
char *log_file;
unsigned max_files;
uint64_t max_size;
+ char *memcached_conf;
+ bool memcached_only;
char *path;
char *prefix_command;
char *prefix_command_cpp;
bool read_only;
bool read_only_direct;
+ bool read_only_memcached;
bool recache;
bool run_second_cpp;
unsigned sloppiness;
diff --git a/configure.ac b/configure.ac
index a35fac0..7ef33e1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,6 +16,7 @@ case $host in
;;
esac
+AC_SUBST(ccache_memcached)
AC_SUBST(extra_libs)
AC_SUBST(include_dev_mk)
AC_SUBST(test_suites)
@@ -84,6 +85,31 @@ HW_FUNC_ASPRINTF
dnl Check if -lm is needed.
AC_SEARCH_LIBS(cos, m)
+AC_ARG_ENABLE(static,
+ [AS_HELP_STRING([--enable-static],
+ [enable static link])])
+
+if test x${enable_static} != x; then
+ extra_ldflags="-static"
+fi
+
+AC_ARG_ENABLE(memcached,
+ [AS_HELP_STRING([--enable-memcached],
+ [enable memcached as a cache backend])])
+
+dnl enable-memcached: Check if -lmemcached is needed.
+if test x${enable_memcached} != x; then
+ if test x${enable_static} != x; then
+ AC_CHECK_LIB(stdc++, __gxx_personality_v0,[])
+ fi
+ AC_CHECK_LIB(pthread, pthread_once)
+ AC_CHECK_LIB(memcached, memcached,[],[
+ echo ' WARNING: recent version libmemcached not found'
+ echo ' please install libmemcached > 1.0 with development files'
+ exit 1
+ ])
+ ccache_memcached='CCACHE_MEMCACHED=1 '
+fi
dnl Check for zlib
AC_ARG_WITH(bundled-zlib,
diff --git a/confitems.gperf b/confitems.gperf
index 531bc92..fd43765 100644
--- a/confitems.gperf
+++ b/confitems.gperf
@@ -26,15 +26,18 @@ limit_multiple, 15, ITEM(limit_multiple, float)
log_file, 16, ITEM(log_file, env_string)
max_files, 17, ITEM(max_files, unsigned)
max_size, 18, ITEM(max_size, size)
-path, 19, ITEM(path, env_string)
-prefix_command, 20, ITEM(prefix_command, env_string)
-prefix_command_cpp, 21, ITEM(prefix_command_cpp, env_string)
-read_only, 22, ITEM(read_only, bool)
-read_only_direct, 23, ITEM(read_only_direct, bool)
-recache, 24, ITEM(recache, bool)
-run_second_cpp, 25, ITEM(run_second_cpp, bool)
-sloppiness, 26, ITEM(sloppiness, sloppiness)
-stats, 27, ITEM(stats, bool)
-temporary_dir, 28, ITEM(temporary_dir, env_string)
-umask, 29, ITEM(umask, umask)
-unify, 30, ITEM(unify, bool)
+memcached_conf, 19, ITEM(memcached_conf, string)
+memcached_only, 20, ITEM(memcached_only, bool)
+path, 21, ITEM(path, env_string)
+prefix_command, 22, ITEM(prefix_command, env_string)
+prefix_command_cpp, 23, ITEM(prefix_command_cpp, env_string)
+read_only, 24, ITEM(read_only, bool)
+read_only_direct, 25, ITEM(read_only_direct, bool)
+read_only_memcached, 26, ITEM(read_only_memcached, bool)
+recache, 27, ITEM(recache, bool)
+run_second_cpp, 28, ITEM(run_second_cpp, bool)
+sloppiness, 29, ITEM(sloppiness, sloppiness)
+stats, 30, ITEM(stats, bool)
+temporary_dir, 31, ITEM(temporary_dir, env_string)
+umask, 32, ITEM(umask, umask)
+unify, 33, ITEM(unify, bool)
diff --git a/confitems_lookup.c b/confitems_lookup.c
index 7482557..b324dad 100644
--- a/confitems_lookup.c
+++ b/confitems_lookup.c
@@ -1,6 +1,6 @@
/* ANSI-C code produced by gperf version 3.0.4 */
/* Command-line: gperf confitems.gperf */
-/* Computed positions: -k'1-2' */
+/* Computed positions: -k'1,$' */
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
@@ -31,7 +31,7 @@
#line 8 "confitems.gperf"
struct conf_item;
-/* maximum key range = 46, duplicates = 0 */
+/* maximum key range = 65, duplicates = 0 */
#ifdef __GNUC__
__inline
@@ -45,34 +45,34 @@ confitems_hash (register const char *str, register unsigned int len)
{
static const unsigned char asso_values[] =
{
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 0, 13, 0,
- 5, 10, 50, 0, 30, 20, 50, 0, 10, 20,
- 5, 0, 0, 50, 5, 0, 10, 15, 50, 50,
- 20, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
- 50, 50, 50, 50, 50, 50
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 5, 20,
+ 5, 0, 30, 70, 30, 10, 70, 20, 25, 0,
+ 10, 70, 0, 70, 0, 0, 10, 0, 70, 70,
+ 70, 55, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70
};
- return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
+ return len + asso_values[(unsigned char)str[len - 1]] + asso_values[(unsigned char)str[0]];
}
static
@@ -87,91 +87,108 @@ confitems_get (register const char *str, register unsigned int len)
{
enum
{
- TOTAL_KEYWORDS = 31,
+ TOTAL_KEYWORDS = 34,
MIN_WORD_LENGTH = 4,
MAX_WORD_LENGTH = 26,
- MIN_HASH_VALUE = 4,
- MAX_HASH_VALUE = 49
+ MIN_HASH_VALUE = 5,
+ MAX_HASH_VALUE = 69
};
static const struct conf_item wordlist[] =
{
{"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
{"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
-#line 29 "confitems.gperf"
- {"path", 19, ITEM(path, env_string)},
- {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
{"",0,NULL,0,NULL},
-#line 13 "confitems.gperf"
- {"compiler", 3, ITEM(compiler, string)},
-#line 11 "confitems.gperf"
- {"cache_dir", 1, ITEM(cache_dir, env_string)},
- {"",0,NULL,0,NULL},
-#line 15 "confitems.gperf"
- {"compression", 5, ITEM(compression, bool)},
- {"",0,NULL,0,NULL},
-#line 17 "confitems.gperf"
- {"cpp_extension", 7, ITEM(cpp_extension, string)},
-#line 14 "confitems.gperf"
- {"compiler_check", 4, ITEM(compiler_check, string)},
-#line 37 "confitems.gperf"
- {"stats", 27, ITEM(stats, bool)},
-#line 12 "confitems.gperf"
- {"cache_dir_levels", 2, ITEM_V(cache_dir_levels, unsigned, dir_levels)},
-#line 16 "confitems.gperf"
- {"compression_level", 6, ITEM(compression_level, unsigned)},
-#line 26 "confitems.gperf"
- {"log_file", 16, ITEM(log_file, env_string)},
-#line 30 "confitems.gperf"
- {"prefix_command", 20, ITEM(prefix_command, env_string)},
-#line 36 "confitems.gperf"
- {"sloppiness", 26, ITEM(sloppiness, sloppiness)},
-#line 10 "confitems.gperf"
- {"base_dir", 0, ITEM_V(base_dir, env_string, absolute_path)},
-#line 34 "confitems.gperf"
- {"recache", 24, ITEM(recache, bool)},
-#line 31 "confitems.gperf"
- {"prefix_command_cpp", 21, ITEM(prefix_command_cpp, env_string)},
-#line 32 "confitems.gperf"
- {"read_only", 22, ITEM(read_only, bool)},
#line 40 "confitems.gperf"
- {"unify", 30, ITEM(unify, bool)},
+ {"stats", 30, ITEM(stats, bool)},
{"",0,NULL,0,NULL},
-#line 24 "confitems.gperf"
- {"keep_comments_cpp", 14, ITEM(keep_comments_cpp, bool)},
+#line 37 "confitems.gperf"
+ {"recache", 27, ITEM(recache, bool)},
#line 28 "confitems.gperf"
{"max_size", 18, ITEM(max_size, size)},
#line 27 "confitems.gperf"
{"max_files", 17, ITEM(max_files, unsigned)},
+#line 39 "confitems.gperf"
+ {"sloppiness", 29, ITEM(sloppiness, sloppiness)},
{"",0,NULL,0,NULL},
-#line 33 "confitems.gperf"
- {"read_only_direct", 23, ITEM(read_only_direct, bool)},
#line 19 "confitems.gperf"
{"disable", 9, ITEM(disable, bool)},
+#line 10 "confitems.gperf"
+ {"base_dir", 0, ITEM_V(base_dir, env_string, absolute_path)},
#line 38 "confitems.gperf"
- {"temporary_dir", 28, ITEM(temporary_dir, env_string)},
-#line 35 "confitems.gperf"
- {"run_second_cpp", 25, ITEM(run_second_cpp, bool)},
+ {"run_second_cpp", 28, ITEM(run_second_cpp, bool)},
{"",0,NULL,0,NULL},
#line 18 "confitems.gperf"
{"direct_mode", 8, ITEM(direct_mode, bool)},
{"",0,NULL,0,NULL},
-#line 22 "confitems.gperf"
- {"hash_dir", 12, ITEM(hash_dir, bool)},
-#line 21 "confitems.gperf"
- {"hard_link", 11, ITEM(hard_link, bool)},
-#line 39 "confitems.gperf"
- {"umask", 29, ITEM(umask, umask)},
+#line 33 "confitems.gperf"
+ {"prefix_command_cpp", 23, ITEM(prefix_command_cpp, env_string)},
+#line 32 "confitems.gperf"
+ {"prefix_command", 22, ITEM(prefix_command, env_string)},
{"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
{"",0,NULL,0,NULL},
+#line 41 "confitems.gperf"
+ {"temporary_dir", 31, ITEM(temporary_dir, env_string)},
+#line 36 "confitems.gperf"
+ {"read_only_memcached", 26, ITEM(read_only_memcached, bool)},
+#line 42 "confitems.gperf"
+ {"umask", 32, ITEM(umask, umask)},
+#line 35 "confitems.gperf"
+ {"read_only_direct", 25, ITEM(read_only_direct, bool)},
+ {"",0,NULL,0,NULL},
+#line 13 "confitems.gperf"
+ {"compiler", 3, ITEM(compiler, string)},
+#line 11 "confitems.gperf"
+ {"cache_dir", 1, ITEM(cache_dir, env_string)},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+ {"",0,NULL,0,NULL},
+#line 26 "confitems.gperf"
+ {"log_file", 16, ITEM(log_file, env_string)},
+#line 31 "confitems.gperf"
+ {"path", 21, ITEM(path, env_string)},
+ {"",0,NULL,0,NULL},
+#line 12 "confitems.gperf"
+ {"cache_dir_levels", 2, ITEM_V(cache_dir_levels, unsigned, dir_levels)},
+#line 24 "confitems.gperf"
+ {"keep_comments_cpp", 14, ITEM(keep_comments_cpp, bool)},
+#line 22 "confitems.gperf"
+ {"hash_dir", 12, ITEM(hash_dir, bool)},
#line 25 "confitems.gperf"
{"limit_multiple", 15, ITEM(limit_multiple, float)},
{"",0,NULL,0,NULL},
+#line 15 "confitems.gperf"
+ {"compression", 5, ITEM(compression, bool)},
+ {"",0,NULL,0,NULL},
+#line 17 "confitems.gperf"
+ {"cpp_extension", 7, ITEM(cpp_extension, string)},
+#line 29 "confitems.gperf"
+ {"memcached_conf", 19, ITEM(memcached_conf, string)},
+ {"",0,NULL,0,NULL},
#line 23 "confitems.gperf"
{"ignore_headers_in_manifest", 13, ITEM(ignore_headers_in_manifest, env_string)},
{"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
#line 20 "confitems.gperf"
- {"extra_files_to_hash", 10, ITEM(extra_files_to_hash, env_string)}
+ {"extra_files_to_hash", 10, ITEM(extra_files_to_hash, env_string)},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+#line 14 "confitems.gperf"
+ {"compiler_check", 4, ITEM(compiler_check, string)},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+#line 21 "confitems.gperf"
+ {"hard_link", 11, ITEM(hard_link, bool)},
+#line 43 "confitems.gperf"
+ {"unify", 33, ITEM(unify, bool)},
+ {"",0,NULL,0,NULL},
+#line 16 "confitems.gperf"
+ {"compression_level", 6, ITEM(compression_level, unsigned)},
+ {"",0,NULL,0,NULL},
+#line 34 "confitems.gperf"
+ {"read_only", 24, ITEM(read_only, bool)},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+ {"",0,NULL,0,NULL}, {"",0,NULL,0,NULL},
+#line 30 "confitems.gperf"
+ {"memcached_only", 20, ITEM(memcached_only, bool)}
};
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
@@ -188,4 +205,4 @@ confitems_get (register const char *str, register unsigned int len)
}
return 0;
}
-static const size_t CONFITEMS_TOTAL_KEYWORDS = 31;
+static const size_t CONFITEMS_TOTAL_KEYWORDS = 34;
diff --git a/dump-memcached.py b/dump-memcached.py
new file mode 100755
index 0000000..e7b2b0d
--- /dev/null
+++ b/dump-memcached.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+import memcache
+import struct
+import sys
+import os
+import binascii
+
+"""
+/* blob format for storing:
+
+ char magic[4]; # 'CCH1', might change for other version of ccache
+ # ccache will erase the blob in memcached if wrong magic
+ uint32_t obj_len; # network endian
+ char *obj[obj_len];
+ uint32_t stderr_len; # network endian
+ char *stderr[stderr_len];
+ uint32_t dia_len; # network endian
+ char *dia[dia_len];
+ uint32_t dep_len; # network endian
+ char *dep[dep_len];
+
+*/
+"""
+MEMCCACHE_MAGIC = 'CCH1'
+
+def get_blob(token):
+ return token[4:4+struct.unpack('!I', val[0:4])[0]]
+MEMCCACHE_BIG = 'CCBM'
+
+"""
+/* blob format for big values:
+
+ char magic[4]; # 'CCBM'
+ uint32_t numkeys; # network endian
+ uint32_t hash_size; # network endian
+ uint32_t reserved; # network endian
+ uint32_t value_length; # network endian
+
+ <hash[0]> hash of include file (<hash_size> bytes)
+ <size[0]> size of include file (4 bytes unsigned int)
+ ...
+ <hash[n-1]>
+ <size[n-1]>
+
+*/
+"""
+MEMCCACHE_BIG = 'CCBM'
+
+server = os.getenv("MEMCACHED_SERVERS", "localhost")
+mc = memcache.Client(server.split(','), debug=1)
+
+key = sys.argv[1]
+val = mc.get(key)
+if val[0:4] == MEMCCACHE_BIG:
+ numkeys = struct.unpack('!I', val[4:8])[0]
+ assert struct.unpack('!I', val[8:12])[0] == 16
+ assert struct.unpack('!I', val[12:16])[0] == 0
+ size = struct.unpack('!I', val[16:20])[0]
+ val = val[20:]
+ buf = ""
+ while val:
+ md4 = val[0:16]
+ size = struct.unpack('!I', val[16:20])[0]
+ val = val[20:]
+ subkey = "%s-%d" % (binascii.hexlify(md4), size)
+ subval = mc.get(subkey)
+ if not subval:
+ print "%s not found" % subkey
+ buf = buf + subval
+ val = buf
+if val:
+ magic = val[0:4]
+ if magic == MEMCCACHE_MAGIC:
+ val = val[4:]
+ obj = get_blob(val)
+ val = val[4+len(obj):]
+ stderr = get_blob(val)
+ val = val[4+len(stderr):]
+ dia = get_blob(val)
+ val = val[4+len(dia):]
+ dep = get_blob(val)
+ val = val[4+len(dep):]
+ assert len(val) == 0
+ print "%s: %d %d %d %d" % (key, len(obj), len(stderr), len(dia), len(dep))
+ else:
+ print "wrong magic"
+else:
+ print "key missing"
diff --git a/envtoconfitems.gperf b/envtoconfitems.gperf
index 81d8444..00f64e0 100644
--- a/envtoconfitems.gperf
+++ b/envtoconfitems.gperf
@@ -27,12 +27,15 @@ LIMIT_MULTIPLE, "limit_multiple"
LOGFILE, "log_file"
MAXFILES, "max_files"
MAXSIZE, "max_size"
+MEMCACHED_CONF, "memcached_conf"
+MEMCACHED_ONLY, "memcached_only"
NLEVELS, "cache_dir_levels"
PATH, "path"
PREFIX, "prefix_command"
PREFIX_CPP, "prefix_command_cpp"
READONLY, "read_only"
READONLY_DIRECT, "read_only_direct"
+READONLY_MEMCACHED, "read_only_memcached"
RECACHE, "recache"
SLOPPINESS, "sloppiness"
STATS, "stats"
diff --git a/envtoconfitems_lookup.c b/envtoconfitems_lookup.c
index 1265bd6..4608827 100644
--- a/envtoconfitems_lookup.c
+++ b/envtoconfitems_lookup.c
@@ -1,6 +1,6 @@
/* ANSI-C code produced by gperf version 3.0.4 */
/* Command-line: gperf envtoconfitems.gperf */
-/* Computed positions: -k'1,5' */
+/* Computed positions: -k'1,5,11' */
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
@@ -31,7 +31,7 @@
#line 9 "envtoconfitems.gperf"
struct env_to_conf_item;
-/* maximum key range = 42, duplicates = 0 */
+/* maximum key range = 47, duplicates = 0 */
#ifdef __GNUC__
__inline
@@ -45,38 +45,46 @@ envtoconfitems_hash (register const char *str, register unsigned int len)
{
static const unsigned char asso_values[] =
{
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 20, 0, 0, 10,
- 0, 44, 5, 15, 0, 44, 10, 25, 9, 0,
- 5, 10, 5, 15, 10, 5, 44, 44, 44, 44,
- 0, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 44, 44, 44, 44
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 5, 0, 0, 5,
+ 40, 49, 20, 5, 0, 49, 20, 5, 0, 5,
+ 5, 0, 15, 0, 25, 0, 25, 49, 49, 49,
+ 0, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49
};
register int hval = len;
switch (hval)
{
default:
+ hval += asso_values[(unsigned char)str[10]];
+ /*FALLTHROUGH*/
+ case 10:
+ case 9:
+ case 8:
+ case 7:
+ case 6:
+ case 5:
hval += asso_values[(unsigned char)str[4]+1];
/*FALLTHROUGH*/
case 4:
@@ -101,11 +109,11 @@ envtoconfitems_get (register const char *str, register unsigned int len)
{
enum
{
- TOTAL_KEYWORDS = 31,
+ TOTAL_KEYWORDS = 34,
MIN_WORD_LENGTH = 2,
- MAX_WORD_LENGTH = 15,
+ MAX_WORD_LENGTH = 18,
MIN_HASH_VALUE = 2,
- MAX_HASH_VALUE = 43
+ MAX_HASH_VALUE = 48
};
static const struct env_to_conf_item wordlist[] =
@@ -117,72 +125,76 @@ envtoconfitems_get (register const char *str, register unsigned int len)
{"DIR", "cache_dir"},
#line 16 "envtoconfitems.gperf"
{"CPP2", "run_second_cpp"},
- {"",""},
+#line 44 "envtoconfitems.gperf"
+ {"UNIFY", "unify"},
#line 19 "envtoconfitems.gperf"
{"DIRECT", "direct_mode"},
#line 20 "envtoconfitems.gperf"
{"DISABLE", "disable"},
-#line 17 "envtoconfitems.gperf"
- {"COMMENTS", "keep_comments_cpp"},
-#line 31 "envtoconfitems.gperf"
+#line 14 "envtoconfitems.gperf"
+ {"COMPRESS", "compression"},
+#line 33 "envtoconfitems.gperf"
{"PATH", "path"},
-#line 41 "envtoconfitems.gperf"
- {"UNIFY", "unify"},
-#line 32 "envtoconfitems.gperf"
+#line 40 "envtoconfitems.gperf"
+ {"SLOPPINESS", "sloppiness"},
+#line 34 "envtoconfitems.gperf"
{"PREFIX", "prefix_command"},
-#line 36 "envtoconfitems.gperf"
- {"RECACHE", "recache"},
+#line 29 "envtoconfitems.gperf"
+ {"MAXSIZE", "max_size"},
+#line 28 "envtoconfitems.gperf"
+ {"MAXFILES", "max_files"},
+ {"",""},
+#line 35 "envtoconfitems.gperf"
+ {"PREFIX_CPP", "prefix_command_cpp"},
+ {"",""},
+#line 11 "envtoconfitems.gperf"
+ {"BASEDIR", "base_dir"},
#line 13 "envtoconfitems.gperf"
{"COMPILERCHECK", "compiler_check"},
+#line 21 "envtoconfitems.gperf"
+ {"EXTENSION", "cpp_extension"},
+#line 22 "envtoconfitems.gperf"
+ {"EXTRAFILES", "extra_files_to_hash"},
{"",""},
-#line 33 "envtoconfitems.gperf"
- {"PREFIX_CPP", "prefix_command_cpp"},
+#line 39 "envtoconfitems.gperf"
+ {"RECACHE", "recache"},
+#line 25 "envtoconfitems.gperf"
+ {"IGNOREHEADERS", "ignore_headers_in_manifest"},
#line 30 "envtoconfitems.gperf"
- {"NLEVELS", "cache_dir_levels"},
+ {"MEMCACHED_CONF", "memcached_conf"},
+#line 43 "envtoconfitems.gperf"
+ {"UMASK", "umask"},
+ {"",""},
#line 27 "envtoconfitems.gperf"
{"LOGFILE", "log_file"},
-#line 34 "envtoconfitems.gperf"
+#line 36 "envtoconfitems.gperf"
{"READONLY", "read_only"},
-#line 21 "envtoconfitems.gperf"
- {"EXTENSION", "cpp_extension"},
-#line 40 "envtoconfitems.gperf"
- {"UMASK", "umask"},
+#line 31 "envtoconfitems.gperf"
+ {"MEMCACHED_ONLY", "memcached_only"},
+#line 41 "envtoconfitems.gperf"
+ {"STATS", "stats"},
{"",""},
#line 24 "envtoconfitems.gperf"
{"HASHDIR", "hash_dir"},
-#line 14 "envtoconfitems.gperf"
- {"COMPRESS", "compression"},
- {"",""},
-#line 35 "envtoconfitems.gperf"
- {"READONLY_DIRECT", "read_only_direct"},
- {"",""},
-#line 39 "envtoconfitems.gperf"
+#line 23 "envtoconfitems.gperf"
+ {"HARDLINK", "hard_link"},
+ {"",""}, {"",""}, {"",""},
+#line 42 "envtoconfitems.gperf"
{"TEMPDIR", "temporary_dir"},
#line 15 "envtoconfitems.gperf"
{"COMPRESSLEVEL", "compression_level"},
#line 26 "envtoconfitems.gperf"
{"LIMIT_MULTIPLE", "limit_multiple"},
-#line 38 "envtoconfitems.gperf"
- {"STATS", "stats"},
- {"",""},
-#line 29 "envtoconfitems.gperf"
- {"MAXSIZE", "max_size"},
-#line 28 "envtoconfitems.gperf"
- {"MAXFILES", "max_files"},
- {"",""},
#line 37 "envtoconfitems.gperf"
- {"SLOPPINESS", "sloppiness"},
- {"",""},
-#line 11 "envtoconfitems.gperf"
- {"BASEDIR", "base_dir"},
-#line 23 "envtoconfitems.gperf"
- {"HARDLINK", "hard_link"},
- {"",""},
-#line 22 "envtoconfitems.gperf"
- {"EXTRAFILES", "extra_files_to_hash"},
+ {"READONLY_DIRECT", "read_only_direct"},
{"",""}, {"",""},
-#line 25 "envtoconfitems.gperf"
- {"IGNOREHEADERS", "ignore_headers_in_manifest"}
+#line 38 "envtoconfitems.gperf"
+ {"READONLY_MEMCACHED", "read_only_memcached"},
+ {"",""}, {"",""}, {"",""},
+#line 32 "envtoconfitems.gperf"
+ {"NLEVELS", "cache_dir_levels"},
+#line 17 "envtoconfitems.gperf"
+ {"COMMENTS", "keep_comments_cpp"}
};
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
@@ -199,4 +211,4 @@ envtoconfitems_get (register const char *str, register unsigned int len)
}
return 0;
}
-static const size_t ENVTOCONFITEMS_TOTAL_KEYWORDS = 31;
+static const size_t ENVTOCONFITEMS_TOTAL_KEYWORDS = 34;
diff --git a/memccached.c b/memccached.c
new file mode 100644
index 0000000..38c44aa
--- /dev/null
+++ b/memccached.c
@@ -0,0 +1,433 @@
+#include "ccache.h"
+
+#ifdef HAVE_LIBMEMCACHED
+
+#include <libmemcached/memcached.h>
+#include <netinet/in.h>
+
+#define MEMCCACHE_MAGIC "CCH1"
+#define MEMCCACHE_BIG "CCBM"
+
+#define MAX_VALUE_SIZE (1000 << 10) /* 1M with memcached overhead */
+#define SPLIT_VALUE_SIZE MAX_VALUE_SIZE
+
+/* status variables for memcached */
+static memcached_st *memc;
+
+int memccached_init(char *conf)
+{
+ memc = memcached(conf, strlen(conf));
+ if (!memc) {
+ char errorbuf[1024];
+ libmemcached_check_configuration(conf, strlen(conf), errorbuf, 1024);
+ cc_log("Problem creating memcached with conf %s:\n%s\n", conf, errorbuf);
+ return -1;
+ }
+ /* Consistent hashing delivers better distribution and allows servers to be
+ added to the cluster with minimal cache losses */
+ memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION,
+ MEMCACHED_DISTRIBUTION_CONSISTENT);
+ return 0;
+}
+
+/* blob format for big values:
+
+ char magic[4]; # 'CCBM'
+ uint32_t numkeys; # network endian
+ uint32_t hash_size; # network endian
+ uint32_t reserved; # network endian
+ uint32_t value_length; # network endian
+
+ <hash[0]> hash of include file (<hash_size> bytes)
+ <size[0]> size of include file (4 bytes unsigned int)
+ ...
+ <hash[n-1]>
+ <size[n-1]>
+
+ */
+static memcached_return_t memccached_big_set(memcached_st *ptr,
+ const char *key,
+ size_t key_length,
+ const char *value,
+ size_t value_length,
+ time_t expiration,
+ uint32_t flags)
+{
+ char *buf;
+ size_t buflen;
+ char *p;
+ int numkeys;
+ struct mdfour md;
+ char subkey[20];
+ size_t n;
+ memcached_return_t ret;
+ size_t x;
+
+ numkeys = (value_length + SPLIT_VALUE_SIZE - 1) / SPLIT_VALUE_SIZE;
+ buflen = 20 + 20 * numkeys;
+ buf = x_malloc(buflen);
+ p = buf;
+
+ memcpy(p, MEMCCACHE_BIG, 4);
+ *((uint32_t *) (p + 4)) = htonl(numkeys);
+ *((uint32_t *) (p + 8)) = htonl(16);
+ *((uint32_t *) (p + 12)) = htonl(0);
+ *((uint32_t *) (p + 16)) = htonl(value_length);
+ p += 20;
+
+ for (x = 0; x < value_length; x += n) {
+ size_t remain;
+ char *s;
+
+ remain = value_length - x;
+ n = remain > SPLIT_VALUE_SIZE ? SPLIT_VALUE_SIZE : remain;
+
+ mdfour_begin(&md);
+ mdfour_update(&md, (const unsigned char *) value + x, n);
+ mdfour_result(&md, (unsigned char *) subkey);
+ *((uint32_t *) (subkey + 16)) = htonl(n);
+ s = format_hash_as_string((const unsigned char *) subkey, n);
+ cc_log("memcached_mset %s %zu", s, n);
+ ret = memcached_set(ptr, s, strlen(s), value + x, n,
+ expiration, flags);
+ free(s);
+ if (ret) {
+ cc_log("Failed to set key in memcached: %s",
+ memcached_strerror(memc, ret));
+ return ret;
+ }
+
+ memcpy(p, subkey, 20);
+ p += 20;
+ }
+
+ cc_log("memcached_set %.*s %zu (%zu)", (int) key_length, key, buflen,
+ value_length);
+ ret = memcached_set(ptr, key, key_length, buf, buflen,
+ expiration, flags);
+ free(buf);
+ return ret;
+}
+
+static char *memccached_big_get(memcached_st *ptr,
+ const char *key,
+ size_t key_length,
+ const char *value,
+ size_t *value_length,
+ uint32_t *flags,
+ memcached_return_t *error)
+{
+ char *buf;
+ size_t buflen;
+ size_t totalsize;
+ char *p;
+ const char *v;
+ int numkeys;
+ char **keys;
+ bool *key_seen;
+ size_t *key_lengths;
+ size_t *value_offsets;
+ int *value_lengths;
+ memcached_return_t ret;
+ memcached_result_st *result;
+ int n;
+ int i;
+
+ if (!value) {
+ value = memcached_get(ptr, key, key_length, value_length, flags, error);
+ if (!value) {
+ return NULL;
+ }
+ }
+
+ p = (char *) value;
+ if (memcmp(p, MEMCCACHE_BIG, 4) != 0) {
+ return NULL;
+ }
+ numkeys = ntohl(*(uint32_t *) (p + 4));
+ assert(ntohl(*(uint32_t *) (p + 8)) == 16);
+ assert(ntohl(*(uint32_t *) (p + 12)) == 0);
+ totalsize = ntohl(*(uint32_t *) (p + 16));
+ p += 20;
+
+ keys = x_malloc(sizeof(char *) * numkeys);
+ key_seen = x_malloc(sizeof(bool) * numkeys);
+ key_lengths = x_malloc(sizeof(size_t) * numkeys);
+ value_offsets = x_malloc(sizeof(size_t) * numkeys);
+ value_lengths = x_malloc(sizeof(int) * numkeys);
+
+ buflen = 0;
+ for (i = 0; i < numkeys; i++) {
+ n = ntohl(*((uint32_t *) (p + 16)));
+ keys[i] = format_hash_as_string((const unsigned char *) p, n);
+ key_lengths[i] = strlen(keys[i]);
+ key_seen[i] = false;
+ cc_log("memcached_mget %.*s %d", (int) key_lengths[i], keys[i], n);
+ value_offsets[i] = buflen;
+ value_lengths[i] = n;
+ buflen += n;
+ p += 20;
+ }
+ assert(buflen == totalsize);
+
+ buf = x_malloc(buflen);
+
+ ret = memcached_mget(ptr, (const char *const *) keys, key_lengths, numkeys);
+ if (ret) {
+ cc_log("Failed to mget keys in memcached: %s",
+ memcached_strerror(memc, ret));
+ for (i = 0; i < numkeys; i++) {
+ free(keys[i]);
+ }
+ free(keys);
+ free(key_lengths);
+ return NULL;
+ }
+
+ result = NULL;
+ do {
+ const char *k;
+ size_t l;
+
+ result = memcached_fetch_result(ptr, result, &ret);
+ if (ret == MEMCACHED_END) {
+ break;
+ }
+ if (ret) {
+ cc_log("Failed to get key in memcached: %s",
+ memcached_strerror(memc, ret));
+ return NULL;
+ }
+ k = memcached_result_key_value(result);
+ l = memcached_result_key_length(result);
+ p = NULL;
+ for (i = 0; i < numkeys; i++) {
+ if (l != key_lengths[i]) {
+ continue;
+ }
+ if (memcmp(k, keys[i], l) == 0) {
+ p = buf + value_offsets[i];
+ break;
+ }
+ }
+ if (!p) {
+ cc_log("Unknown key was returned: %s", k);
+ return NULL;
+ }
+ if (key_seen[i]) {
+ cc_log("Have already seen chunk: %s", k);
+ return NULL;
+ }
+ key_seen[i] = true;
+ n = memcached_result_length(result);
+ v = memcached_result_value(result);
+ if (n != value_lengths[i]) {
+ cc_log("Unexpected length was returned");
+ return NULL;
+ }
+ memcpy(p, v, n);
+ } while (ret == MEMCACHED_SUCCESS);
+
+ for (i = 0; i < numkeys; i++) {
+ if (!key_seen[i]) {
+ cc_log("Failed to get all %d chunks", numkeys);
+ return NULL;
+ }
+ }
+ cc_log("memcached_get %.*s %zu (%zu)", (int) key_length, key, *value_length,
+ buflen);
+ for (i = 0; i < numkeys; i++) {
+ free(keys[i]);
+ }
+ free(keys);
+ free(key_lengths);
+ free(value_offsets);
+ free(value_lengths);
+
+ *value_length = buflen;
+ return buf;
+}
+
+int memccached_raw_set(const char *key, const char *data, size_t len)
+{
+ memcached_return_t mret;
+
+ mret = memcached_set(memc, key, strlen(key), data, len, 0, 0);
+ if (mret != MEMCACHED_SUCCESS) {
+ cc_log("Failed to move %s to memcached: %s", key,
+ memcached_strerror(memc, mret));
+ return -1;
+ }
+ return 0;
+}
+
+/* blob format for storing:
+
+ char magic[4]; # 'CCH1', might change for other version of ccache
+ # ccache will erase the blob in memcached if wrong magic
+ uint32_t obj_len; # network endian
+ char *obj[obj_len];
+ uint32_t stderr_len; # network endian
+ char *stderr[stderr_len];
+ uint32_t dia_len; # network endian
+ char *dia[dia_len];
+ uint32_t dep_len; # network endian
+ char *dep[dep_len];
+
+ */
+int memccached_set(const char *key,
+ const char *obj,
+ const char *stderr,
+ const char *dia,
+ const char *dep,
+ size_t obj_len,
+ size_t stderr_len,
+ size_t dia_len,
+ size_t dep_len)
+{
+ size_t buf_len = 4 + 4*4 + obj_len + stderr_len + dia_len + dep_len;
+ char *buf = x_malloc(buf_len);
+ char *ptr;
+ memcached_return_t mret;
+
+ memcpy(buf, MEMCCACHE_MAGIC, 4);
+ ptr = buf + 4;
+
+#define PROCESS_ONE_BUFFER(src_ptr, src_len) \
+ do { \
+ *((uint32_t *)ptr) = htonl(src_len); \
+ ptr += 4; \
+ if (src_len > 0) { \
+ memcpy(ptr, src_ptr, src_len); \
+ } \
+ ptr += src_len; \
+ } while (false)
+
+ PROCESS_ONE_BUFFER(obj, obj_len);
+ PROCESS_ONE_BUFFER(stderr, stderr_len);
+ PROCESS_ONE_BUFFER(dia, dia_len);
+ PROCESS_ONE_BUFFER(dep, dep_len);
+
+#undef PROCESS_ONE_BUFFER
+
+ if (buf_len > MAX_VALUE_SIZE) {
+ mret = memccached_big_set(memc, key, strlen(key), buf, buf_len, 0, 0);
+ } else {
+ mret = memcached_set(memc, key, strlen(key), buf, buf_len, 0, 0);
+ }
+
+ if (mret != MEMCACHED_SUCCESS) {
+ cc_log("Failed to move %s to memcached: %s", key,
+ memcached_strerror(memc, mret));
+ return -1;
+ }
+ return 0;
+}
+
+static void *memccached_prune(const char *key)
+{
+ cc_log("key from memcached has wrong data %s: pruning...", key);
+ /* don't really care whether delete failed */
+ memcached_delete(memc, key, strlen(key), 0);
+ return NULL;
+}
+
+void *memccached_raw_get(const char *key, char **data, size_t *size)
+{
+ memcached_return_t mret;
+ void *value;
+ size_t value_l;
+
+ value = memcached_get(memc, key, strlen(key), &value_l,
+ NULL /*flags*/, &mret);
+ if (!value) {
+ cc_log("Failed to get key from memcached %s: %s", key,
+ memcached_strerror(memc, mret));
+ return NULL;
+ }
+ *data = value;
+ *size = value_l;
+ return value; /* caller must free this when done with the ptr */
+}
+
+void *memccached_get(const char *key,
+ char **obj,
+ char **stderr,
+ char **dia,
+ char **dep,
+ size_t *obj_len,
+ size_t *stderr_len,
+ size_t *dia_len,
+ size_t *dep_len)
+{
+ memcached_return_t mret;
+ char *value, *ptr;
+ size_t value_l;
+ value = memcached_get(memc, key, strlen(key), &value_l,
+ NULL /*flags*/, &mret);
+ if (!value) {
+ cc_log("Failed to get key from memcached %s: %s", key,
+ memcached_strerror(memc, mret));
+ return NULL;
+ }
+ if (value_l > 4 && memcmp(value, MEMCCACHE_BIG, 4) == 0) {
+ value = memccached_big_get(memc, key, strlen(key), value, &value_l,
+ NULL /*flags*/, &mret);
+ }
+ if (!value) {
+ cc_log("Failed to get key from memcached %s: %s", key,
+ memcached_strerror(memc, mret));
+ return NULL;
+ }
+ if (value_l < 20 || memcmp(value, MEMCCACHE_MAGIC, 4) != 0) {
+ cc_log("wrong magic or length %.4s: %d", value, (int)value_l);
+ free(value);
+ return memccached_prune(key);
+ }
+ ptr = value;
+ /* skip the magic */
+ ptr += 4;
+ value_l -= 4;
+
+#define PROCESS_ONE_BUFFER(dst_ptr, dst_len) \
+ do { \
+ if (value_l < 4) { \
+ free(value); \
+ cc_log("no more buffer for %s: %d", \
+ #dst_ptr, (int)value_l); \
+ return memccached_prune(key); \
+ } \
+ dst_len = ntohl(*((uint32_t *)ptr)); \
+ ptr += 4; value_l -= 4; \
+ if (value_l < dst_len) { \
+ cc_log("no more buffer for %s: %d %d", \
+ #dst_ptr, (int)value_l, (int) dst_len); \
+ free(value); \
+ return memccached_prune(key); \
+ } \
+ dst_ptr = ptr; \
+ ptr += dst_len; value_l -= dst_len; \
+ } while (false)
+
+ PROCESS_ONE_BUFFER(*obj, *obj_len);
+ PROCESS_ONE_BUFFER(*stderr, *stderr_len);
+ PROCESS_ONE_BUFFER(*dia, *dia_len);
+ PROCESS_ONE_BUFFER(*dep, *dep_len);
+
+#undef PROCESS_ONE_BUFFER
+
+ return value; /* caller must free this when done with the ptrs */
+}
+
+void memccached_free(void *blob)
+{
+ free(blob);
+}
+
+int memccached_release(void)
+{
+ memcached_free(memc);
+ return 1;
+}
+
+#endif /* HAVE_LIBMEMCACHED */
diff --git a/test.sh b/test.sh
index 3e04157..ac3eb6d 100755
--- a/test.sh
+++ b/test.sh
@@ -43,6 +43,11 @@ test_failed() {
$CCACHE -s
echo
echo "Test data and log file have been left in $TESTDIR"
+ tail -n 50 $CCACHE_LOGFILE
+ if [ ! -z $CCACHE_MEMCACHED_CONF ]; then
+ memstat --servers=localhost:22122
+ kill %1
+ fi
exit 1
}
@@ -188,6 +193,8 @@ TEST() {
unset CCACHE_IGNOREHEADERS
unset CCACHE_LIMIT_MULTIPLE
unset CCACHE_LOGFILE
+ unset CCACHE_MEMCACHED_CONF
+ unset CCACHE_MEMCACHED_ONLY
unset CCACHE_NLEVELS
unset CCACHE_NOCPP2
unset CCACHE_NOHASHDIR
@@ -244,13 +251,13 @@ base_tests() {
$CCACHE_COMPILE -c test1.c
expect_stat 'cache hit (preprocessed)' 0
expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $CCACHE_NOFILES expect_stat 'files in cache' 1
expect_equal_object_files reference_test1.o test1.o
$CCACHE_COMPILE -c test1.c
expect_stat 'cache hit (preprocessed)' 1
expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $CCACHE_NOFILES expect_stat 'files in cache' 1
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
@@ -259,7 +266,7 @@ base_tests() {
$CCACHE_COMPILE -c test1.c -g
expect_stat 'cache hit (preprocessed)' 0
expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $CCACHE_NOFILES expect_stat 'files in cache' 1
$CCACHE_COMPILE -c test1.c -g
expect_stat 'cache hit (preprocessed)' 1
@@ -602,7 +609,7 @@ b"
done
expect_stat 'cache hit (preprocessed)' 0
expect_stat 'cache miss' 32
- expect_stat 'files in cache' 32
+ $CCACHE_NOFILES expect_stat 'files in cache' 32
# -------------------------------------------------------------------------
TEST "Called for preprocessing"
@@ -1366,6 +1373,52 @@ SUITE_masquerading() {
# =============================================================================
+SUITE_memcached_SETUP() {
+ generate_code 1 test1.c
+}
+
+SUITE_memcached() {
+ export CCACHE_MEMCACHED_CONF=--SERVER=localhost:22122
+ memcached -p 22122 &
+ memcached_pid=$!
+ base_tests
+ kill $memcached_pid
+ unset CCACHE_MEMCACHED_CONF
+}
+
+SUITE_memcached_only_SETUP() {
+ generate_code 1 test1.c
+}
+
+SUITE_memcached_only() {
+ CCACHE_NOFILES=true
+ export CCACHE_MEMCACHED_CONF=--SERVER=localhost:22122
+ export CCACHE_MEMCACHED_ONLY=1
+ memcached -p 22122 &
+ memcached_pid=$!
+ base_tests
+ kill $memcached_pid
+ unset CCACHE_MEMCACHED_CONF
+ unset CCACHE_MEMCACHED_ONLY
+ unset CCACHE_NOFILES
+}
+
+SUITE_memcached_socket_SETUP() {
+ generate_code 1 test1.c
+}
+
+SUITE_memcached_socket() {
+ export CCACHE_MEMCACHED_CONF=--SOCKET=\"/tmp/memcached.$$\"
+ memcached -s /tmp/memcached.$$ &
+ memcached_pid=$!
+ base_tests
+ kill $memcached_pid
+ rm /tmp/memcached.$$
+ unset CCACHE_MEMCACHED_CONF
+}
+
+# =============================================================================
+
SUITE_hardlink_PROBE() {
touch file1
if ! ln file1 file2 >/dev/null 2>&1; then
@@ -1539,7 +1592,7 @@ EOF
test_failed "$dep_file missing"
fi
done
- expect_stat 'files in cache' 12
+ $CCACHE_NOFILES expect_stat 'files in cache' 12
# -------------------------------------------------------------------------
TEST "-Wp,-MD"
@@ -3412,6 +3465,14 @@ upgrade
input_charset
"
+if [ ! -z $CCACHE_MEMCACHED ]; then
+ all_suites="$all_suites
+memcached
+memcached_only
+memcached_socket
+"
+fi
+
compiler_location=$(which $(echo "$COMPILER" | awk '{print $1}'))
if [ "$compiler_location" = "$COMPILER" ]; then
echo "Compiler: $COMPILER"
diff --git a/test/test_conf.c b/test/test_conf.c
index ea43e2e..d65372c 100644
--- a/test/test_conf.c
+++ b/test/test_conf.c
@@ -18,7 +18,7 @@
#include "framework.h"
#include "util.h"
-#define N_CONFIG_ITEMS 31
+#define N_CONFIG_ITEMS 34
static struct {
char *descr;
const char *origin;
@@ -68,11 +68,14 @@ TEST(conf_create)
CHECK_STR_EQ("", conf->log_file);
CHECK_INT_EQ(0, conf->max_files);
CHECK_INT_EQ((uint64_t)5 * 1000 * 1000 * 1000, conf->max_size);
+ CHECK_STR_EQ("", conf->memcached_conf);
+ CHECK(!conf->memcached_only);
CHECK_STR_EQ("", conf->path);
CHECK_STR_EQ("", conf->prefix_command);
CHECK_STR_EQ("", conf->prefix_command_cpp);
CHECK(!conf->read_only);
CHECK(!conf->read_only_direct);
+ CHECK(!conf->read_only_memcached);
CHECK(!conf->recache);
CHECK(conf->run_second_cpp);
CHECK_INT_EQ(0, conf->sloppiness);
@@ -119,11 +122,14 @@ TEST(conf_read_valid_config)
"log_file = $USER${USER} \n"
"max_files = 17\n"
"max_size = 123M\n"
+ "memcached_conf = --SERVER=localhost\n"
+ "memcached_only = true\n"
"path = $USER.x\n"
"prefix_command = x$USER\n"
"prefix_command_cpp = y\n"
"read_only = true\n"
"read_only_direct = true\n"
+ "read_only_memcached = false\n"
"recache = true\n"
"run_second_cpp = false\n"
"sloppiness = file_macro ,time_macros, include_file_mtime,include_file_ctime,file_stat_matches,pch_defines , no_system_headers \n"
@@ -157,11 +163,14 @@ TEST(conf_read_valid_config)
CHECK_STR_EQ_FREE1(format("%s%s", user, user), conf->log_file);
CHECK_INT_EQ(17, conf->max_files);
CHECK_INT_EQ(123 * 1000 * 1000, conf->max_size);
+ CHECK_STR_EQ("--SERVER=localhost", conf->memcached_conf);
+ CHECK(conf->memcached_only);
CHECK_STR_EQ_FREE1(format("%s.x", user), conf->path);
CHECK_STR_EQ_FREE1(format("x%s", user), conf->prefix_command);
CHECK_STR_EQ("y", conf->prefix_command_cpp);
CHECK(conf->read_only);
CHECK(conf->read_only_direct);
+ CHECK(!conf->read_only_memcached);
CHECK(conf->recache);
CHECK(!conf->run_second_cpp);
CHECK_INT_EQ(SLOPPY_INCLUDE_FILE_MTIME|SLOPPY_INCLUDE_FILE_CTIME|
@@ -383,11 +392,14 @@ TEST(conf_print_items)
"lf",
4711,
98.7 * 1000 * 1000,
+ "mc",
+ false,
"p",
"pc",
"pcc",
true,
true,
+ false,
true,
.run_second_cpp = false,
SLOPPY_FILE_MACRO|SLOPPY_INCLUDE_FILE_MTIME|
@@ -433,11 +445,14 @@ TEST(conf_print_items)
CHECK_STR_EQ("log_file = lf", received_conf_items[n++].descr);
CHECK_STR_EQ("max_files = 4711", received_conf_items[n++].descr);
CHECK_STR_EQ("max_size = 98.7M", received_conf_items[n++].descr);
+ CHECK_STR_EQ("memcached_conf = mc", received_conf_items[n++].descr);
+ CHECK_STR_EQ("memcached_only = false", received_conf_items[n++].descr);
CHECK_STR_EQ("path = p", received_conf_items[n++].descr);
CHECK_STR_EQ("prefix_command = pc", received_conf_items[n++].descr);
CHECK_STR_EQ("prefix_command_cpp = pcc", received_conf_items[n++].descr);
CHECK_STR_EQ("read_only = true", received_conf_items[n++].descr);
CHECK_STR_EQ("read_only_direct = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("read_only_memcached = false", received_conf_items[n++].descr);
CHECK_STR_EQ("recache = true", received_conf_items[n++].descr);
CHECK_STR_EQ("run_second_cpp = false", received_conf_items[n++].descr);
CHECK_STR_EQ("sloppiness = file_macro, include_file_mtime,"
diff --git a/upload-memcached.py b/upload-memcached.py
new file mode 100755
index 0000000..bc489b0
--- /dev/null
+++ b/upload-memcached.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+
+import memcache
+import struct
+import os
+import hashlib
+
+"""
+/* blob format for storing:
+
+ char magic[4]; # 'CCH1', might change for other version of ccache
+ # ccache will erase the blob in memcached if wrong magic
+ uint32_t obj_len; # network endian
+ char *obj[obj_len];
+ uint32_t stderr_len; # network endian
+ char *stderr[stderr_len];
+ uint32_t dia_len; # network endian
+ char *dia[dia_len];
+ uint32_t dep_len; # network endian
+ char *dep[dep_len];
+
+*/
+"""
+MEMCCACHE_MAGIC = 'CCH1'
+
+def set_blob(data):
+ return struct.pack('!I', len(data)) + str(data)
+MEMCCACHE_BIG = 'CCBM'
+
+"""
+/* blob format for big values:
+
+ char magic[4]; # 'CCBM'
+ uint32_t numkeys; # network endian
+ uint32_t hash_size; # network endian
+ uint32_t reserved; # network endian
+ uint32_t value_length; # network endian
+
+ <hash[0]> hash of include file (<hash_size> bytes)
+ <size[0]> size of include file (4 bytes unsigned int)
+ ...
+ <hash[n-1]>
+ <size[n-1]>
+
+*/
+"""
+MEMCCACHE_BIG = 'CCBM'
+
+MAX_VALUE_SIZE = 1000 << 10 # 1M with memcached overhead
+SPLIT_VALUE_SIZE = MAX_VALUE_SIZE
+
+server = os.getenv("MEMCACHED_SERVERS", "localhost")
+mc = memcache.Client(server.split(','), debug=1)
+
+ccache = os.getenv("CCACHE_DIR", os.path.expanduser("~/.ccache"))
+filelist = []
+for dirpath, dirnames, filenames in os.walk(ccache):
+ # sort by modification time, most recently used last
+ for filename in filenames:
+ stat = os.stat(os.path.join(dirpath, filename))
+ filelist.append((stat.st_mtime, dirpath, filename))
+filelist.sort()
+files = blobs = chunks = objects = manifest = 0
+for mtime, dirpath, filename in filelist:
+ dirname = dirpath.replace(ccache + os.path.sep, "")
+ if filename == "CACHEDIR.TAG":
+ # ignore these
+ files = files + 1
+ else:
+ (base, ext) = os.path.splitext(filename)
+ if ext == '.o':
+ objects = objects + 1
+ key = "".join(list(os.path.split(dirname)) + [base])
+ def read_file(path):
+ return os.path.exists(path) and open(path).read() or ""
+ obj = read_file(os.path.join(dirpath, filename))
+ stderr = read_file(os.path.join(dirpath, base) + '.stderr')
+ dia = read_file(os.path.join(dirpath, base) + '.dia')
+ dep = read_file(os.path.join(dirpath, base) + '.d')
+
+ print "%s: %d %d %d %d" % (key, len(obj), len(stderr), len(dia), len(dep))
+ val = MEMCCACHE_MAGIC
+ val += set_blob(obj)
+ val += set_blob(stderr)
+ val += set_blob(dia)
+ val += set_blob(dep)
+ if len(val) > MAX_VALUE_SIZE:
+ numkeys = (len(val) + SPLIT_VALUE_SIZE - 1) / SPLIT_VALUE_SIZE
+ buf = MEMCCACHE_BIG
+ buf += struct.pack('!I', numkeys)
+ buf += struct.pack('!I', 16)
+ buf += struct.pack('!I', 0)
+ buf += struct.pack('!I', len(val))
+ def splitchunks(s, n):
+ """Produce `n`-character chunks from `s`."""
+ for start in range(0, len(s), n):
+ yield s[start:start+n]
+ valmap = {}
+ for subval in splitchunks(val, SPLIT_VALUE_SIZE):
+ subhash = hashlib.new('md4')
+ subhash.update(subval)
+ buf += subhash.digest() + struct.pack('!I', len(subval))
+ subkey = "%s-%d" % (subhash.hexdigest(), len(subval))
+ print "# %s: chunk %d" % (subkey, len(subval))
+ #mc.set(subkey, subval)
+ valmap[subkey] = subval
+ chunks = chunks + 1
+ mc.set_multi(valmap)
+ mc.set(key, buf)
+ else:
+ mc.set(key, val)
+ files = files + 1
+ blobs = blobs + 1
+ elif ext == '.stderr' or ext == '.d' or ext == '.dia':
+ # was added above
+ files = files + 1
+ elif ext == '.manifest':
+ manifest = manifest + 1
+ key = "".join(list(os.path.split(dirname)) + [base])
+ val = open(os.path.join(dirpath, filename)).read() or None
+ if val:
+ print "%s: manifest %d" % (key, len(val))
+ mc.set(key, val, 0, 0)
+ files = files + 1
+ blobs = blobs + 1
+print "%d files, %d objects (%d manifest) = %d blobs (%d chunks)" % (files, objects, manifest, blobs, chunks)
diff --git a/util.c b/util.c
index f048d97..6059f25 100644
--- a/util.c
+++ b/util.c
@@ -388,6 +388,75 @@ copy_file(const char *src, const char *dest, int compress_level)
return -1;
}
+// Write data to a fd.
+int safe_write(int fd_out, const char *data, size_t length)
+{
+ size_t written = 0;
+ do {
+ int ret;
+ ret = write(fd_out, data + written, length - written);
+ if (ret < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ return ret;
+ }
+ } else {
+ written += ret;
+ }
+ } while (written < length);
+ return 0;
+}
+
+// Write data to a file.
+int write_file(const char *data, const char *dest, size_t length)
+{
+ int fd_out;
+ char *tmp_name;
+ int ret;
+ int saved_errno = 0;
+
+ tmp_name = x_strdup(dest);
+ fd_out = create_tmp_fd(&tmp_name);
+ if (fd_out < 0) {
+ tmp_unlink(tmp_name);
+ free(tmp_name);
+ return -1;
+ }
+
+ ret = safe_write(fd_out, data, length);
+ if (ret < 0) {
+ saved_errno = errno;
+ cc_log("write error: %s", strerror(saved_errno));
+ goto error;
+ }
+
+#ifndef _WIN32
+ fchmod(fd_out, 0666 & ~get_umask());
+#endif
+
+ /* the close can fail on NFS if out of space */
+ if (close(fd_out) == -1) {
+ saved_errno = errno;
+ cc_log("close error: %s", strerror(saved_errno));
+ goto error;
+ }
+
+ if (x_rename(tmp_name, dest) == -1) {
+ saved_errno = errno;
+ cc_log("rename error: %s", strerror(saved_errno));
+ goto error;
+ }
+
+ free(tmp_name);
+ return 0;
+
+error:
+ close(fd_out);
+ tmp_unlink(tmp_name);
+ free(tmp_name);
+ errno = saved_errno;
+ return -1;
+}
+
// Run copy_file() and, if successful, delete the source file.
int
move_file(const char *src, const char *dest, int compress_level)