3 * Copyright 2007, Intel Corporation
5 * This file is part of corewatcher.org
7 * This program file is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; version 2 of the License.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * You should have received a copy of the GNU General Public License
17 * along with this program in a file named COPYING; if not, write to the
18 * Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301 USA
23 * Arjan van de Ven <arjan@linux.intel.com>
24 * William Douglas <william.douglas@intel.com>
25 * Tim Pepper <timothy.c.pepper@linux.intel.com>
34 #include <asm/unistd.h>
35 #include <sys/types.h>
42 #include "corewatcher.h"
45 * processing "queue" loop's condition variable and associated
46 * lock. Note the queue is an implicit data structure consisting
47 * of the non-submitted core files in the filesystem, but the bool pq is
48 * used to mark whether the "queue" holds something to prevent the possible
49 * race where the condition is set before the thread is awaiting it and
53 static gboolean pq = FALSE;
56 static char *get_release(void)
62 file = fopen("/etc/os-release", "r");
67 if (getline(&line, &dummy, file) == -1)
69 if (strstr(line, "VERSION_ID=")) {
72 c = strchr(line, '\n');
75 c = strdup(&line[11]);
90 * Strip the directories from the path
93 char *strip_directories(char *fullpath)
95 char *dfile = NULL, *c1 = NULL, *c2 = NULL, *r = NULL;
102 dfile = strdup(fullpath);
106 c1 = strtok_r(dfile, delim, &saveptr);
109 c1 = strtok_r(NULL, delim, &saveptr);
121 * Move corefile from core_folder to processed_folder subdir.
122 * If this type of core has recently been seen, unlink this more recent
123 * example in order to rate limit submissions of extremely crashy
125 * Add extension and attempt to create directories if needed.
127 static int move_core(char *fullpath, char *extension)
129 char *corefilename = NULL, *newpath = NULL, *coreprefix = NULL;
133 struct dirent *entry = NULL;
138 corefilename = strip_directories(fullpath);
142 /* if the corefile's name minus any suffixes (such as .$PID) and
143 * minus two additional characters (ie: last two digits of
144 * timestamp assuming core_%e_%t) matches another core file in the
145 * processed_folder, simply unlink it instead of processing it for
146 * submission. TODO: consider a (configurable) time delta greater
147 * than which the cores must be separated, stat'ing the files, etc.
149 coreprefix = strdup(corefilename);
154 s = strstr(coreprefix, ".");
161 prefix_len = strlen(coreprefix);
162 if (prefix_len > 2) {
163 s = strndup(coreprefix, prefix_len - 2);
169 dir = opendir(processed_folder);
173 entry = readdir(dir);
174 if (!entry || !entry->d_name)
176 if (entry->d_name[0] == '.')
178 if (!strstr(entry->d_name, coreprefix))
180 fprintf(stderr, "+ ...ignoring/unlinking %s\n", fullpath);
187 if (asprintf(&newpath, "%s%s.%s", processed_folder, corefilename, extension) == -1)
192 rename(fullpath, newpath);
204 * Use GDB to extract backtrace information from corefile
206 static struct oops *extract_core(char *fullpath, char *appfile, char *reportname)
208 struct oops *oops = NULL;
210 char *command = NULL, *h1 = NULL, *c1 = NULL, *c2 = NULL, *line = NULL;
211 char *text = NULL, *coretime = NULL;
212 char *m1 = NULL, *m2 = NULL;
213 int bt_lines = 0, maps_lines = 0;
215 char *badchar = NULL;
216 char *release = get_release();
217 int parsing_maps = 0;
218 struct stat stat_buf;
220 ssize_t bytesread = 0;
222 fprintf(stderr, "+ extract_core() called for %s\n", fullpath);
224 if (asprintf(&command, "LANG=C gdb --batch -f %s %s -x /etc/corewatcher/gdb.command 2> /dev/null", appfile, fullpath) == -1)
227 file = popen(command, "r");
230 fprintf(stderr, "+ gdb failed for %s\n", fullpath);
232 if (stat(fullpath, &stat_buf) != -1) {
233 coretime = malloc(26);
235 ctime_r(&stat_buf.st_mtime, coretime);
243 release ? release : "Unknown",
244 coretime ? coretime : "Unknown");
252 while (file && !feof(file)) {
253 bytesread = getline(&line, &size, file);
259 /* try to figure out if we're onto the maps output yet */
260 if (strncmp(line, "From", 4) == 0) {
263 /* maps might not be present */
264 if (strncmp(line, "No shared libraries", 19) == 0) {
268 if (!parsing_maps) { /* parsing backtrace */
271 /* gdb's backtrace lines start with a line number */
275 /* gdb prints some initial info which may include the
276 * "#0" line of the backtrace, then prints the
277 * backtrace in its entirety, leading to a
278 * duplicate "#0" in our summary if we do do: */
279 if ((bt_lines == 1) && (strncmp(line, "#0 ", 3) == 0))
283 /* gdb outputs some 0x1a's which break XML */
285 badchar = memchr(line, 0x1a, bytesread);
292 if (asprintf(&c1, "%s %s", c2, line) == -1)
296 /* keep going even if asprintf has errors */
297 ret = asprintf(&c1, " %s", line);
299 } else { /* parsing maps */
304 if (asprintf(&m1, "%s %s", m2, line) == -1)
308 /* keep going even if asprintf has errors */
309 ret = asprintf(&m1, " %s", line);
318 ret = asprintf(&text,
325 c1 ? c1 : " Unknown",
326 m1 ? m1 : " Unknown");
336 oops = malloc(sizeof(struct oops));
341 memset(oops, 0, sizeof(struct oops));
343 oops->application = strdup(appfile);
345 oops->filename = strdup(fullpath);
346 oops->detail_filename = strdup(reportname);
351 * input filename has the form: core_$APP_$TIMESTAMP[.$PID]
352 * output filename has form of: $APP_$TIMESTAMP.txt
354 static char *make_report_filename(char *filename)
356 char *name = NULL, *dotpid = NULL, *stamp = NULL, *detail_filename = NULL;
361 if (!(stamp = strstr(filename, "_")))
367 if (!(name = strdup(stamp)))
370 /* strip trailing .PID if present */
371 dotpid = strstr(name, ".");
375 if ((asprintf(&detail_filename, "%s%s.txt", processed_folder, name)) == -1) {
381 return detail_filename;
385 * Write the backtrace from the core file into a text
386 * file named as $APP_$TIMESTAMP.txt
388 static void write_core_detail_file(struct oops *oops)
392 if (!oops->detail_filename)
395 fd = open(oops->detail_filename, O_WRONLY | O_CREAT | O_TRUNC, 0);
397 fprintf(stderr, "+ Error creating/opening %s for write\n", oops->detail_filename);
401 if(write(fd, oops->text, strlen(oops->text)) >= 0) {
402 fprintf(stderr, "+ Wrote %s\n", oops->detail_filename);
405 fprintf(stderr, "+ Error writing %s\n", oops->detail_filename);
406 unlink(oops->detail_filename);
412 * Common function for processing core
413 * files to generate oops structures and write *.txt
414 * if not already present
416 static struct oops *process_common(char *fullpath)
418 struct oops *oops = NULL;
419 char *appname = NULL, *appfile = NULL, *corefn = NULL, *reportname = NULL;
420 struct stat stat_buf;
422 corefn = strip_directories(fullpath);
424 fprintf(stderr, "+ No corefile? (%s)\n", fullpath);
428 appname = find_causingapp(fullpath);
435 * don't process rpm, gdb or corewatcher crashes,
436 * also skip apps which don't appear to be part of the OS
438 appfile = find_apppath(appname);
440 !strncmp(appname, "rpm", 3) ||
441 !strncmp(appname, "gdb", 3) ||
442 !strncmp(appname, "corewatcher", 11)) {
445 fprintf(stderr, "+ ...ignoring %s's %s\n", appname, fullpath);
446 move_core(fullpath, "skipped");
451 reportname = make_report_filename(corefn);
453 fprintf(stderr, "+ Couldn't make report name for %s\n", corefn);
459 if (stat(reportname, &stat_buf) == 0) {
463 * If the file already has trailing ".processed" and the txt file
464 * is a low quality report, then create a new report.
466 fprintf(stderr, "+ Report already exists in %s\n", reportname);
468 oops = malloc(sizeof(struct oops));
470 fprintf(stderr, "+ Malloc failed for struct oops\n");
475 memset(oops, 0, sizeof(struct oops));
478 oops->application = strdup(appfile);
479 oops->filename = strdup(fullpath);
480 oops->detail_filename = strdup(reportname);
484 oops->text = malloc(stat_buf.st_size + 1);
486 fprintf(stderr, "+ Malloc failed for oops text\n");
489 fd = open(oops->detail_filename, O_RDONLY);
491 fprintf(stderr, "+ Open failed for oops text\n");
494 ret = read(fd, oops->text, stat_buf.st_size);
496 if (ret != stat_buf.st_size) {
497 fprintf(stderr, "+ Read failed for oops text\n");
500 oops->text[stat_buf.st_size] = '\0';
504 oops = extract_core(fullpath, appfile, reportname);
505 write_core_detail_file(oops);
516 * Creates $APP_$TIMESTAMP.txt report summaries if they don't exist and
517 * adds the oops struct to the submit queue
519 static void *create_report(char *fullpath)
521 struct oops *oops = NULL;
525 fprintf(stderr, "+ Entered create_report() for %s\n", fullpath);
528 * If the file has trailing ".to-process", create a new report.
530 if (strstr(fullpath, ".to-process"))
533 oops = process_common(fullpath);
535 fprintf(stderr, "+ Did not generate struct oops for %s\n", fullpath);
540 procfn = replace_name(fullpath, ".to-process", ".processed");
542 fprintf(stderr, "+ Problems with filename manipulation for %s\n", fullpath);
543 goto clean_process_new;
545 ret = rename(fullpath, procfn);
547 fprintf(stderr, "+ Unable to move %s to %s\n", fullpath, procfn);
549 goto clean_process_new;
551 free(oops->filename);
552 oops->filename = strdup(procfn);
564 * scan once for core files in core_folder, moving any to the
565 * processed_folder with ".to-process" appended to their name
567 int scan_core_folder(void __unused *unused)
570 struct dirent *entry = NULL;
571 char *fullpath = NULL;
574 dir = opendir(core_folder);
576 fprintf(stderr, "+ Unable to open %s\n", core_folder);
579 fprintf(stderr, "+ Begin scanning %s...\n", core_folder);
581 entry = readdir(dir);
582 if (!entry || !entry->d_name)
584 if (entry->d_name[0] == '.')
586 if (strncmp(entry->d_name, "core_", 5))
589 /* matched core_#### */
590 if (asprintf(&fullpath, "%s%s", core_folder, entry->d_name) == -1) {
595 /* If one were to prompt the user before submitting, that
596 * might happen here. */
598 fprintf(stderr, "+ Looking at %s\n", fullpath);
600 ret = move_core(fullpath, "to-process");
610 fprintf(stderr, "+ Found %d files, setting pq_work condition\n", work);
611 g_mutex_lock(pq_mtx);
612 g_cond_signal(pq_work);
614 g_mutex_unlock(pq_mtx);
617 fprintf(stderr, "+ End scanning %s...\n", core_folder);
622 * scan for core_*.to-process and core_*.processed,
623 * insure a summary *.txt report exists, then queue it
625 void *scan_processed_folder(void __unused *unused)
628 struct dirent *entry = NULL;
629 char *fullpath = NULL;
630 struct oops *oops = NULL;
633 g_mutex_lock(pq_mtx);
635 fprintf(stderr, "+ Awaiting work in %s...\n", processed_folder);
636 g_cond_wait(pq_work, pq_mtx);
639 g_mutex_unlock(pq_mtx);
641 fprintf(stderr, "+ Begin scanning %s...\n", processed_folder);
643 dir = opendir(processed_folder);
645 fprintf(stderr, "+ Unable to open %s\n", processed_folder);
649 entry = readdir(dir);
650 if (!entry || !entry->d_name)
652 if (entry->d_name[0] == '.')
655 /* files with trailing ".to-process" or "processed" represent new work */
656 if (!strstr(entry->d_name, "process"))
659 if (asprintf(&fullpath, "%s%s", processed_folder, entry->d_name) == -1) {
664 fprintf(stderr, "+ Looking at %s\n", fullpath);
666 oops = create_report(fullpath);
669 fprintf(stderr, "+ Queued backtrace from %s\n", oops->detail_filename);
670 queue_backtrace(oops);
677 fprintf(stderr, "+ End scanning %s...\n", processed_folder);
683 /* do everything, called from timer event */
684 int scan_folders(void __unused *unused)
686 scan_core_folder(NULL);
688 g_mutex_lock(pq_mtx);
689 g_cond_signal(pq_work);
691 g_mutex_unlock(pq_mtx);