summaryrefslogtreecommitdiff
path: root/cleanup.c
diff options
context:
space:
mode:
Diffstat (limited to 'cleanup.c')
-rw-r--r--cleanup.c262
1 files changed, 262 insertions, 0 deletions
diff --git a/cleanup.c b/cleanup.c
new file mode 100644
index 0000000..530bd45
--- /dev/null
+++ b/cleanup.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2002-2006 Andrew Tridgell
+ * Copyright (C) 2009-2010 Joel Rosdahl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "ccache.h"
+
+/*
+ * When "max files" or "max cache size" is reached, one of the 16 cache
+ * subdirectories is cleaned up. When doing so, files are deleted (in LRU
+ * order) until the levels are below LIMIT_MULTIPLE.
+ */
+#define LIMIT_MULTIPLE 0.8
+
+static struct files {
+ char *fname;
+ time_t mtime;
+ size_t size; /* In KiB. */
+} **files;
+static unsigned allocated; /* Size of the files array. */
+static unsigned num_files; /* Number of used entries in the files array. */
+
+static size_t cache_size; /* In KiB. */
+static size_t files_in_cache;
+static size_t cache_size_threshold;
+static size_t files_in_cache_threshold;
+
+/* File comparison function that orders files in mtime order, oldest first. */
+static int
+files_compare(struct files **f1, struct files **f2)
+{
+ if ((*f2)->mtime == (*f1)->mtime) {
+ return strcmp((*f1)->fname, (*f2)->fname);
+ }
+ if ((*f2)->mtime > (*f1)->mtime) {
+ return -1;
+ }
+ return 1;
+}
+
+/* this builds the list of files in the cache */
+static void
+traverse_fn(const char *fname, struct stat *st)
+{
+ char *p;
+
+ if (!S_ISREG(st->st_mode)) return;
+
+ p = basename(fname);
+ if (str_eq(p, "stats")) {
+ goto out;
+ }
+
+ if (str_startswith(p, ".nfs")) {
+ /* Ignore temporary NFS files that may be left for open but deleted files. */
+ goto out;
+ }
+
+ if (strstr(p, ".tmp.") != NULL) {
+ /* delete any tmp files older than 1 hour */
+ if (st->st_mtime + 3600 < time(NULL)) {
+ x_unlink(fname);
+ goto out;
+ }
+ }
+
+ if (num_files == allocated) {
+ allocated = 10000 + num_files*2;
+ files = (struct files **)x_realloc(files, sizeof(struct files *)*allocated);
+ }
+
+ files[num_files] = (struct files *)x_malloc(sizeof(struct files));
+ files[num_files]->fname = x_strdup(fname);
+ files[num_files]->mtime = st->st_mtime;
+ files[num_files]->size = file_size(st) / 1024;
+ cache_size += files[num_files]->size;
+ files_in_cache++;
+ num_files++;
+
+out:
+ free(p);
+}
+
+static void
+delete_file(const char *path, size_t size)
+{
+ if (x_unlink(path) == 0) {
+ cache_size -= size;
+ files_in_cache--;
+ } else if (errno != ENOENT) {
+ cc_log("Failed to unlink %s (%s)", path, strerror(errno));
+ }
+}
+
+static void
+delete_sibling_file(const char *base, const char *extension)
+{
+ struct stat st;
+ char *path;
+
+ path = format("%s%s", base, extension);
+ if (lstat(path, &st) == 0) {
+ delete_file(path, file_size(&st) / 1024);
+ } else if (errno != ENOENT) {
+ cc_log("Failed to stat %s (%s)", path, strerror(errno));
+ }
+ free(path);
+}
+
+/* sort the files we've found and delete the oldest ones until we are
+ below the thresholds */
+static void
+sort_and_clean(void)
+{
+ unsigned i;
+ const char *ext;
+ char *last_base = x_strdup("");
+
+ if (num_files > 1) {
+ /* Sort in ascending mtime order. */
+ qsort(files, num_files, sizeof(struct files *), (COMPAR_FN_T)files_compare);
+ }
+
+ /* delete enough files to bring us below the threshold */
+ for (i = 0; i < num_files; i++) {
+ if ((cache_size_threshold == 0
+ || cache_size <= cache_size_threshold)
+ && (files_in_cache_threshold == 0
+ || files_in_cache <= files_in_cache_threshold)) {
+ break;
+ }
+
+ ext = get_extension(files[i]->fname);
+ if (str_eq(ext, ".o")
+ || str_eq(ext, ".d")
+ || str_eq(ext, ".stderr")
+ || str_eq(ext, "")) {
+ char *base = remove_extension(files[i]->fname);
+ if (!str_eq(base, last_base)) { /* Avoid redundant unlinks. */
+ /*
+ * Make sure that all sibling files are deleted so that a cached result
+ * is removed completely. Note the order of deletions -- the stderr
+ * file must be deleted last because if the ccache process gets killed
+ * after deleting the .stderr but before deleting the .o, the cached
+ * result would be inconsistent.
+ */
+ delete_sibling_file(base, ".o");
+ delete_sibling_file(base, ".d");
+ delete_sibling_file(base, ".stderr");
+ delete_sibling_file(base, ""); /* Object file from ccache 2.4. */
+ }
+ free(last_base);
+ last_base = base;
+ } else {
+ /* .manifest or unknown file. */
+ delete_file(files[i]->fname, files[i]->size);
+ }
+ }
+ free(last_base);
+}
+
+/* cleanup in one cache subdir */
+void
+cleanup_dir(const char *dir, size_t maxfiles, size_t maxsize)
+{
+ unsigned i;
+
+ cc_log("Cleaning up cache directory %s", dir);
+
+ cache_size_threshold = maxsize * LIMIT_MULTIPLE;
+ files_in_cache_threshold = maxfiles * LIMIT_MULTIPLE;
+
+ num_files = 0;
+ cache_size = 0;
+ files_in_cache = 0;
+
+ /* build a list of files */
+ traverse(dir, traverse_fn);
+
+ /* clean the cache */
+ sort_and_clean();
+
+ stats_set_sizes(dir, files_in_cache, cache_size);
+
+ /* free it up */
+ for (i = 0; i < num_files; i++) {
+ free(files[i]->fname);
+ free(files[i]);
+ files[i] = NULL;
+ }
+ if (files) {
+ free(files);
+ }
+ allocated = 0;
+ files = NULL;
+
+ num_files = 0;
+ cache_size = 0;
+ files_in_cache = 0;
+}
+
+/* cleanup in all cache subdirs */
+void cleanup_all(const char *dir)
+{
+ unsigned maxfiles, maxsize;
+ char *dname;
+ int i;
+
+ for (i = 0; i <= 0xF; i++) {
+ dname = format("%s/%1x", dir, i);
+ stats_get_limits(dname, &maxfiles, &maxsize);
+ cleanup_dir(dname, maxfiles, maxsize);
+ free(dname);
+ }
+}
+
+/* traverse function for wiping files */
+static void wipe_fn(const char *fname, struct stat *st)
+{
+ char *p;
+
+ if (!S_ISREG(st->st_mode)) return;
+
+ p = basename(fname);
+ if (str_eq(p, "stats")) {
+ free(p);
+ return;
+ }
+ free(p);
+
+ x_unlink(fname);
+}
+
+/* wipe all cached files in all subdirs */
+void wipe_all(const char *dir)
+{
+ char *dname;
+ int i;
+
+ for (i = 0; i <= 0xF; i++) {
+ dname = format("%s/%1x", dir, i);
+ traverse(dir, wipe_fn);
+ free(dname);
+ }
+
+ /* and fix the counters */
+ cleanup_all(dir);
+}