Blame src/plugins/abrt-watch-log.c

Packit Service 8a8a03
/*
Packit Service 8a8a03
    Copyright (C) 2012  ABRT Team
Packit Service 8a8a03
    Copyright (C) 2012  Red Hat, Inc.
Packit Service 8a8a03
Packit Service 8a8a03
    This program is free software; you can redistribute it and/or modify
Packit Service 8a8a03
    it under the terms of the GNU General Public License as published by
Packit Service 8a8a03
    the Free Software Foundation; either version 2 of the License, or
Packit Service 8a8a03
    (at your option) any later version.
Packit Service 8a8a03
Packit Service 8a8a03
    This program is distributed in the hope that it will be useful,
Packit Service 8a8a03
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 8a8a03
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 8a8a03
    GNU General Public License for more details.
Packit Service 8a8a03
Packit Service 8a8a03
    You should have received a copy of the GNU General Public License along
Packit Service 8a8a03
    with this program; if not, write to the Free Software Foundation, Inc.,
Packit Service 8a8a03
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit Service 8a8a03
 */
Packit Service 8a8a03
#include <sys/inotify.h>
Packit Service 8a8a03
#include "libabrt.h"
Packit Service 8a8a03
Packit Service 8a8a03
#define MAX_SCAN_BLOCK  (4*1024*1024)
Packit Service 8a8a03
#define READ_AHEAD          (10*1024)
Packit Service 8a8a03
Packit Service 8a8a03
static unsigned page_size;
Packit Service 8a8a03
Packit Service 8a8a03
static bool memstr(void *buf, unsigned size, const char *str)
Packit Service 8a8a03
{
Packit Service 8a8a03
    int len = strlen(str);
Packit Service 8a8a03
    while ((int)size >= len)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        //log_warning("LOOKING FOR:'%s'", str);
Packit Service 8a8a03
        char *first = memchr(buf, (unsigned char)str[0], size - len + 1);
Packit Service 8a8a03
        if (!first)
Packit Service 8a8a03
            break;
Packit Service 8a8a03
        //log_warning("FOUND:'%.66s'", first);
Packit Service 8a8a03
        first++;
Packit Service 8a8a03
        if (len <= 1 || strncmp(first, str + 1, len - 1) == 0)
Packit Service 8a8a03
            return true;
Packit Service 8a8a03
        size -= (first - (char*)buf);
Packit Service 8a8a03
        //log_warning("SKIP TO:'%.66s' %d chars", first, (int)(first - (char*)buf));
Packit Service 8a8a03
        buf = first;
Packit Service 8a8a03
    }
Packit Service 8a8a03
    return false;
Packit Service 8a8a03
}
Packit Service 8a8a03
Packit Service 8a8a03
static void run_scanner_prog(int fd, struct stat *statbuf, GList *match_list, char **prog)
Packit Service 8a8a03
{
Packit Service 8a8a03
    /* fstat(fd, &statbuf) was just done by caller */
Packit Service 8a8a03
Packit Service 8a8a03
    off_t cur_pos = lseek(fd, 0, SEEK_CUR);
Packit Service 8a8a03
    if (statbuf->st_size <= cur_pos)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        /* If file was truncated, treat it as a new file.
Packit Service 8a8a03
         * (changing inode# causes caller to think that file was closed or renamed)
Packit Service 8a8a03
         */
Packit Service 8a8a03
        if (statbuf->st_size < cur_pos)
Packit Service 8a8a03
            statbuf->st_ino++;
Packit Service 8a8a03
        return; /* we are at EOF, nothing to do */
Packit Service 8a8a03
    }
Packit Service 8a8a03
Packit Service 8a8a03
    log_info("File grew by %llu bytes, from %llu to %llu",
Packit Service 8a8a03
        (long long)(statbuf->st_size - cur_pos),
Packit Service 8a8a03
        (long long)(cur_pos),
Packit Service 8a8a03
        (long long)(statbuf->st_size));
Packit Service 8a8a03
Packit Service 8a8a03
    if (match_list && (statbuf->st_size - cur_pos) < MAX_SCAN_BLOCK)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        size_t length = statbuf->st_size - cur_pos;
Packit Service 8a8a03
Packit Service 8a8a03
        off_t mapofs = cur_pos & ~(off_t)(page_size - 1);
Packit Service 8a8a03
        size_t maplen = statbuf->st_size - mapofs;
Packit Service 8a8a03
        void *map = mmap(NULL, maplen, PROT_READ, MAP_SHARED, fd, mapofs);
Packit Service 8a8a03
Packit Service 8a8a03
        if (map != MAP_FAILED)
Packit Service 8a8a03
        {
Packit Service 8a8a03
            char *start = (char*)map + (cur_pos & (page_size - 1));
Packit Service 8a8a03
            for (GList *l = match_list; l; l = l->next)
Packit Service 8a8a03
            {
Packit Service 8a8a03
                log_debug("Searching for '%s' in '%.*s'",
Packit Service 8a8a03
                                (char*)l->data,
Packit Service 8a8a03
                                length > 20 ? 20 : (int)length, start
Packit Service 8a8a03
                );
Packit Service 8a8a03
                if (memstr(start, length, (char*)l->data))
Packit Service 8a8a03
                {
Packit Service 8a8a03
                    log_debug("FOUND:'%s'", (char*)l->data);
Packit Service 8a8a03
                    goto found;
Packit Service 8a8a03
                }
Packit Service 8a8a03
            }
Packit Service 8a8a03
            /* None of the strings are found */
Packit Service 8a8a03
            log_debug("NOT FOUND");
Packit Service 8a8a03
            munmap(map, maplen);
Packit Service 8a8a03
            lseek(fd, statbuf->st_size, SEEK_SET);
Packit Service 8a8a03
            return;
Packit Service 8a8a03
 found: ;
Packit Service 8a8a03
            munmap(map, maplen);
Packit Service 8a8a03
        }
Packit Service 8a8a03
    }
Packit Service 8a8a03
Packit Service 8a8a03
    fflush(NULL); /* paranoia */
Packit Service 8a8a03
    pid_t pid = vfork();
Packit Service 8a8a03
    if (pid < 0)
Packit Service 8a8a03
        perror_msg_and_die("vfork");
Packit Service 8a8a03
    if (pid == 0)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        xmove_fd(fd, STDIN_FILENO);
Packit Service 8a8a03
        log_debug("Execing '%s'", prog[0]);
Packit Service 8a8a03
        execvp(prog[0], prog);
Packit Service 8a8a03
        perror_msg_and_die("Can't execute '%s'", prog[0]);
Packit Service 8a8a03
    }
Packit Service 8a8a03
Packit Service 8a8a03
    safe_waitpid(pid, NULL, 0);
Packit Service 8a8a03
Packit Service 8a8a03
    /* Check fd's position, and move to end if it wasn't advanced.
Packit Service 8a8a03
     * This means that child failed to read its stdin.
Packit Service 8a8a03
     * This is not supposed to happen, so warn about it.
Packit Service 8a8a03
     */
Packit Service 8a8a03
    if (lseek(fd, 0, SEEK_CUR) <= cur_pos)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        log_warning("Warning, '%s' did not process its input", prog[0]);
Packit Service 8a8a03
        lseek(fd, statbuf->st_size, SEEK_SET);
Packit Service 8a8a03
    }
Packit Service 8a8a03
}
Packit Service 8a8a03
Packit Service 8a8a03
int main(int argc, char **argv)
Packit Service 8a8a03
{
Packit Service 8a8a03
    /* I18n */
Packit Service 8a8a03
    setlocale(LC_ALL, "");
Packit Service 8a8a03
#if ENABLE_NLS
Packit Service 8a8a03
    bindtextdomain(PACKAGE, LOCALEDIR);
Packit Service 8a8a03
    textdomain(PACKAGE);
Packit Service 8a8a03
#endif
Packit Service 8a8a03
Packit Service 8a8a03
    abrt_init(argv);
Packit Service 8a8a03
Packit Service 8a8a03
    page_size = sysconf(_SC_PAGE_SIZE);
Packit Service 8a8a03
Packit Service 8a8a03
    GList *match_list = NULL;
Packit Service 8a8a03
Packit Service 8a8a03
    /* Can't keep these strings/structs static: _() doesn't support that */
Packit Service 8a8a03
    const char *program_usage_string = _(
Packit Service 8a8a03
        "& [-vs] [-F STR]... FILE PROG [ARGS]\n"
Packit Service 8a8a03
        "\n"
Packit Service 8a8a03
        "Watch log file FILE, run PROG when it grows or is replaced"
Packit Service 8a8a03
    );
Packit Service 8a8a03
    enum {
Packit Service 8a8a03
        OPT_v = 1 << 0,
Packit Service 8a8a03
        OPT_s = 1 << 1,
Packit Service 8a8a03
    };
Packit Service 8a8a03
    /* Keep enum above and order of options below in sync! */
Packit Service 8a8a03
    struct options program_options[] = {
Packit Service 8a8a03
        OPT__VERBOSE(&g_verbose),
Packit Service 8a8a03
        OPT_BOOL('s', NULL, NULL              , _("Log to syslog")),
Packit Service 8a8a03
        OPT_LIST('F', NULL, &match_list, "STR", _("Don't run PROG if STRs aren't found")),
Packit Service 8a8a03
        OPT_END()
Packit Service 8a8a03
    };
Packit Service 8a8a03
    unsigned opts = parse_opts(argc, argv, program_options, program_usage_string);
Packit Service 8a8a03
Packit Service 8a8a03
    export_abrt_envvars(0);
Packit Service 8a8a03
Packit Service 8a8a03
    msg_prefix = g_progname;
Packit Service 8a8a03
    if ((opts & OPT_s) || getenv("ABRT_SYSLOG"))
Packit Service 8a8a03
    {
Packit Service 8a8a03
        logmode = LOGMODE_JOURNAL;
Packit Service 8a8a03
    }
Packit Service 8a8a03
Packit Service 8a8a03
    argv += optind;
Packit Service 8a8a03
    if (!argv[0] || !argv[1])
Packit Service 8a8a03
        show_usage_and_die(program_usage_string, program_options);
Packit Service 8a8a03
Packit Service 8a8a03
    /* We want to support -F "`echo foo; echo bar`" -
Packit Service 8a8a03
     * need to split strings by newline, and be careful about
Packit Service 8a8a03
     * possible last empty string: "foo\nbar\n" = "foo", "bar",
Packit Service 8a8a03
     * NOT "foo", "bar", ""!
Packit Service 8a8a03
     */
Packit Service 8a8a03
    for (GList *l = match_list; l; l = l->next)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        char *eol = strchr((char*)l->data, '\n');
Packit Service 8a8a03
        if (!eol)
Packit Service 8a8a03
            continue;
Packit Service 8a8a03
        *eol++ = '\0';
Packit Service 8a8a03
        if (!*eol)
Packit Service 8a8a03
            continue;
Packit Service 8a8a03
        l = g_list_append(l, eol); /* in fact, always returns unchanged l */
Packit Service 8a8a03
    }
Packit Service 8a8a03
Packit Service 8a8a03
    const char *filename = *argv++;
Packit Service 8a8a03
Packit Service 8a8a03
    int inotify_fd = inotify_init();
Packit Service 8a8a03
    if (inotify_fd == -1)
Packit Service 8a8a03
        perror_msg_and_die("inotify_init failed");
Packit Service 8a8a03
    close_on_exec_on(inotify_fd);
Packit Service 8a8a03
Packit Service 8a8a03
    struct stat statbuf;
Packit Service 8a8a03
    int file_fd = -1;
Packit Service 8a8a03
    int wd = -1;
Packit Service 8a8a03
Packit Service 8a8a03
    while (1)
Packit Service 8a8a03
    {
Packit Service 8a8a03
        /* If file is already opened, scan it from current pos */
Packit Service 8a8a03
        if (file_fd >= 0)
Packit Service 8a8a03
        {
Packit Service 8a8a03
            memset(&statbuf, 0, sizeof(statbuf));
Packit Service 8a8a03
            if (fstat(file_fd, &statbuf) != 0)
Packit Service 8a8a03
                goto close_fd;
Packit Service 8a8a03
            run_scanner_prog(file_fd, &statbuf, match_list, argv);
Packit Service 8a8a03
Packit Service 8a8a03
            /* Was file deleted or replaced? */
Packit Service 8a8a03
            ino_t fd_ino = statbuf.st_ino;
Packit Service 8a8a03
            if (stat(filename, &statbuf) != 0 || statbuf.st_ino != fd_ino) /* yes */
Packit Service 8a8a03
            {
Packit Service 8a8a03
                log_info("Inode# changed, closing fd");
Packit Service 8a8a03
 close_fd:
Packit Service 8a8a03
                close(file_fd);
Packit Service 8a8a03
                if (wd >= 0)
Packit Service 8a8a03
                    inotify_rm_watch(inotify_fd, wd);
Packit Service 8a8a03
                file_fd = -1;
Packit Service 8a8a03
                wd = -1;
Packit Service 8a8a03
            }
Packit Service 8a8a03
        }
Packit Service 8a8a03
Packit Service 8a8a03
        /* If file isn't opened, try to open it and scan */
Packit Service 8a8a03
        if (file_fd < 0)
Packit Service 8a8a03
        {
Packit Service 8a8a03
            file_fd = open(filename, O_RDONLY);
Packit Service 8a8a03
            if (file_fd >= 0)
Packit Service 8a8a03
            {
Packit Service 8a8a03
                log_info("Opened '%s'", filename);
Packit Service 8a8a03
                /* For -w case, if we don't have inotify watch yet, open one */
Packit Service 8a8a03
                if (wd < 0)
Packit Service 8a8a03
                {
Packit Service 8a8a03
                    wd = inotify_add_watch(inotify_fd, filename, IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF);
Packit Service 8a8a03
                    if (wd < 0)
Packit Service 8a8a03
                        perror_msg("inotify_add_watch failed on '%s'", filename);
Packit Service 8a8a03
                    else
Packit Service 8a8a03
                        log_info("Added inotify watch for '%s'", filename);
Packit Service 8a8a03
                }
Packit Service 8a8a03
                if (fstat(file_fd, &statbuf) == 0)
Packit Service 8a8a03
                {
Packit Service 8a8a03
                    /* If file is large, skip the beginning.
Packit Service 8a8a03
                     * IOW: ignore old log messages because they are unlikely
Packit Service 8a8a03
                     * to have sufficiently recent data to be useful.
Packit Service 8a8a03
                     */
Packit Service 8a8a03
                    if (statbuf.st_size > (MAX_SCAN_BLOCK - READ_AHEAD))
Packit Service 8a8a03
                        lseek(file_fd, statbuf.st_size - (MAX_SCAN_BLOCK - READ_AHEAD), SEEK_SET);
Packit Service 8a8a03
                    /* Note that statbuf is filled by fstat by now,
Packit Service 8a8a03
                     * run_scanner_prog needs that
Packit Service 8a8a03
                     */
Packit Service 8a8a03
                    run_scanner_prog(file_fd, &statbuf, match_list, argv);
Packit Service 8a8a03
                }
Packit Service 8a8a03
            }
Packit Service 8a8a03
        }
Packit Service 8a8a03
Packit Service 8a8a03
        /* Even if log file grows all the time, say, a new line every 5 ms,
Packit Service 8a8a03
         * we don't want to scan it all the time. Sleep a bit and let it grow
Packit Service 8a8a03
         * in bigger increments.
Packit Service 8a8a03
         * Sleep longer if file does not exist.
Packit Service 8a8a03
         */
Packit Service 8a8a03
        sleep(file_fd >= 0 ? 1 : 59);
Packit Service 8a8a03
Packit Service 8a8a03
        /* Now wait for it to change, be moved or deleted */
Packit Service 8a8a03
        if (wd >= 0)
Packit Service 8a8a03
        {
Packit Service 8a8a03
            char buf[4096];
Packit Service 8a8a03
            log_debug("Waiting for '%s' to change", filename);
Packit Service 8a8a03
            /* We block here: */
Packit Service 8a8a03
            int len = read(inotify_fd, buf, sizeof(buf));
Packit Service 8a8a03
            if (len < 0 && errno != EINTR) /* I saw EINTR here on strace attach */
Packit Service 8a8a03
                perror_msg("Error reading inotify fd");
Packit Service 8a8a03
            /* we don't actually check what happened to file -
Packit Service 8a8a03
             * the code will handle all possibilities.
Packit Service 8a8a03
             */
Packit Service 8a8a03
            log_debug("Change in '%s' detected", filename);
Packit Service 8a8a03
            /* Let them finish writing to the log file. otherwise
Packit Service 8a8a03
             * we may end up trying to analyze partial oops.
Packit Service 8a8a03
             */
Packit Service 8a8a03
            sleep(1);
Packit Service 8a8a03
        }
Packit Service 8a8a03
Packit Service 8a8a03
    } /* while (1) */
Packit Service 8a8a03
Packit Service 8a8a03
    return 0;
Packit Service 8a8a03
}