|
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 |
}
|