Quote filenames
[platform/upstream/corewatcher.git] / src / coredump.c
1 #define _GNU_SOURCE
2 /*
3  * Copyright 2007, Intel Corporation
4  *
5  * This file is part of corewatcher.org
6  *
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.
10  *
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
14  * for more details.
15  *
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
21  *
22  * Authors:
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>
26  */
27
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <assert.h>
33 #include <fcntl.h>
34 #include <asm/unistd.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/statvfs.h>
39 #include <syslog.h>
40 #include <dirent.h>
41 #include <glib.h>
42 #include <errno.h>
43
44 #include "corewatcher.h"
45
46 /*
47  * processing "queue" loop's condition variable and associated
48  * lock.  Note the queue is an implicit data structure consisting
49  * of the non-submitted core files in the filesystem, but the bool pq is
50  * used to mark whether the "queue" holds something to prevent the possible
51  * race where the condition is set before the thread is awaiting it and
52  * thus is not woken.
53  */
54 GMutex *pq_mtx;
55 static gboolean pq = FALSE;
56 GCond *pq_work;
57
58 static int diskfree = 100;
59
60 static char *get_release(void)
61 {
62         FILE *file = NULL;
63         char *line = NULL;
64         size_t dummy = 0;
65
66         file = fopen("/etc/os-release", "r");
67         if (!file)
68                 return NULL;
69
70         while (!feof(file)) {
71                 if (getline(&line, &dummy, file) == -1)
72                         break;
73                 if (strstr(line, "VERSION_ID=")) {
74                         char *c = NULL;
75
76                         c = strchr(line, '\n');
77                         if (c) {
78                                 *c = 0;
79                                 c = strdup(&line[11]);
80                                 fclose(file);
81                                 free(line);
82                                 return c;
83                         }
84                 }
85         }
86
87         fclose(file);
88         free(line);
89
90         return NULL;
91 }
92
93 /*
94  * Strip the directories from the path
95  * given by fullname
96  */
97 char *strip_directories(char *fullpath)
98 {
99         char *dfile = NULL, *c1 = NULL, *c2 = NULL, *r = NULL;
100         char delim[] = "/";
101         char *saveptr;
102
103         if (!fullpath)
104                 return NULL;
105
106         dfile = strdup(fullpath);
107         if (!dfile)
108                 return NULL;
109
110         c1 = strtok_r(dfile, delim, &saveptr);
111         while(c1) {
112                 c2 = c1;
113                 c1 = strtok_r(NULL, delim, &saveptr);
114         }
115
116         if (c2)
117                 r = strdup(c2);
118
119         free(dfile);
120
121         return r;
122 }
123
124 /*
125  * Move corefile from core_folder to processed_folder subdir.
126  * If this type of core has recently been seen, unlink this more recent
127  * example in order to rate limit submissions of extremely crashy
128  * applications.
129  * Add extension and attempt to create directories if needed.
130  */
131 static int move_core(char *fullpath, char *extension)
132 {
133         char *corefilename = NULL, *newpath = NULL, *coreprefix = NULL;
134         char *s = NULL;
135         size_t prefix_len;
136         DIR *dir = NULL;
137         struct dirent *entry = NULL;
138
139         if (!fullpath)
140                 return -1;
141
142         corefilename = strip_directories(fullpath);
143         if (!corefilename)
144                 return -ENOMEM;
145
146         /* if the corefile's name minus any suffixes (such as .$PID) and
147          * minus two additional characters (ie: last two digits of
148          * timestamp assuming core_%e_%t) matches another core file in the
149          * processed_folder, simply unlink it instead of processing it for
150          * submission.  TODO: consider a (configurable) time delta greater
151          * than which the cores must be separated, stat'ing the files, etc.
152          */
153         coreprefix = strdup(corefilename);
154         if (!coreprefix) {
155                 free(corefilename);
156                 return -ENOMEM;
157         }
158         s = strstr(coreprefix, ".");
159         if (!s) {
160                 free(coreprefix);
161                 free(corefilename);
162                 return -1;
163         }
164         *s = '\0';
165         prefix_len = strlen(coreprefix);
166         if (prefix_len > 2) {
167                 s = strndup(coreprefix, prefix_len - 2);
168                 free(coreprefix);
169                 coreprefix = s;
170         } else {
171                 goto error;
172         }
173         dir = opendir(processed_folder);
174         if (!dir)
175                 goto error;
176         while(1) {
177                 entry = readdir(dir);
178                 if (!entry || !entry->d_name)
179                         break;
180                 if (entry->d_name[0] == '.')
181                         continue;
182                 if (!strstr(entry->d_name, coreprefix))
183                         continue;
184                 fprintf(stderr, "+ ...ignoring/unlinking %s\n", fullpath);
185                 unlink(fullpath);
186                 closedir(dir);
187                 goto error;
188         }
189         closedir(dir);
190
191         if (asprintf(&newpath, "%s%s.%s", processed_folder, corefilename, extension) == -1)
192                 goto error;
193
194         free(coreprefix);
195         free(corefilename);
196         rename(fullpath, newpath);
197         free(newpath);
198         return 0;
199
200 error:
201         free(coreprefix);
202         free(corefilename);
203         return -1;
204 }
205
206
207 /*
208  * Use GDB to extract backtrace information from corefile
209  */
210 static struct oops *extract_core(char *fullpath, char *appfile, char *reportname)
211 {
212         struct oops *oops = NULL;
213         int ret = 0;
214         char *command = NULL, *h1 = NULL, *c1 = NULL, *c2 = NULL, *line = NULL;
215         char *text = NULL, *coretime = NULL;
216         char *m1 = NULL, *m2 = NULL;
217         int bt_lines = 0, maps_lines = 0;
218         FILE *file = NULL;
219         char *badchar = NULL;
220         char *release = get_release();
221         int parsing_maps = 0;
222         struct stat stat_buf;
223         size_t size = 0;
224         ssize_t bytesread = 0;
225
226         fprintf(stderr, "+ extract_core() called for %s\n", fullpath);
227
228         if (asprintf(&command, "LANG=C gdb --batch -f '%s' '%s' -x /etc/corewatcher/gdb.command 2> /dev/null", appfile, fullpath) == -1)
229                 return NULL;
230
231         file = popen(command, "r");
232         free(command);
233         if (!file)
234                 fprintf(stderr, "+ gdb failed for %s\n", fullpath);
235
236         if (stat(fullpath, &stat_buf) != -1) {
237                 coretime = malloc(26);
238                 if (coretime)
239                         ctime_r(&stat_buf.st_mtime, coretime);
240         }
241
242         ret = asprintf(&h1,
243                        "cmdline: %s\n"
244                        "release: %s\n"
245                        "time: %s",
246                        appfile,
247                        release ? release : "Unknown",
248                        coretime ? coretime : "Unknown");
249         if (release)
250                 free(release);
251         if (coretime)
252                 free(coretime);
253         if (ret == -1)
254                 return NULL;
255
256         while (file && !feof(file)) {
257                 bytesread = getline(&line, &size, file);
258                 if (!size)
259                         break;
260                 if (bytesread == -1)
261                         break;
262
263                 /* try to figure out if we're onto the maps output yet */
264                 if (strncmp(line, "From", 4) == 0) {
265                         parsing_maps = 1;
266                 }
267                 /* maps might not be present */
268                 if (strncmp(line, "No shared libraries", 19) == 0) {
269                         break;
270                 }
271
272                 if (!parsing_maps) { /* parsing backtrace */
273                         c2 = c1;
274
275                         /* gdb's backtrace lines start with a line number */
276                         if (line[0] != '#')
277                                 continue;
278
279                         /* gdb prints some initial info which may include the
280                          * "#0" line of the backtrace, then prints the
281                          * backtrace in its entirety, leading to a
282                          * duplicate "#0" in our summary if we do do: */
283                         if ((bt_lines == 1) && (strncmp(line, "#0 ", 3) == 0))
284                                 continue;
285                         bt_lines++;
286
287                         /* gdb outputs some 0x1a's which break XML */
288                         do {
289                                 badchar = memchr(line, 0x1a, bytesread);
290                                 if (badchar)
291                                         *badchar = ' ';
292                         } while (badchar);
293
294                         if (c1) {
295                                 c1 = NULL;
296                                 if (asprintf(&c1, "%s        %s", c2, line) == -1)
297                                         continue;
298                                 free(c2);
299                         } else {
300                                 /* keep going even if asprintf has errors */
301                                 ret = asprintf(&c1, "        %s", line);
302                         }
303                 } else { /* parsing maps */
304                         m2 = m1;
305                         maps_lines++;
306                         if (m1) {
307                                 m1 = NULL;
308                                 if (asprintf(&m1, "%s        %s", m2, line) == -1)
309                                         continue;
310                                 free(m2);
311                         } else {
312                                 /* keep going even if asprintf has errors */
313                                 ret = asprintf(&m1, "        %s", line);
314                         }
315                 }
316         }
317         if (line)
318                 free(line);
319         if (file)
320                 pclose(file);
321
322         ret = asprintf(&text,
323                        "%s"
324                        "backtrace: |\n"
325                        "%s"
326                        "maps: |\n"
327                        "%s",
328                        h1,
329                        c1 ? c1 : "        Unknown\n",
330                        m1 ? m1 : "        Unknown\n");
331         free(h1);
332         if (c1)
333                 free(c1);
334         if (m1)
335                 free(m1);
336
337         if (ret == -1)
338                 return NULL;
339
340         oops = malloc(sizeof(struct oops));
341         if (!oops) {
342                 free(text);
343                 return NULL;
344         }
345         memset(oops, 0, sizeof(struct oops));
346         oops->next = NULL;
347         oops->application = strdup(appfile);
348         oops->text = text;
349         oops->filename = strdup(fullpath);
350         oops->detail_filename = strdup(reportname);
351         return oops;
352 }
353
354 /*
355  * input filename has the form: core_$APP_$TIMESTAMP[.$PID]
356  * output filename has form of: $APP_$TIMESTAMP.txt
357  */
358 static char *make_report_filename(char *filename)
359 {
360         char *name = NULL, *dotpid = NULL, *stamp = NULL, *detail_filename = NULL;
361
362         if (!filename)
363                 return NULL;
364
365         if (!(stamp = strstr(filename, "_")))
366                 return NULL;
367
368         if (!(++stamp))
369                 return NULL;
370
371         if (!(name = strdup(stamp)))
372                 return NULL;
373
374         /* strip trailing .PID if present */
375         dotpid = strstr(name, ".");
376         if (dotpid)
377                 *dotpid = '\0';
378
379         if ((asprintf(&detail_filename, "%s%s.txt", processed_folder, name)) == -1) {
380                 free(name);
381                 return NULL;
382         }
383         free(name);
384
385         return detail_filename;
386 }
387
388 /*
389  * Write the backtrace from the core file into a text
390  * file named as $APP_$TIMESTAMP.txt
391  */
392 static void write_core_detail_file(struct oops *oops)
393 {
394         int fd = 0;
395
396         if (!oops->detail_filename)
397                 return;
398
399         fd = open(oops->detail_filename, O_WRONLY | O_CREAT | O_TRUNC, 0);
400         if (fd == -1) {
401                 fprintf(stderr, "+ Error creating/opening %s for write\n", oops->detail_filename);
402                 return;
403         }
404
405         if(write(fd, oops->text, strlen(oops->text)) >= 0) {
406                 fprintf(stderr, "+ Wrote %s\n", oops->detail_filename);
407                 fchmod(fd, 0644);
408         } else {
409                 fprintf(stderr, "+ Error writing %s\n", oops->detail_filename);
410                 unlink(oops->detail_filename);
411         }
412         close(fd);
413 }
414
415 /*
416  * Common function for processing core
417  * files to generate oops structures and write *.txt
418  * if not already present
419  */
420 static struct oops *process_common(char *fullpath)
421 {
422         struct oops *oops = NULL;
423         char *appname = NULL, *appfile = NULL, *corefn = NULL, *reportname = NULL;
424         struct stat stat_buf;
425
426         corefn = strip_directories(fullpath);
427         if (!corefn) {
428                 fprintf(stderr, "+ No corefile? (%s)\n", fullpath);
429                 return NULL;
430         }
431
432         appname = find_causingapp(fullpath);
433         if (!appname) {
434                 free(corefn);
435                 free(reportname);
436                 return NULL;
437         }
438         /*
439          * don't process rpm, gdb or corewatcher crashes,
440          * also skip apps which don't appear to be part of the OS
441          */
442         appfile = find_apppath(appname);
443         if (!appfile ||
444             !strncmp(appname, "rpm", 3) ||
445             !strncmp(appname, "gdb", 3) ||
446             !strncmp(appname, "corewatcher", 11)) {
447                 free(corefn);
448                 free(appname);
449                 fprintf(stderr, "+ ...ignoring %s's %s\n", appname, fullpath);
450                 move_core(fullpath, "skipped");
451                 return NULL;
452         }
453         free(appname);
454
455         reportname = make_report_filename(corefn);
456         if (!reportname) {
457                 fprintf(stderr, "+ Couldn't make report name for %s\n", corefn);
458                 free(corefn);
459                 free(appfile);
460                 return NULL;
461         }
462         free(corefn);
463         if (stat(reportname, &stat_buf) == 0) {
464                 int fd, ret;
465                 /*
466                  * TODO:
467                  *   If the file already has trailing ".processed" and the txt file
468                  *   is a low quality report, then create a new report.
469                  */
470                 fprintf(stderr, "+ Report already exists in %s\n", reportname);
471
472                 oops = malloc(sizeof(struct oops));
473                 if (!oops) {
474                         fprintf(stderr, "+ Malloc failed for struct oops\n");
475                         free(reportname);
476                         free(appfile);
477                         return NULL;
478                 }
479                 memset(oops, 0, sizeof(struct oops));
480
481                 oops->next = NULL;
482                 oops->application = strdup(appfile);
483                 oops->filename = strdup(fullpath);
484                 oops->detail_filename = strdup(reportname);
485                 free(reportname);
486                 free(appfile);
487
488                 oops->text = malloc(stat_buf.st_size + 1);
489                 if (!oops->text) {
490                         fprintf(stderr, "+ Malloc failed for oops text\n");
491                         goto err;
492                 }
493                 fd = open(oops->detail_filename, O_RDONLY);
494                 if (fd == -1) {
495                         fprintf(stderr, "+ Open failed for oops text\n");
496                         goto err;
497                 }
498                 ret = read(fd, oops->text, stat_buf.st_size);
499                 close(fd);
500                 if (ret != stat_buf.st_size) {
501                         fprintf(stderr, "+ Read failed for oops text\n");
502                         goto err;
503                 }
504                 oops->text[stat_buf.st_size] = '\0';
505                 return oops;
506         }
507
508         oops = extract_core(fullpath, appfile, reportname);
509         write_core_detail_file(oops);
510         free(reportname);
511         free(appfile);
512         return oops;
513 err:
514         FREE_OOPS(oops);
515         return NULL;
516 }
517
518
519 /*
520  * Creates $APP_$TIMESTAMP.txt report summaries if they don't exist and
521  * adds the oops struct to the submit queue
522  */
523 static void *create_report(char *fullpath)
524 {
525         struct oops *oops = NULL;
526         char *procfn = NULL;
527         int new = 0, ret;
528
529         fprintf(stderr, "+ Entered create_report() for %s\n", fullpath);
530
531         /*
532          * If the file has trailing ".to-process", create a new report.
533          */
534         if (strstr(fullpath, ".to-process"))
535                 new = 1;
536
537         oops = process_common(fullpath);
538         if (!oops) {
539                 fprintf(stderr, "+ Did not generate struct oops for %s\n", fullpath);
540                 return NULL;
541         }
542
543         if (new) {
544                 procfn = replace_name(fullpath, ".to-process", ".processed");
545                 if (!procfn) {
546                         fprintf(stderr, "+ Problems with filename manipulation for %s\n", fullpath);
547                         goto clean_process_new;
548                 }
549                 ret = rename(fullpath, procfn);
550                 if (ret) {
551                         fprintf(stderr, "+ Unable to move %s to %s\n", fullpath, procfn);
552                         free(procfn);
553                         goto clean_process_new;
554                 }
555                 free(oops->filename);
556                 oops->filename = strdup(procfn);
557                 free(procfn);
558         }
559
560         return oops;
561
562 clean_process_new:
563         FREE_OOPS(oops);
564         return NULL;
565 }
566
567 /*
568  * scan once for core files in core_folder, moving any to the
569  * processed_folder with ".to-process" appended to their name
570  */
571 int scan_core_folder(void __unused *unused)
572 {
573         DIR *dir = NULL;
574         struct dirent *entry = NULL;
575         char *fullpath = NULL;
576         int ret, work = 0;
577
578         dir = opendir(core_folder);
579         if (!dir) {
580                 fprintf(stderr, "+ Unable to open %s\n", core_folder);
581                 return -1;
582         }
583         fprintf(stderr, "+ Begin scanning %s...\n", core_folder);
584         while(1) {
585                 entry = readdir(dir);
586                 if (!entry || !entry->d_name)
587                         break;
588                 if (entry->d_name[0] == '.')
589                         continue;
590                 if (strncmp(entry->d_name, "core_", 5))
591                         continue;
592
593                 /* matched core_#### */
594                 if (asprintf(&fullpath, "%s%s", core_folder, entry->d_name) == -1) {
595                         fullpath = NULL;
596                         continue;
597                 }
598
599                 /* If one were to prompt the user before submitting, that
600                  * might happen here.  */
601
602                 fprintf(stderr, "+ Looking at %s\n", fullpath);
603
604                 ret = move_core(fullpath, "to-process");
605                 if (ret == 0)
606                         work++;
607
608                 free(fullpath);
609                 fullpath = NULL;
610         }
611         closedir(dir);
612
613         if (work) {
614                 fprintf(stderr, "+ Found %d files, setting pq_work condition\n", work);
615                 g_mutex_lock(pq_mtx);
616                 g_cond_signal(pq_work);
617                 pq = TRUE;
618                 g_mutex_unlock(pq_mtx);
619         }
620
621         fprintf(stderr, "+ End scanning %s...\n", core_folder);
622         return TRUE;
623 }
624
625 /*
626  * scan for core_*.to-process and core_*.processed,
627  * insure a summary *.txt report exists, then queue it
628  */
629 void *scan_processed_folder(void __unused *unused)
630 {
631         DIR *dir = NULL;
632         struct dirent *entry = NULL;
633         char *fullpath = NULL;
634         struct oops *oops = NULL;
635
636         while(1) {
637                 g_mutex_lock(pq_mtx);
638                 while (pq != TRUE) {
639                         fprintf(stderr, "+ Awaiting work in %s...\n", processed_folder);
640                         g_cond_wait(pq_work, pq_mtx);
641                 }
642                 pq = FALSE;
643                 g_mutex_unlock(pq_mtx);
644
645                 fprintf(stderr, "+ Begin scanning %s...\n", processed_folder);
646
647                 dir = opendir(processed_folder);
648                 if (!dir) {
649                         fprintf(stderr, "+ Unable to open %s\n", processed_folder);
650                         continue;
651                 }
652                 while(1) {
653                         entry = readdir(dir);
654                         if (!entry || !entry->d_name)
655                                 break;
656                         if (entry->d_name[0] == '.')
657                                 continue;
658
659                         /* files with trailing ".to-process" or "processed" represent new work */
660                         if (!strstr(entry->d_name, "process"))
661                                 continue;
662
663                         if (asprintf(&fullpath, "%s%s", processed_folder, entry->d_name) == -1) {
664                                 fullpath = NULL;
665                                 continue;
666                         }
667
668                         fprintf(stderr, "+ Looking at %s\n", fullpath);
669
670                         oops = create_report(fullpath);
671
672                         if (oops) {
673                                 fprintf(stderr, "+ Queued backtrace from %s\n", oops->detail_filename);
674                                 queue_backtrace(oops);
675                         }
676
677                         free(fullpath);
678                         fullpath = NULL;
679                 }
680                 closedir(dir);
681                 fprintf(stderr, "+ End scanning %s...\n", processed_folder);
682         }
683
684         return NULL;
685 }
686
687 static void disable_corefiles(int diskfree)
688 {
689         int ret;
690         ret = system("echo \"\" > /proc/sys/kernel/core_pattern");
691         if (ret != -1) {
692                 fprintf(stderr, "+ disabled core pattern, disk low %d%%\n", diskfree);
693                 syslog(LOG_WARNING,
694                         "Disabled kernel core_pattern, %s only has %d%% available",
695                         core_folder, diskfree);
696         }
697 }
698
699 void enable_corefiles(int diskfree)
700 {
701         int ret;
702         char * proc_core_string = NULL;
703         ret = asprintf(&proc_core_string,
704                         "echo \"%score_%%e_%%t\" > /proc/sys/kernel/core_pattern",
705                         core_folder);
706         if (ret == -1)
707                 goto err;
708
709         ret = system(proc_core_string);
710         free(proc_core_string);
711         if (ret == -1)
712                 goto err;
713
714         if (diskfree == -1) {
715                fprintf(stderr, "+ enabled core pattern\n");
716                syslog(LOG_INFO, "Enabled corewatcher kernel core_pattern\n");
717         } else {
718                fprintf(stderr, "+ reenabled core pattern, disk %d%%", diskfree);
719                syslog(LOG_WARNING,
720                        "Reenabled corewatcher kernel core_pattern, %s now has %d%% available",
721                        core_folder, diskfree);
722         }
723         return;
724 err:
725         fprintf(stderr, "+ unable to enable core pattern\n");
726         syslog(LOG_WARNING, "Unable to enable kernel core_pattern\n");
727         return;
728 }
729
730 /* do everything, called from timer event */
731 int scan_folders(void __unused *unused)
732 {
733         struct statvfs stat;
734         int newdiskfree;
735
736         if (statvfs(core_folder, &stat) == 0) {
737                 newdiskfree = (int)(100 * stat.f_bavail / stat.f_blocks);
738
739                 openlog("corewatcher", 0, LOG_KERN);
740                 if ((newdiskfree < 10) && (diskfree >= 10))
741                         disable_corefiles(newdiskfree);
742                 if ((newdiskfree > 12) && (diskfree <= 12))
743                         enable_corefiles(newdiskfree);
744                 closelog();
745
746                 diskfree = newdiskfree;
747         }
748
749         scan_core_folder(NULL);
750
751         g_mutex_lock(pq_mtx);
752         g_cond_signal(pq_work);
753         pq = TRUE;
754         g_mutex_unlock(pq_mtx);
755
756         return TRUE;
757 }