Add PID information as a new type of bugreport
[platform/core/system/crash-worker.git] / src / bugreport-service / diagnostics / diagnostics_dump.c
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <alloca.h>
17 #include <assert.h>
18 #include <diagnostics.h>
19 #include <dirent.h>
20 #include <getopt.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/mman.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include "diagnostics/diagnostics.h"
32 #include "diagnostics/diagnostics_dump.h"
33 #include "shared/log.h"
34 #include "shared/util.h"
35
36 enum BUGREPORT_REPORT_TYPE { BR_UNKNOWN, BR_BUGREPORT, BR_CRASHINFO, BR_CRASHINFO_JSON, BR_COREDUMP_TAR, BR_FULLREPORT, BR_PID };
37
38 struct report_file {
39         char *file_name;
40         char *file_path;
41         struct timespec ctime;
42 };
43
44 static bool get_reports_list(const char *path, struct report_file **list, size_t *list_count)
45 {
46         assert(list);
47         assert(list_count);
48
49         DIR *dp;
50         struct dirent *ep;
51         dp = opendir(path);
52         if (dp == NULL) {
53                 _E("Open directory %s error: %m", path);
54                 return false;
55         }
56
57         char file_path[PATH_MAX];
58         bool ret = false;
59
60         while ((ep = readdir(dp))) {
61                 if (ep->d_type != DT_REG)
62                         continue;
63                 struct stat st;
64                 int r = snprintf(file_path, sizeof file_path, "%s/%s", path, ep->d_name);
65                 if (r == -1 || r >= sizeof file_path) {
66                         _E("Error while preparing report path: %m");
67                         goto out;
68                 }
69                 if (stat(file_path, &st) != 0) {
70                         _E("Get stats about %s error: %m", path);
71                         goto out;
72                 }
73                 struct tm t;
74                 if (localtime_r(&(st.st_ctim.tv_sec), &t) == NULL) {
75                         _E("localtime_r error: %m");
76                         goto out;
77                 }
78
79                 struct report_file *tmp_list = realloc(*list, sizeof(struct report_file)*(*list_count + 1));
80                 char *fname = strdup(ep->d_name);
81                 char *fpath = strdup(file_path);
82                 if (tmp_list == NULL || fname == NULL || fpath == NULL) {
83                         _E("Out of memory");
84                         free(fname);
85                         free(fpath);
86                         free(tmp_list);
87                         goto out;
88                 }
89
90                 *list = tmp_list;
91                 (*list)[*list_count].file_name = fname;
92                 (*list)[*list_count].file_path = fpath;
93                 (*list)[*list_count].ctime = st.st_ctim;
94                 (*list_count)++;
95         }
96
97         ret = true;
98 out:
99         closedir(dp);
100         return ret;
101 }
102
103 static void free_record(struct report_file *record)
104 {
105         free(record->file_name);
106         free(record->file_path);
107 }
108
109 static void filter_time(struct report_file *list, size_t *list_count, long time_from, long time_to)
110 {
111         size_t dest = 0;
112         size_t n_list_count = *list_count;
113
114         struct timespec current_ts;
115         clock_gettime(CLOCK_REALTIME, &current_ts);
116
117         for (int i = 0; i < *list_count; i++) {
118                 if (list[i].ctime.tv_sec >= time_from && list[i].ctime.tv_sec < time_to) {
119                         if (dest != i)
120                                 list[dest] = list[i];
121                         dest++;
122                 } else {
123                         free_record(&list[i]);
124                         n_list_count--;
125                 }
126         }
127         *list_count = n_list_count;
128 }
129
130 static int ctime_compare(const void *a, const void *b)
131 {
132         return ((struct report_file*)a)->ctime.tv_sec - ((struct report_file*)b)->ctime.tv_sec;
133 }
134
135 static struct report_file* get_reports(size_t *count)
136 {
137         assert(count);
138
139         struct report_file *list = NULL;
140         *count = 0;
141
142         const char *dirs[] = {
143                 CRASH_ROOT_PATH CRASH_PATH_SUBDIR,
144                 CRASH_ROOT_PATH LIVE_PATH_SUBDIR
145         };
146
147         for (int i = 0; i < ARRAY_SIZE(dirs); i++) {
148                 if (file_exists(dirs[i]) && !get_reports_list(dirs[i], &list, count)) {
149                         _E("Can not read reports from: %s", dirs[i]);
150                         return NULL;
151                 }
152         }
153
154         qsort(list, *count, sizeof(struct report_file), ctime_compare);
155         return list;
156 }
157
158 static struct report_file* get_reports_from_range(long time_from, long time_to, size_t *count)
159 {
160         struct report_file *list = get_reports(count);
161
162         filter_time(list, count, time_from, time_to);
163         return list;
164 }
165
166 static void write_bugreport_log_file(int out_fd, const char *report_path)
167 {
168         int in_fd = diagnostics_report_get_file(report_path, LOG_FILE);
169         if (in_fd < 0) {
170                 _D("No LOG_FILE report in %s file", report_path);
171                 return;
172         }
173
174         if (dprintf(out_fd, "Begin systemtate-log\n") == -1) {
175                 _E("Write error: %m");
176                 goto out;
177         }
178
179         if (copy_bytes(out_fd, in_fd, NULL) == -1) {
180                 _E("Copy data error");
181                 goto out;
182         }
183
184 out:
185         close(in_fd);
186         if (dprintf(out_fd, "End systemtate-log\n") == -1)
187                 _E("Write error: %m");
188 }
189
190 static bool write_bugreport(int fd, long time_from, long time_to)
191 {
192         size_t list_count = 0;
193         bool result = true;
194
195         struct report_file *list = get_reports_from_range(time_from, time_to, &list_count);
196         if (list == NULL)
197                 return false;
198
199         for (int i = 0; i < list_count; i++) {
200                 if (dprintf(fd, "%sBegin bugreport <%s>\n", (i > 0) ? "\n" : "", list[i].file_path) == -1) {
201                         _E("Write error: %m");
202                         result = false;
203                         goto out;
204                 }
205
206                 write_bugreport_log_file(fd, list[i].file_path);
207
208                 if (dprintf(fd, "End bugreport <%s>\n", list[i].file_path) == -1) {
209                         _E("Write error: %m");
210                         result = false;
211                         goto out;
212                 }
213         }
214
215 out:
216         for (int i = 0; i < list_count; i++)
217                 free_record(&list[i]);
218         free(list);
219         return result;
220 }
221
222 static bool write_crash_info(int fd, long time_from, long time_to, bool as_json)
223 {
224         bool result = true;
225         size_t list_count = 0;
226
227         struct report_file *list = get_reports_from_range(time_from, time_to, &list_count);
228         if (list == NULL) {
229                 if (as_json)
230                         dprintf(fd, "{\"error\": \"Internal error.\"}");
231                 else
232                         dprintf(fd, "Internal error\n");
233                 return false;
234         }
235
236
237         const enum diagnostics_entry entry_type = as_json ? INFO_JSON : INFO_FILE;
238         for (int i = 0, p = 0; i < list_count; i++) {
239                 int nfd = diagnostics_report_get_file(list[i].file_path, entry_type);
240                 if (nfd <= 0) {
241                         _I("No INFO_FILE in %s file", list[i].file_path);
242                         dprintf(nfd, "No INFO_FILE in %s\n",  list[i].file_path);
243                         continue;
244                 }
245
246                 int res = 0;
247                 if (as_json)
248                         res = dprintf(fd, "%c\n", (p == 0) ? '[' : ',');
249                 else
250                         res = dprintf(fd, "%sBegin crash-info <%s>\n", (i > 0) ? "\n" : "", list[i].file_path);
251
252                 if (res == -1) {
253                         _E("Write error: %m");
254                         result = false;
255                         goto out;
256                 }
257
258                 if (copy_bytes(fd, nfd, NULL) == -1) {
259                         _E("Copy data error");
260                         close(nfd);
261                         result = false;
262                         goto out;
263                 }
264                 p++;
265
266                 close(nfd);
267
268                 if (as_json) {
269                         if (i == list_count-1)
270                                 res = dprintf(fd, "]\n");
271                 } else {
272                         res = dprintf(fd, "End crash-info <%s>\n", list[i].file_path);
273                 }
274                 if (res == -1) {
275                         _E("Write error: %m");
276                         result = false;
277                         goto out;
278                 }
279         }
280
281 out:
282         for (int i = 0; i < list_count; i++)
283                 free_record(&list[i]);
284         free(list);
285         return result;
286 }
287
288 static struct report_file *find_report_file(const char *ident, struct report_file *list, size_t list_count)
289 {
290         assert(ident);
291         assert(list);
292
293         for (size_t i = 0; i < list_count; i++) {
294                 char *dot = rindex(list[i].file_name, '.');
295                 if (dot == NULL)
296                         continue;
297                 if (strncmp(ident, list[i].file_name, dot - list[i].file_name) == 0)
298                         return &list[i];
299         }
300         return NULL;
301 }
302
303 static bool parse_pid_from_report_file(int report_fd, char *str)
304 {
305         FILE *fp;
306         char buff[1024];
307         int pid = -1;
308
309         fp = fdopen(report_fd, "r");
310
311         while (fgets(buff, sizeof(buff), fp)) {
312                 if (sscanf(buff, "PID = %d", &pid) == 1)
313                         break;
314         }
315         if (pid < 0)
316                 return false;
317
318         sprintf(str, "%d", pid);
319
320         return true;
321 }
322
323 static bool write_pid(int fd, int in_fd)
324 {
325         char pid[10];
326
327         if (!parse_pid_from_report_file(in_fd, pid)) {
328                 dprintf(fd, "Internal error\n");
329                 return false;
330         }
331
332         if (write(fd, pid, strlen(pid)) < 0) {
333                 dprintf(fd, "Internal error: %m\n");
334                 return false;
335         }
336
337         return true;
338 }
339
340 static bool write_blank(int fd)
341 {
342         if (write(fd, " ", 1) < 0) {
343                 dprintf(fd, "Internal error: %m\n");
344                 return false;
345         }
346
347         return true;
348 }
349
350 static bool write_pids(int fd, long time_from, long time_to)
351 {
352         bool result = true;
353         size_t list_count = 0;
354         struct report_file *list = get_reports_from_range(time_from, time_to, &list_count);
355         if (list == NULL) {
356                 dprintf(fd, "Internal error\n");
357                 return false;
358         }
359
360         for (int i = 0; i < list_count; i++) {
361                 int nfd = diagnostics_report_get_file(list[i].file_path, INFO_FILE);
362                 if (nfd <= 0) {
363                         _I("No INFO_FILE in %s file", list[i].file_path);
364                         dprintf(nfd, "No INFO_FILE in %s\n",  list[i].file_path);
365                         continue;
366                 }
367
368                 if (!write_pid(fd, nfd)) {
369                         _E("write_pid error");
370                         close(nfd);
371                         result = false;
372                         break;
373                 }
374
375                 close(nfd);
376
377                 if (!write_blank(fd)) {
378                         _E("write_blank error");
379                         result = false;
380                         break;
381                 }
382         }
383
384         for (int i = 0; i < list_count; i++)
385                 free_record(&list[i]);
386         free(list);
387
388         return result;
389 }
390
391 static bool write_single_file(int fd, enum BUGREPORT_REPORT_TYPE report_type, const char *ident)
392 {
393         assert(fd >= 0);
394         assert(ident);
395
396         bool result = true;
397         size_t list_count = 0;
398         struct report_file *list = get_reports(&list_count);
399         if (list == NULL) {
400                 if (report_type == BR_CRASHINFO_JSON)
401                         dprintf(fd, "{\"error\": \"Internal error.\"}");
402                 else
403                         dprintf(fd, "Internal error\n");
404                 return false;
405         }
406
407         struct report_file *report = find_report_file(ident, list, list_count);
408         if (report == NULL) {
409                 result = false;
410                 goto out;
411         }
412
413         enum diagnostics_entry report_entry = INFO_FILE;
414         switch (report_type) {
415         case BR_BUGREPORT:
416                 report_entry = LOG_FILE;
417                 break;
418         case BR_CRASHINFO:
419                 report_entry = INFO_FILE;
420                 break;
421         case BR_CRASHINFO_JSON:
422                 report_entry = INFO_JSON;
423                 break;
424         case BR_COREDUMP_TAR:
425                 report_entry = COREDUMP_TAR;
426                 break;
427         case BR_FULLREPORT:
428                 report_entry = FULL_REPORT;
429                 break;
430         case BR_PID:
431                 report_entry = INFO_PID;
432                 break;
433         default:
434                 _E("Unsupported report type: %d", report_type);
435                 result = false;
436                 goto out;
437         }
438
439         int in_fd = diagnostics_report_get_file(report->file_path, report_entry);
440         if (in_fd < 0) {
441                 _E("Can not get report type: %d from: %s (report ident: %s)", report_type, report->file_path, ident);
442                 result = false;
443                 goto out;
444         }
445
446         if (report_entry == INFO_PID)
447                 write_pid(fd, in_fd);
448         else if (copy_bytes(fd, in_fd, NULL) == -1) {
449                 _E("Copy data error");
450                 result = false;
451                 goto out;
452         }
453 out:
454         for (size_t i = 0; i < list_count; i++)
455                 free_record(&list[i]);
456         free(list);
457         return result;
458 }
459
460 struct diagnostics_call_options {
461         enum BUGREPORT_REPORT_TYPE report_type;
462         long from, to;
463         bool last_set, from_set, to_set;
464         char *arg;
465 };
466
467 static void diagnostics_print_help(int out_fd)
468 {
469         dprintf(out_fd, "Usage:\n\n"
470         "  dumpsys org.tizen.bugreport-service [--type=<report_type>] [[--last <seconds> |--from <timestamp> [--to <timestamp>]]|[report_ident]]\n\n"
471         "  --type <report_type>    Specify the type of report. Available types: bugreport, crash-info, crash-info-json. Additional types are available\n"
472         "                          if single report is specified by <report_ident>: fullreport, coredumptar (default: bugreport).\n"
473         "  --last <seconds>        Get reports generated in the last <seconds>.\n"
474         "  --from <timestamp>      Get reports generated after specified time.\n"
475         "  --to <timestamp>        Get reports generated before specified time (default: current time).\n"
476         "  report_ident            Get a specified report.\n\n"
477         "  If no time range or report_ident is specified, all reports will be returned.\n\n"
478         );
479 }
480
481 static bool get_file_from_ctx(diagnostics_ctx_h ctx, char **report_file)
482 {
483         assert(report_file);
484
485         if (ctx == NULL)
486                 return false;
487
488         bool result = false;
489         char *event_name = NULL;
490         bundle *event_data = NULL;
491
492         int ret = diagnostics_get_event_name(ctx, &event_name);
493         if (ret != DIAGNOSTICS_ERROR_NONE) {
494                 _E("Get event name error: %d", ret);
495                 goto out;
496         }
497         if (strcmp(event_name, "BugreportCreated") != 0) {
498                 _E("Unknown context: %s", event_name);
499                 goto out;
500         }
501
502         ret = diagnostics_get_event_data(ctx, &event_data);
503         if (ret != DIAGNOSTICS_ERROR_NONE) {
504                 _E("Get event data error: %d", ret);
505                 goto out;
506         }
507
508         char *tmp_report_file;
509         ret = bundle_get_str(event_data, "report_file", &tmp_report_file);
510         if (ret != BUNDLE_ERROR_NONE) {
511                 _E("Can not get report_file from event data: %d", ret);
512                 goto out;
513         }
514
515         *report_file = strdup(tmp_report_file);
516         if (*report_file == NULL) {
517                 _E("Out of memory: %m");
518                 goto out;
519         }
520         _D("Report file from event data: %s", *report_file);
521
522         result = true;
523 out:
524         if (event_name != NULL)
525                 free(event_name);
526         if (event_data != NULL)
527                 bundle_free(event_data);
528
529         return result;
530 }
531
532 static bool diagnostics_call_parse_options(int out_fd, char **params, int params_size, diagnostics_ctx_h ctx, struct diagnostics_call_options *dco)
533 {
534         struct timespec cur_time;
535         clock_gettime(CLOCK_REALTIME, &cur_time);
536         dco->report_type = BR_BUGREPORT;
537         dco->from = 0;
538         dco->to = cur_time.tv_sec;
539         dco->last_set = false;
540         dco->from_set = false;
541         dco->to_set = false;
542         dco->arg = NULL;
543
544         enum PARAM_NAME { PN_TYPE = 1, PN_LAST, PN_TO, PN_FROM, PN_HELP };
545
546         struct option long_options[] = {
547                 {"type", required_argument, NULL, PN_TYPE},
548                 {"last", required_argument, NULL, PN_LAST},
549                 {"to", required_argument, NULL, PN_TO},
550                 {"from", required_argument, NULL, PN_FROM},
551                 {"help", no_argument, NULL, PN_HELP},
552                 {0, 0, 0, 0},
553         };
554
555         int opt;
556         optind = 0;
557
558         char **nparams = alloca((params_size+1)*sizeof(char*));
559         memcpy(&nparams[1], params, params_size*sizeof(char*));
560         nparams[0] = "n";
561         while ((opt = getopt_long_only(params_size+1, nparams, "", long_options, NULL)) != -1) {
562                 switch (opt) {
563                 case PN_TYPE:
564                         if (strcmp(optarg, "bugreport") == 0) {
565                                 dco->report_type = BR_BUGREPORT;
566                         } else if (strcmp(optarg, "crash-info") == 0) {
567                                 dco->report_type = BR_CRASHINFO;
568                         } else if (strcmp(optarg, "crash-info-json") == 0) {
569                                 dco->report_type = BR_CRASHINFO_JSON;
570                         } else if (strcmp(optarg, "coredumptar") == 0) {
571                                 dco->report_type = BR_COREDUMP_TAR;
572                         } else if (strcmp(optarg, "fullreport") == 0) {
573                                 dco->report_type = BR_FULLREPORT;
574                         } else if (strcmp(optarg, "pid") == 0) {
575                                 dco->report_type = BR_PID;
576                         }else {
577                                 _E("Incorrect report type: %s", optarg);
578                                 dprintf(out_fd, "Incorrect report type: %s\n", optarg);
579                                 return false;
580                         }
581                         break;
582                 case PN_LAST:
583                         dco->last_set = true;
584                         dco->from = cur_time.tv_sec - strtol(optarg, NULL, 10);
585                         break;
586                 case PN_FROM:
587                         dco->from_set = true;
588                         dco->from = strtol(optarg, NULL, 10);
589                         break;
590                 case PN_TO:
591                         dco->to_set = true;
592                         dco->to = strtol(optarg, NULL, 10);
593                         break;
594                 case PN_HELP:
595                         diagnostics_print_help(out_fd);
596                         return false;
597                 default:
598                         _E("Incorrect option: %s", (optind > 0 && optind - 1 < params_size + 1) ? nparams[optind-1] : "<unknown>");
599                         dprintf(out_fd, "Incorrect option: %s\n", (optind > 0 && optind - 1 < params_size + 1) ? nparams[optind-1] : "<unknown>");
600                         return false;
601                 }
602         }
603
604         if (optind < params_size+1) {
605                 dco->arg = strdup(nparams[optind]);
606                 if (dco->arg == NULL) {
607                         _E("Out of memory: %m");
608                         return false;
609                 }
610         } else {
611                 get_file_from_ctx(ctx, &dco->arg);
612         }
613
614         return true;
615 }
616
617 static void diagnostics_callback(diagnostics_data_h data, char **params, int params_size, diagnostics_ctx_h ctx, void *user_data)
618 {
619         int fd;
620         int ret = diagnostics_data_get_fd(data, &fd);
621         if (ret < 0) {
622                 _E("Get data file descriptor error: %d", ret);
623                 return;
624         }
625         struct diagnostics_call_options dco;
626
627         if (!diagnostics_call_parse_options(fd, params, params_size, ctx, &dco))
628                 return;
629
630         if ((dco.last_set && (dco.from_set || dco.to_set)) || (dco.to_set && !dco.from_set)) {
631                 _E("Incorrect parameters set");
632                 dprintf(fd, "Incorrect parameters set.\n");
633                 goto out;
634         }
635
636         if (dco.arg != NULL) {
637                 if (dco.last_set || dco.from_set || dco.to_set) {
638                         _E("Incorrect parameters set.");
639                         dprintf(fd, "Incorrect parameters set.\n");
640                         goto out;
641                 }
642                 write_single_file(fd, dco.report_type, dco.arg);
643                 goto out;
644         }
645
646         switch(dco.report_type) {
647         case BR_CRASHINFO:
648                 write_crash_info(fd, dco.from, dco.to, false);
649                 break;
650         case BR_CRASHINFO_JSON:
651                 write_crash_info(fd, dco.from, dco.to, true);
652                 break;
653         case BR_BUGREPORT:
654                 write_bugreport(fd, dco.from, dco.to);
655                 break;
656         case BR_COREDUMP_TAR:
657                 dprintf(fd, "Report type \"coredumptar\" requires a specific report with report_ident argument.\n");
658                 break;
659         case BR_FULLREPORT:
660                 dprintf(fd, "Report type \"fullreport\" requires a specific report with report_ident argument.\n");
661                 break;
662         case BR_PID:
663                 write_pids(fd, dco.from, dco.to);
664                 break;
665         default:
666                 _E("Unknown report type\n");
667         }
668 out:
669         if (dco.arg != NULL)
670                 free(dco.arg);
671 }
672
673 bool diagnostics_init()
674 {
675         diagnostics_set_client_id("org.tizen.bugreport-service");
676         if (diagnostics_set_data_request_cb(&diagnostics_callback, NULL) != DIAGNOSTICS_ERROR_NONE) {
677                 _E("Unable to register diagnostics callback");
678                 return false;
679         }
680
681         return true;
682 }
683