/* Copyright (C) 2011 ABRT team Copyright (C) 2011 RedHat Inc 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 2 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 "libabrt.h" /* We remember N worst files and their sizes, not just one: * if DIR is deep, scanning it takes *a few seconds* even if it's in cache. * If we rescan it after each single file deletion, it can be VERY slow. * (I observed ~20 min long case). */ #define MAX_VICTIM_LIST_SIZE 128 struct name_and_size { off_t size; double weighted_size_and_age; char name[1]; }; /* Updates a list of files sorted by increasing weighted_size_and_age. */ static GList *insert_name_and_sizes(GList *list, const char *name, double wsa, off_t sz) { struct name_and_size *ns; unsigned list_len = 0; GList *cur = list; while (cur) { ns = cur->data; if (ns->weighted_size_and_age >= wsa) break; list_len++; cur = cur->next; } list_len += g_list_length(cur); if (cur != list || list_len < MAX_VICTIM_LIST_SIZE) { ns = xmalloc(sizeof(*ns) + strlen(name)); ns->weighted_size_and_age = wsa; ns->size = sz; strcpy(ns->name, name); list = g_list_insert_before(list, cur, ns); list_len++; if (list_len > MAX_VICTIM_LIST_SIZE) { free(list->data); list = g_list_delete_link(list, list); } } return list; } static double get_dir_size(const char *dirname, GList **pp_worst_file_list, GList *preserve_files_list ) { DIR *dp = opendir(dirname); if (!dp) return 0; /* "now" is used only if caller wants to know worst_file */ time_t now = pp_worst_file_list ? time(NULL) : 0; struct dirent *dent; double size = 0; while ((dent = readdir(dp)) != NULL) { if (dot_or_dotdot(dent->d_name)) continue; char *fullname = concat_path_file(dirname, dent->d_name); struct stat stats; if (lstat(fullname, &stats) != 0) goto next; if (S_ISDIR(stats.st_mode)) { double sz = get_dir_size(fullname, pp_worst_file_list, preserve_files_list); size += sz; } else if (S_ISREG(stats.st_mode) || S_ISLNK(stats.st_mode)) { double sz = stats.st_size; /* Account for filename and inode storage (approximately). * This also makes even zero-length files to have nonzero cost. */ sz += strlen(dent->d_name) + sizeof(stats); size += sz; if (pp_worst_file_list) { GList *cur = preserve_files_list; while (cur) { //log_warning("'%s' ? '%s'", fullname, *pp); if (strcmp(fullname, (char*)cur->data) == 0) goto next; cur = cur->next; } /* Calculate "weighted" size and age * w = sz_kbytes * age_mins */ sz /= 1024; long age = (now - stats.st_mtime) / 60; if (age > 1) sz *= age; *pp_worst_file_list = insert_name_and_sizes(*pp_worst_file_list, fullname, sz, stats.st_size); } } next: free(fullname); } closedir(dp); return size; } static const char *parse_size_pfx(double *size, const char *str) { errno = (isdigit(str[0]) ? 0 : ERANGE); char *end; *size = strtoull(str, &end, 10); if (end != str) { char c = (*end | 0x20); /* lowercasing */ if (c == 'k') end++, *size *= 1024; else if (c == 'm') end++, *size *= 1024*1024; else if (c == 'g') end++, *size *= 1024*1024*1024; else if (c == 't') end++, *size *= 1024.0*1024*1024*1024; } if (errno || end == str || *end != ':') perror_msg_and_die("Bad size prefix in '%s'", str); return end + 1; } static void delete_dirs(gpointer data, gpointer exclude_path) { double cap_size; const char *dir = parse_size_pfx(&cap_size, data); trim_problem_dirs(dir, cap_size, exclude_path); } static void delete_files(gpointer data, gpointer void_preserve_list) { double cap_size; const char *dir = parse_size_pfx(&cap_size, data); GList *preserve_files_list = void_preserve_list; unsigned count = 100; while (--count != 0) { GList *worst_file_list = NULL; double cur_size = get_dir_size(dir, &worst_file_list, preserve_files_list); if (cur_size <= cap_size || !worst_file_list) { list_free_with_free(worst_file_list); log_info("cur_size:%.0f cap_size:%.0f, no (more) trimming", cur_size, cap_size); break; } /* Invert the list, so that largest/oldest file is first */ worst_file_list = g_list_reverse(worst_file_list); /* And delete (some of) them */ while (worst_file_list && cur_size > cap_size) { struct name_and_size *ns = worst_file_list->data; log_notice("%s is %.0f bytes (more than %.0f MB), deleting '%s' (%llu bytes)", dir, cur_size, cap_size / (1024*1024), ns->name, (long long)ns->size); if (unlink(ns->name) != 0) perror_msg("Can't unlink '%s'", ns->name); else cur_size -= ns->size; free(ns); worst_file_list = g_list_delete_link(worst_file_list, worst_file_list); } } } int main(int argc, char **argv) { /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif abrt_init(argv); GList *dir_list = NULL; GList *file_list = NULL; char *preserve = NULL; /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "& [-v] [-d SIZE:DIR]... [-f SIZE:DIR]... [-p DIR] [FILE]...\n" "\n" "Deletes problem dirs (-d) or files (-f) in DIRs until they are smaller than SIZE.\n" "FILEs are preserved (never deleted)." ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, OPT_f = 1 << 2, OPT_p = 1 << 3, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_LIST('d' , NULL, &dir_list , "SIZE:DIR", _("Delete whole problem directories")), OPT_LIST('f' , NULL, &file_list, "SIZE:DIR", _("Delete files inside this directory")), OPT_STRING('p', NULL, &preserve, "DIR" , _("Preserve this directory")), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); argv += optind; if ((argv[0] && !file_list) || !(dir_list || file_list) ) { show_usage_and_die(program_usage_string, program_options); } /* We don't have children, so this is not needed: */ //export_abrt_envvars(/*set_pfx:*/ 0); /* Preserve not only files specified on command line, but, * if they are symlinks, preserve also the real files they point to: */ GList *preserve_files_list = NULL; while (*argv) { char *name = *argv++; /* Since we don't bother freeing preserve_files_list on exit, * we take a shortcut and insert name instead of xstrdup(name) * in the next line: */ preserve_files_list = g_list_prepend(preserve_files_list, name); char *rp = realpath(name, NULL); if (rp) { if (strcmp(rp, name) != 0) preserve_files_list = g_list_prepend(preserve_files_list, rp); else free(rp); } } /* Not really necessary, but helps to reduce confusion when debugging */ preserve_files_list = g_list_reverse(preserve_files_list); g_list_foreach(dir_list, delete_dirs, preserve); g_list_foreach(file_list, delete_files, preserve_files_list); return 0; }