bugreport-service: Add crash-info-json report type
[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 };
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(long time_from, long time_to, size_t *count)
136 {
137         struct report_file *list = NULL;
138         *count = 0;
139
140         const char *dirs[] = {
141                 CRASH_ROOT_PATH CRASH_PATH_SUBDIR,
142                 CRASH_ROOT_PATH LIVE_PATH_SUBDIR
143         };
144
145         for (int i = 0; i < ARRAY_SIZE(dirs); i++) {
146                 if (file_exists(dirs[i]) && !get_reports_list(dirs[i], &list, count)) {
147                         _E("Can not read reports from: %s", dirs[i]);
148                         return NULL;
149                 }
150         }
151
152         filter_time(list, count, time_from, time_to);
153
154         qsort(list, *count, sizeof(struct report_file), ctime_compare);
155         return list;
156 }
157
158 static void write_bugreport_log_file(int out_fd, const char *report_path)
159 {
160         int in_fd = diagnostics_report_get_file(report_path, LOG_FILE);
161         if (in_fd < 0) {
162                 _D("No LOG_FILE report in %s file", report_path);
163                 return;
164         }
165
166         if (dprintf(out_fd, "Begin systemtate-log\n") == -1) {
167                 _E("Write error: %m");
168                 goto out;
169         }
170
171         if (copy_bytes(out_fd, in_fd, NULL) == -1) {
172                 _E("Copy data error");
173                 goto out;
174         }
175
176 out:
177         close(in_fd);
178         if (dprintf(out_fd, "End systemtate-log\n") == -1)
179                 _E("Write error: %m");
180 }
181
182 static bool write_bugreport(int fd, long time_from, long time_to)
183 {
184         size_t list_count = 0;
185         bool result = true;
186
187         struct report_file *list = get_reports(time_from, time_to, &list_count);
188         if (list == NULL) {
189                 dprintf(fd, "Internal error.\n");
190                 return false;
191         }
192
193         for (int i = 0; i < list_count; i++) {
194                 if (dprintf(fd, "%sBegin bugreport <%s>\n", (i > 0) ? "\n" : "", list[i].file_path) == -1) {
195                         _E("Write error: %m");
196                         result = false;
197                         goto out;
198                 }
199
200                 write_bugreport_log_file(fd, list[i].file_path);
201
202                 if (dprintf(fd, "End bugreport <%s>\n", list[i].file_path) == -1) {
203                         _E("Write error: %m");
204                         result = false;
205                         goto out;
206                 }
207         }
208
209 out:
210         for (int i = 0; i < list_count; i++)
211                 free_record(&list[i]);
212         free(list);
213         return result;
214 }
215
216 static bool write_crash_info(int fd, long time_from, long time_to, bool as_json)
217 {
218         bool result = true;
219         size_t list_count = 0;
220
221         struct report_file *list = get_reports(time_from, time_to, &list_count);
222         if (list == NULL) {
223                 dprintf(fd, "Internal error.\n");
224                 return false;
225         }
226
227
228         const enum diagnostics_entry entry_type = as_json ? INFO_JSON : INFO_FILE;
229         for (int i = 0, p = 0; i < list_count; i++) {
230                 int nfd = diagnostics_report_get_file(list[i].file_path, entry_type);
231                 if (nfd <= 0) {
232                         _I("No INFO_FILE in %s file", list[i].file_path);
233                         dprintf(nfd, "No INFO_FILE in %s\n",  list[i].file_path);
234                         continue;
235                 }
236
237                 int res = 0;
238                 if (as_json)
239                         res = dprintf(fd, "%c\n", (p == 0) ? '[' : ',');
240                 else
241                         res = dprintf(fd, "%sBegin crash-info <%s>\n", (i > 0) ? "\n" : "", list[i].file_path);
242
243                 if (res == -1) {
244                         _E("Write error: %m");
245                         result = false;
246                         goto out;
247                 }
248
249                 if (copy_bytes(fd, nfd, NULL) == -1) {
250                         _E("Copy data error");
251                         close(nfd);
252                         result = false;
253                         goto out;
254                 }
255                 p++;
256
257                 close(nfd);
258
259                 if (as_json) {
260                         if (i == list_count-1)
261                                 res = dprintf(fd, "]\n");
262                 } else {
263                         res = dprintf(fd, "End crash-info <%s>\n", list[i].file_path);
264                 }
265                 if (res == -1) {
266                         _E("Write error: %m");
267                         result = false;
268                         goto out;
269                 }
270         }
271
272 out:
273         for (int i = 0; i < list_count; i++)
274                 free_record(&list[i]);
275         free(list);
276         return result;
277 }
278
279 struct diagnostics_call_options {
280         enum BUGREPORT_REPORT_TYPE report_type;
281         long from, to;
282         bool last_set, from_set, to_set;
283 };
284
285 static bool diagnostics_call_parse_options(int out_fd, char **params, int params_size, struct diagnostics_call_options *dco)
286 {
287         struct timespec cur_time;
288         clock_gettime(CLOCK_REALTIME, &cur_time);
289         dco->report_type = BR_BUGREPORT;
290         dco->from = 0;
291         dco->to = cur_time.tv_sec;
292         dco->last_set = false;
293         dco->from_set = false;
294         dco->to_set = false;
295
296         enum PARAM_NAME { PN_TYPE = 1, PN_LAST, PN_TO, PN_FROM };
297
298         struct option long_options[] = {
299                 {"type", required_argument, NULL, PN_TYPE},
300                 {"last", required_argument, NULL, PN_LAST},
301                 {"to", required_argument, NULL, PN_TO},
302                 {"from", required_argument, NULL, PN_FROM},
303                 {0, 0, 0, 0},
304         };
305
306         int opt;
307         optind = 0;
308
309         char **nparams = alloca((params_size+1)*sizeof(char*));
310         memcpy(&nparams[1], params, params_size*sizeof(char*));
311         nparams[0] = "n";
312         while ((opt = getopt_long_only(params_size+1, nparams, "", long_options, NULL)) != -1) {
313                 switch(opt) {
314                 case PN_TYPE:
315                         if (strcmp(optarg, "bugreport") == 0) {
316                                 dco->report_type = BR_BUGREPORT;
317                         } else if (strcmp(optarg, "crash-info") == 0) {
318                                 dco->report_type = BR_CRASHINFO;
319                         } else if (strcmp(optarg, "crash-info-json") == 0) {
320                                 dco->report_type = BR_CRASHINFO_JSON;
321                         } else {
322                                 _E("Incorrect report type: %s", optarg);
323                                 dprintf(out_fd, "Incorrect report type: %s\n", optarg);
324                                 return false;
325                         }
326                         break;
327                 case PN_LAST:
328                         dco->last_set = true;
329                         dco->from = cur_time.tv_sec - strtol(optarg, NULL, 10);
330                         break;
331                 case PN_FROM:
332                         dco->from_set = true;
333                         dco->from = strtol(optarg, NULL, 10);
334                         break;
335                 case PN_TO:
336                         dco->to_set = true;
337                         dco->to = strtol(optarg, NULL, 10);
338                         break;
339                 default:
340                         _E("Incorrect option: %s", (optind > 0 && optind + 1 < params_size) ? params[optind-1] : "<unknown>");
341                         dprintf(out_fd, "Incorrect option: %s\n", (optind > 0 && optind + 1 < params_size) ? params[optind-1] : "<unknown>");
342                         return false;
343                 }
344         }
345
346         return true;
347 }
348
349 static void diagnostics_callback(diagnostics_data_h data, char **params, int params_size, diagnostics_ctx_h ctx, void *user_data)
350 {
351         int fd;
352         int ret = diagnostics_data_get_fd(data, &fd);
353         if (ret < 0) {
354                 _E("Get data file descriptor error: %d", ret);
355                 return;
356         }
357         struct diagnostics_call_options dco;
358
359         if (!diagnostics_call_parse_options(fd, params, params_size, &dco))
360                 return;
361
362         if ((dco.last_set && (dco.from_set || dco.to_set)) || (dco.to_set && !dco.from_set)) {
363                 _E("Incorrect parameters set");
364                 dprintf(fd, "Incorrect parameters set.\n");
365                 return;
366         }
367
368         switch(dco.report_type) {
369         case BR_CRASHINFO:
370                 write_crash_info(fd, dco.from, dco.to, false);
371                 break;
372         case BR_CRASHINFO_JSON:
373                 write_crash_info(fd, dco.from, dco.to, true);
374                 break;
375         case BR_BUGREPORT:
376                 write_bugreport(fd, dco.from, dco.to);
377                 break;
378         default:
379                 _E("Unknown report type\n");
380         }
381 }
382
383 bool diagnostics_init()
384 {
385         diagnostics_set_client_id("org.tizen.bugreport-service");
386         if (diagnostics_set_data_request_cb(&diagnostics_callback, NULL) != DIAGNOSTICS_ERROR_NONE) {
387                 _E("Unable to register diagnostics callback");
388                 return false;
389         }
390
391         return true;
392 }
393