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