// Copyright (C) 2002-2006 Andrew Tridgell // Copyright (C) 2009-2018 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" #include static struct files { char *fname; time_t mtime; uint64_t size; } **files; static unsigned allocated; // Size of the files array. static unsigned num_files; // Number of used entries in the files array. static uint64_t cache_size; static size_t files_in_cache; static uint64_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) { if (!S_ISREG(st->st_mode)) { return; } char *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; } // Delete any tmp files older than 1 hour. if (strstr(p, ".tmp.") && st->st_mtime + 3600 < time(NULL)) { x_unlink(fname); goto out; } if (strstr(p, "CACHEDIR.TAG")) { 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); cache_size += files[num_files]->size; files_in_cache++; num_files++; out: free(p); } static void delete_file(const char *path, size_t size, bool update_counters) { bool deleted = x_try_unlink(path) == 0; if (!deleted && errno != ENOENT && errno != ESTALE) { cc_log("Failed to unlink %s (%s)", path, strerror(errno)); } else if (update_counters) { // The counters are intentionally subtracted even if there was no file to // delete since the final cache size calculation will be incorrect if they // aren't. (This can happen when there are several parallel ongoing // cleanups of the same directory.) cache_size -= size; files_in_cache--; } } // Sort the files we've found and delete the oldest ones until we are below the // thresholds. static bool sort_and_clean(void) { 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. bool cleaned = false; for (unsigned i = 0; i < num_files; i++) { const char *ext; 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, ".stderr")) { // Make sure that the .o file is deleted before .stderr, because if the // ccache process gets killed after deleting the .stderr but before // deleting the .o, the cached result will be inconsistent. (.stderr is // the only file that is optional; any other file missing from the cache // will be detected by get_file_from_cache.) char *base = remove_extension(files[i]->fname); char *o_file = format("%s.o", base); // Don't subtract this extra deletion from the cache size; that // bookkeeping will be done when the loop reaches the .o file. If the // loop doesn't reach the .o file since the target limits have been // reached, the bookkeeping won't happen, but that small counter // discrepancy won't do much harm and it will correct itself in the next // cleanup. delete_file(o_file, 0, false); free(o_file); free(base); } delete_file(files[i]->fname, files[i]->size, true); cleaned = true; } return cleaned; } // Clean up one cache subdirectory. void clean_up_dir(struct conf *conf, const char *dir, double limit_multiple) { cc_log("Cleaning up cache directory %s", dir); // 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. double cache_size_float = round(conf->max_size * limit_multiple / 16); cache_size_threshold = (uint64_t)cache_size_float; double files_in_cache_float = round(conf->max_files * limit_multiple / 16); files_in_cache_threshold = (size_t)files_in_cache_float; num_files = 0; cache_size = 0; files_in_cache = 0; // Build a list of files. traverse(dir, traverse_fn); // Clean the cache. cc_log("Before cleanup: %.0f KiB, %.0f files", (double)cache_size / 1024, (double)files_in_cache); bool cleaned = sort_and_clean(); cc_log("After cleanup: %.0f KiB, %.0f files", (double)cache_size / 1024, (double)files_in_cache); if (cleaned) { cc_log("Cleaned up cache directory %s", dir); stats_add_cleanup(dir, 1); } stats_set_sizes(dir, files_in_cache, cache_size); // Free it up. for (unsigned 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; } // Clean up all cache subdirectories. void clean_up_all(struct conf *conf) { for (int i = 0; i <= 0xF; i++) { char *dname = format("%s/%1x", conf->cache_dir, i); clean_up_dir(conf, dname, 1.0); free(dname); } } // Traverse function for wiping files. static void wipe_fn(const char *fname, struct stat *st) { if (!S_ISREG(st->st_mode)) { return; } char *p = basename(fname); if (str_eq(p, "stats")) { free(p); return; } free(p); files_in_cache++; x_unlink(fname); } // Wipe one cache subdirectory. static void wipe_dir(const char *dir) { cc_log("Clearing out cache directory %s", dir); files_in_cache = 0; traverse(dir, wipe_fn); if (files_in_cache > 0) { cc_log("Cleared out cache directory %s", dir); stats_add_cleanup(dir, 1); } files_in_cache = 0; } // Wipe all cached files in all subdirectories. void wipe_all(struct conf *conf) { for (int i = 0; i <= 0xF; i++) { char *dname = format("%s/%1x", conf->cache_dir, i); wipe_dir(dname); free(dname); } // Fix the counters. clean_up_all(conf); }