Change the crash-service to the bugreport-service
[platform/core/system/crash-worker.git] / src / bugreport-service / diagnostics / diagnostics.c
1 /*
2  * crash-manager
3  *
4  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 #include <assert.h>
19 #include <fcntl.h>
20 #include <limits.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28 #include <zip.h>
29 #include <errno.h>
30 #include <dirent.h>
31 #include "diagnostics/diagnostics.h"
32
33 #define LOG_TAG "CRASH_MANAGER"
34
35 #include "defs.h"
36 #include "shared/log.h"
37 #include "shared/util.h"
38 #include "shared/config.h"
39
40 #define SHARE_BASE_DIR    "/run/diagnostics/priv/fifo"
41 #define TMP_FILE_TEMPLATE "file_XXXXXX"
42
43 #define JSON_FILE_EXT     ".json"
44 #define ZIP_FILE_EXT      ".zip"
45
46 enum diagnostics_type {
47         BT_UNKNOWN,
48         BT_FULL_REPORT,
49         BT_INFO_JSON,
50         BT_DIR
51 };
52
53 static char *type_to_str(enum diagnostics_entry entry) {
54         switch (entry) {
55         case FULL_REPORT:
56                 return "FULL_REPORT";
57         case INFO_JSON:
58                 return "INFO_JSON";
59         default:
60                 return "UKNOWN";
61         }
62 }
63
64 static char *get_temp_file_name()
65 {
66         char *result;
67         if (asprintf(&result, "%s/%s", SHARE_BASE_DIR, TMP_FILE_TEMPLATE) == -1) {
68                 _E("asprintf() error: %m\n");
69                 return NULL;
70         }
71
72         if (mktemp(result) == NULL || result[0] == '\0') {
73                 _E("mktemp() error: %m\n");
74                 free(result);
75                 return NULL;
76         }
77
78         return result;
79 }
80
81 static bool is_report_file(const char *path, const char *reports_dir)
82 {
83         assert(path);
84         assert(reports_dir);
85
86         char *r_path = realpath(path, NULL);
87         if (r_path == NULL) {
88                 _E("realpath() for %s error: %m\n", path);
89                 return false;
90         }
91
92         if (access(r_path, F_OK) == -1) {
93                 _E("access() for %s error: %m\n", path);
94                 return false;
95         }
96
97         char *start = strstr(r_path, reports_dir);
98
99         if (start == NULL) {
100                 _I("%s is not within %s\n", r_path, reports_dir);
101                 return false;
102         }
103
104         return true;
105 }
106
107 static enum diagnostics_type check_report_type(const char *path)
108 {
109         assert(path);
110
111         if (strstr(path, JSON_FILE_EXT) == &path[strlen(path) - strlen(JSON_FILE_EXT)]) {
112                 _D("Report type of %s is JSON_FILE\n", path);
113                 return BT_INFO_JSON;
114         }
115
116         if (strstr(path, ZIP_FILE_EXT) == &path[strlen(path) - strlen(ZIP_FILE_EXT)]) {
117                 _D("Report type of %s is FULL_REPORT\n", path);
118                 return BT_FULL_REPORT;
119         }
120
121         struct stat file_stat;
122         if (stat(path, &file_stat) == -1) {
123                 _E("stat() for %s error: %m\n", path);
124                 return BT_UNKNOWN;
125         }
126
127         if (stat(path, &file_stat) == 0 && S_ISDIR(file_stat.st_mode)) {
128                 _D("Report type of %s is DIR", path);
129                 return BT_DIR;
130         }
131
132         _I("Report type of %s is UNKNOWN", path);
133         return BT_UNKNOWN;
134 }
135
136 static bool ends_with(const char *string, const char *end)
137 {
138         assert(string);
139         assert(end);
140
141         ssize_t end_len = strlen(end);
142         ssize_t string_len = strlen(string);
143
144         if (end_len == 0 || end_len > string_len)
145                 return false;
146
147         return strcasecmp(&string[string_len - end_len], end) == 0;
148 }
149
150 static bool file_match(const char *file_name, enum diagnostics_entry entry)
151 {
152         assert(file_name);
153
154         switch(entry) {
155         case INFO_JSON:
156                 return ends_with(file_name, JSON_FILE_EXT);
157         default:
158                 return false;
159         }
160 }
161
162 static bool extract_file(zip_t *archive, int index, const char *out_file_name)
163 {
164         bool result = false;
165         zip_file_t *zfile = NULL;
166
167         int extracted_fd = open(out_file_name, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
168         if (extracted_fd < 0) {
169                 _E("open() file \"%s\" for write error: %m\n", out_file_name);
170                 goto out;
171         }
172
173         struct zip_stat stat;
174         if (zip_stat_index(archive, index, ZIP_FL_UNCHANGED, &stat) == -1) {
175                 printf("zip_stat_index() error\n");
176                 goto out;
177         }
178
179         zfile = zip_fopen_index(archive, index, ZIP_FL_UNCHANGED);
180         if (zfile == NULL) {
181                 _E("zip_fopen_index() error\n");
182                 goto out;
183         }
184
185         char buff[0x1000];
186         zip_uint64_t sum = 0;
187
188         while (sum < stat.size) {
189                 zip_int64_t len = zip_fread(zfile, buff, sizeof(buff));
190                 if (len < 0) {
191                         _E("zip_fread() error\n");
192                         goto out;
193                 }
194                 if (write(extracted_fd, buff, len) == -1) {
195                         _E("write() error: %m\n");
196                         goto out;
197                 }
198                 sum += len;
199         }
200
201         result = true;
202 out:
203         if (zfile != NULL)
204                 zip_fclose(zfile);
205
206         if (extracted_fd >= 0)
207                 close(extracted_fd);
208
209         return result;
210 }
211
212 static int search_file_in_zip(zip_t *archive, enum diagnostics_entry entry)
213 {
214         zip_int64_t count = zip_get_num_entries(archive, ZIP_FL_UNCHANGED);
215
216         for (zip_int64_t i = 0; i < count; i++) {
217                 const char *entry_name = zip_get_name(archive, i, ZIP_FL_ENC_GUESS);
218                 if (entry_name == NULL) {
219                         _E("zip_get_name() error\n");
220                         continue;
221                 }
222
223                 if (!file_match(entry_name, entry))
224                         continue;
225
226                 return i;
227         }
228         return -1;
229 }
230
231 static int share_compressed_file(const char *path, enum diagnostics_entry entry)
232 {
233         assert(path);
234
235         int err = 0;
236         int fd = -BR_ERR_INTERNAL;
237
238         char *out_file = get_temp_file_name();
239
240         if (out_file == NULL)
241                 return -BR_ERR_INTERNAL;
242
243         zip_t *archive = zip_open(path, ZIP_RDONLY, &err);
244         if (err != 0) {
245                 _E("zip_open() %s error: %d\n", path, err);
246                 archive = NULL;
247                 goto out;
248         }
249
250         int file_index = search_file_in_zip(archive, entry);
251         if (file_index < 0) {
252                 _I("Archive \"%s\" doesn't contain file type: %s", path, type_to_str(entry));
253                 fd = -BR_ERR_PARAM;
254                 goto out;
255         }
256
257         if (extract_file(archive, file_index, out_file)) {
258                 fd = open(out_file, O_RDONLY);
259                 if (fd == -1) {
260                         _E("open() file \"%s\" for read error: %m", out_file);
261                         goto out;
262                 }
263                 _D("share file %s from \"%s\" as \"%s\"", type_to_str(entry), path, out_file);
264         }
265
266 out:
267         if (archive)
268                 zip_close(archive);
269         unlink(out_file);
270         free(out_file);
271         return fd;
272 }
273
274 static int share_file(const char *path)
275 {
276         assert(path);
277
278         const char *dest_file = get_temp_file_name();
279
280         if (dest_file == NULL)
281                 return -BR_ERR_INTERNAL;
282
283         _D("share file \"%s\" as \"%s\"", path, dest_file);
284
285         if (copy_file(dest_file, path) == -1) {
286                 _E("copy_file() error");
287                 return -BR_ERR_INTERNAL;
288         }
289
290         int fd = open(dest_file, O_RDONLY);
291         if (fd  == -1) {
292                 _E("open() file: \"%s\" error: %m\n", path);
293                 return -BR_ERR_INTERNAL;
294         }
295         unlink(dest_file);
296
297         return fd;
298 }
299
300 static int share_uncompressed_file(const char *path, enum diagnostics_entry entry)
301 {
302         assert(path);
303
304         struct dirent *ent;
305         DIR *dir = NULL;
306         int result = -BR_ERR_PARAM;
307         if ((dir = opendir(path)) == NULL) {
308                 _E("opendir() for \"%s\" error: %m\n", path);
309                 result = -BR_ERR_INTERNAL;
310                 goto out;
311         }
312
313         while((ent = readdir(dir)) != NULL) {
314                 if (file_match(ent->d_name, entry)) {
315                         char file_path[PATH_MAX];
316                         int ret = snprintf(file_path, PATH_MAX, "%s/%s", path, ent->d_name);
317
318                         if (ret < 0) {
319                                 _E("sprintf() error: %m");
320                                 result = -BR_ERR_INTERNAL;
321                                 goto out;
322                         } else if (ret >= PATH_MAX) {
323                                 _E("sprintf(): result string is too long");
324                                 result = -BR_ERR_INTERNAL;
325                                 goto out;
326                         }
327                         result = share_file(file_path);
328                         break;
329                 }
330         }
331
332         if (result < 0)
333                 _I("Report \"%s\" doesn't contain requested entry type.", path);
334
335 out:
336         if (dir != NULL)
337                 closedir(dir);
338
339         return result;
340 }
341
342 static int share_simple_file(const char *path, enum diagnostics_entry entry)
343 {
344         assert(path);
345
346         if (entry != INFO_JSON) {
347                 _E("Unsupported file type for report \"%s\" (%s)\n", path, type_to_str(entry));
348                 return -BR_ERR_PARAM;
349         }
350
351         return share_file(path);
352 }
353
354 int diagnostics_report_get_file(const char *report_path,
355                               const enum diagnostics_entry entry)
356 {
357         assert(report_path);
358
359         config_t config;
360         if (!config_init(&config, CRASH_MANAGER_CONFIG_PATH))
361                 return -BR_ERR_INTERNAL;
362
363         bool is_report = is_report_file(report_path, config.crash_root_path);
364         config_free(&config);
365
366         if (!is_report) {
367                 _E("File \"%s\" doesn't look like a report.", report_path);
368                 return -BR_ERR_PARAM;
369         }
370
371         enum diagnostics_type rtype = check_report_type(report_path);
372
373         switch(rtype) {
374         case BT_INFO_JSON:
375                 return share_simple_file(report_path, entry);
376                 break;
377         case BT_FULL_REPORT:
378                 if (entry == FULL_REPORT)
379                         return share_file(report_path);
380                 return share_compressed_file(report_path, entry);
381                 break;
382         case BT_DIR:
383                 return share_uncompressed_file(report_path, entry);
384                 break;
385         default:
386                 _E("%s is an unsupported report file.", report_path);
387                 return -BR_ERR_PARAM;
388         }
389 }