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

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