Blame src/plugins/abrt-action-trim-files.c

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
}