Allow to get bugreports using the diagnostics API 21/255621/3
authorMateusz Moscicki <m.moscicki2@partner.samsung.com>
Fri, 19 Mar 2021 16:53:49 +0000 (17:53 +0100)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Mon, 22 Mar 2021 12:43:16 +0000 (13:43 +0100)
bugreport-service registers as a org.tizen.bugreport-service and allows
to get bugreport and crash-info report by dumpsys.
For example: to get all bugreports created in the last hour run:

  dumpsys org.tizen.bugreport-service -- --type bugreport --last 3600

Supported parameters:

  --type <bugreport|crash-info>  Get specified report
  --last <sec>          Get reports created in the last <sec>
  --from <timestamp>    Get reports created after a specified time
  --to <timestamp>      Get reports created before a specified time

Change-Id: I0d3e04c34b63f3a559c8bde9af703eeabeb4e7ae

packaging/crash-worker.spec
src/bugreport-service/CMakeLists.txt
src/bugreport-service/bugreport-service.c
src/bugreport-service/diagnostics/diagnostics.c
src/bugreport-service/diagnostics/diagnostics.h
src/bugreport-service/diagnostics/diagnostics_dump.c [new file with mode: 0644]
src/bugreport-service/diagnostics/diagnostics_dump.h [new file with mode: 0644]
src/bugreport-service/org.tizen.dumpsys.providers.org.tizen.bugreport-service.service [new file with mode: 0644]

index 52ef2105c916638d694b25cbe6a86d13ce1a9dc2..4db80560feb0f69bffa85bf7507f73e16296630d 100644 (file)
@@ -344,6 +344,7 @@ fi
 %attr(-,root,root) %{_sysconfdir}/dbus-1/system.d/bugreport-service.conf
 %attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.system.crash.livedump.service
 %attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.system.diagnostics.service
+%attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.dumpsys.providers.org.tizen.bugreport-service.service
 %attr(0644,root,root) %{_prefix}/lib/tmpfiles.d/diagnostics-run.conf
 
 %if %{with doc}
index 3d493be52affd56a5df233337e8090a34f7e8225..b289d70f71d16742cbef7ca16aa9611e8e35c4e7 100644 (file)
@@ -7,6 +7,7 @@ SET(CRASH_SERVICE_SRCS
        bugreport-service.c
        systemdump.c
        diagnostics/diagnostics.c
+       diagnostics/diagnostics_dump.c
        ${CMAKE_SOURCE_DIR}/src/shared/util.c
        ${CMAKE_SOURCE_DIR}/src/shared/spawn.c
        ${CMAKE_SOURCE_DIR}/src/shared/config.c
@@ -22,6 +23,7 @@ pkg_check_modules(bugreport-service_pkgs REQUIRED
        gio-unix-2.0
        libzip
        iniparser
+       diagnostics
        )
 
 FOREACH(flag ${bugreport-service_pkgs_CFLAGS})
@@ -47,6 +49,9 @@ INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.tizen.system.crash.livedump.servic
 INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/diagnostics/org.tizen.system.diagnostics.service
        DESTINATION /usr/share/dbus-1/system-services)
 
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.tizen.dumpsys.providers.org.tizen.bugreport-service.service
+       DESTINATION /usr/share/dbus-1/system-services)
+
 INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/bugreport-service.conf
        DESTINATION /etc/dbus-1/system.d)
 
index 3bea41897fa5fc7a504271f67bb3a3ad1ef8d690..002149bcf9d77b4ef4ae8603569b0b8fc7214769 100644 (file)
@@ -27,6 +27,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 #include <diagnostics.h>
+#include <unistd.h>
 
 #include "bugreport-service.h"
 
@@ -35,6 +36,7 @@
 #include "shared/log.h"
 #include "shared/util.h"
 #include "diagnostics/diagnostics.h"
+#include "diagnostics/diagnostics_dump.h"
 #include "systemdump.h"
 
 /* Dbus activation */
@@ -510,8 +512,10 @@ int main(void)
        g_mutex_init(&timeout_mutex);
        add_timeout();
 
-       diagnostics_set_client_id("org.tizen.bugreport-service");
-       g_main_loop_run(loop);
+       if (diagnostics_init())
+               g_main_loop_run(loop);
+       else
+               _E("Diagnostics API init error.");
 
        if (introspection_data)
                g_dbus_node_info_unref(introspection_data);
index e66394fc8ecdd8efd522df027911c6534f1a833b..defee1781790d3538a559d9a920575448b9d0761 100644 (file)
 #define TMP_FILE_TEMPLATE "file_XXXXXX"
 
 #define JSON_FILE_EXT     ".json"
+#define INFO_FILE_EXT     ".info"
+#define LOG_FILE_EXT      ".log"
 #define ZIP_FILE_EXT      ".zip"
 
 enum diagnostics_type {
        BT_UNKNOWN,
        BT_FULL_REPORT,
        BT_INFO_JSON,
+       BT_INFO_FILE,
+       BT_LOG,
        BT_DIR
 };
 
@@ -56,6 +60,10 @@ static char *type_to_str(enum diagnostics_entry entry) {
                return "FULL_REPORT";
        case INFO_JSON:
                return "INFO_JSON";
+       case INFO_FILE:
+               return "INFO_FILE";
+       case LOG_FILE:
+               return "LOG_FILE";
        default:
                return "UKNOWN";
        }
@@ -113,6 +121,16 @@ static enum diagnostics_type check_report_type(const char *path)
                return BT_INFO_JSON;
        }
 
+       if (strstr(path, INFO_FILE_EXT) == &path[strlen(path) - strlen(INFO_FILE_EXT)]) {
+               _D("Report type of %s is INFO_FILE\n", path);
+               return BT_INFO_FILE;
+       }
+
+       if (strstr(path, LOG_FILE_EXT) == &path[strlen(path) - strlen(LOG_FILE_EXT)]) {
+               _D("Report type of %s is LOG_FILE\n", path);
+               return BT_LOG;
+       }
+
        if (strstr(path, ZIP_FILE_EXT) == &path[strlen(path) - strlen(ZIP_FILE_EXT)]) {
                _D("Report type of %s is FULL_REPORT\n", path);
                return BT_FULL_REPORT;
@@ -154,6 +172,10 @@ static bool file_match(const char *file_name, enum diagnostics_entry entry)
        switch(entry) {
        case INFO_JSON:
                return ends_with(file_name, JSON_FILE_EXT);
+       case INFO_FILE:
+               return ends_with(file_name, INFO_FILE_EXT);
+       case LOG_FILE:
+               return ends_with(file_name, LOG_FILE_EXT);
        default:
                return false;
        }
index 8b2bea962f3aa69877270666fe18b3e552abbdea..376ac543796128c8f82fa11b2a43f0252cddaf81 100644 (file)
@@ -25,7 +25,9 @@
 
 enum diagnostics_entry {
        FULL_REPORT = 0,
-       INFO_JSON
+       INFO_JSON,
+       INFO_FILE,
+       LOG_FILE,
 };
 
 enum diagnostics_metadata {
diff --git a/src/bugreport-service/diagnostics/diagnostics_dump.c b/src/bugreport-service/diagnostics/diagnostics_dump.c
new file mode 100644 (file)
index 0000000..f1fb212
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <alloca.h>
+#include <assert.h>
+#include <diagnostics.h>
+#include <dirent.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "diagnostics/diagnostics.h"
+#include "diagnostics/diagnostics_dump.h"
+#include "shared/log.h"
+#include "shared/util.h"
+
+enum BUGREPORT_REPORT_TYPE { BR_UNKNOWN, BR_BUGREPORT, BR_CRASHINFO };
+
+struct report_file {
+       char *file_name;
+       char *file_path;
+       struct timespec ctime;
+};
+
+static bool get_reports_list(const char *path, struct report_file **list, size_t *list_count)
+{
+       assert(list);
+       assert(list_count);
+
+       DIR *dp;
+       struct dirent *ep;
+       dp = opendir(path);
+       if (dp == NULL) {
+               _E("Open directory %s error: %m", path);
+               return false;
+       }
+
+       while ((ep = readdir(dp))) {
+               if (ep->d_type != 8)
+                       continue;
+               struct stat st;
+               char *file_path = NULL;
+               if (asprintf(&file_path, "%s/%s", path, ep->d_name) == -1) {
+                       _E("Memory allocation error: %m");
+                       return false;
+               }
+               if (stat(file_path, &st) != 0) {
+                       _E("Get stats about %s error: %m", path);
+                       return false;
+               }
+               struct tm t;
+               if (localtime_r(&(st.st_ctim.tv_sec), &t) == NULL) {
+                       _E("localtime_r error: %m");
+                       return false;
+               }
+
+               *list = realloc(*list, sizeof(struct report_file)*(*list_count + 1));
+               (*list)[*list_count].file_name = strdup(ep->d_name);
+               (*list)[*list_count].file_path = strdup(file_path);
+               (*list)[*list_count].ctime = st.st_ctim;
+               (*list_count)++;
+       }
+       closedir(dp);
+       return true;
+}
+
+static void free_record(struct report_file *record)
+{
+       free(record->file_name);
+       free(record->file_path);
+}
+
+static void filter_time(struct report_file *list, size_t *list_count, long time_from, long time_to)
+{
+       size_t dest = 0;
+       size_t n_list_count = *list_count;
+
+       struct timespec current_ts;
+       clock_gettime(CLOCK_REALTIME, &current_ts);
+
+       for (int i = 0; i < *list_count; i++) {
+               if (list[i].ctime.tv_sec >= time_from && list[i].ctime.tv_sec < time_to) {
+                       if (dest != i)
+                               list[dest] = list[i];
+                       dest++;
+               } else {
+                       free_record(&list[i]);
+                       n_list_count--;
+               }
+       }
+       *list_count = n_list_count;
+}
+
+static int ctime_compare(const void *a, const void *b)
+{
+       return ((struct report_file*)a)->ctime.tv_sec - ((struct report_file*)b)->ctime.tv_sec;
+}
+
+static struct report_file* get_reports(long time_from, long time_to, size_t *count)
+{
+       struct report_file *list = NULL;
+       *count = 0;
+
+       const char *dirs[] = {
+               CRASH_ROOT_PATH CRASH_PATH_SUBDIR,
+               CRASH_ROOT_PATH LIVE_PATH_SUBDIR
+       };
+
+       for (int i = 0; i < ARRAY_SIZE(dirs); i++) {
+               if (file_exists(dirs[i]) && !get_reports_list(dirs[i], &list, count)) {
+                       _E("Can not read reports from: %s", dirs[i]);
+                       return NULL;
+               }
+       }
+
+       filter_time(list, count, time_from, time_to);
+
+       qsort(list, *count, sizeof(struct report_file), ctime_compare);
+       return list;
+}
+
+static void write_bugreport_log_file(int out_fd, const char *report_path)
+{
+       int in_fd = diagnostics_report_get_file(report_path, LOG_FILE);
+       if (in_fd < 0) {
+               _D("No LOG_FILE report in %s file", report_path);
+               return;
+       }
+
+       if (dprintf(out_fd, "Begin systemtate-log\n") == -1) {
+               _E("Write error: %m");
+               goto out;
+       }
+
+       if (copy_bytes(out_fd, in_fd, NULL) == -1) {
+               _E("Copy data error");
+               goto out;
+       }
+
+out:
+       close(in_fd);
+       if (dprintf(out_fd, "End systemtate-log\n") == -1)
+               _E("Write error: %m");
+}
+
+static bool write_bugreport(int fd, long time_from, long time_to)
+{
+       size_t list_count = 0;
+       bool result = true;
+
+       struct report_file *list = get_reports(time_from, time_to, &list_count);
+       if (list == NULL) {
+               dprintf(fd, "Internal error.\n");
+               return false;
+       }
+
+       for (int i = 0; i < list_count; i++) {
+               if (dprintf(fd, "%sBegin bugreport <%s>\n", (i > 0) ? "\n" : "", list[i].file_path) == -1) {
+                       _E("Write error: %m");
+                       result = false;
+                       goto out;
+               }
+
+               write_bugreport_log_file(fd, list[i].file_path);
+
+               if (dprintf(fd, "End bugreport <%s>\n", list[i].file_path) == -1) {
+                       _E("Write error: %m");
+                       result = false;
+                       goto out;
+               }
+       }
+
+out:
+       for (int i = 0; i < list_count; i++)
+               free_record(&list[i]);
+       free(list);
+       return result;
+}
+
+static bool write_crash_info(int fd, long time_from, long time_to)
+{
+       bool result = true;
+       size_t list_count = 0;
+
+       struct report_file *list = get_reports(time_from, time_to, &list_count);
+       if (list == NULL) {
+               dprintf(fd, "Internal error.\n");
+               return false;
+       }
+
+       for (int i = 0; i < list_count; i++) {
+               int nfd = diagnostics_report_get_file(list[i].file_path, INFO_FILE);
+               if (nfd <= 0) {
+                       _I("No INFO_FILE in %s file", list[i].file_path);
+                       dprintf(nfd, "No INFO_FILE in %s\n",  list[i].file_path);
+                       continue;
+               }
+
+               if (dprintf(fd, "%sBegin crash-info <%s>\n", (i > 0) ? "\n" : "", list[i].file_path) == -1) {
+                       _E("Write error: %m");
+                       result = false;
+                       goto out;
+               }
+
+               if (copy_bytes(fd, nfd, NULL) == -1) {
+                       _E("Copy data error");
+                       close(nfd);
+                       result = false;
+                       goto out;
+               }
+
+               close(nfd);
+
+               if (dprintf(fd, "End crash-info <%s>\n", list[i].file_path) == -1) {
+                       _E("Write error: %m");
+                       result = false;
+                       goto out;
+               }
+       }
+
+out:
+       for (int i = 0; i < list_count; i++)
+               free_record(&list[i]);
+       free(list);
+       return result;
+}
+
+struct diagnostics_call_options {
+       enum BUGREPORT_REPORT_TYPE report_type;
+       long from, to;
+       bool last_set, from_set, to_set;
+};
+
+static bool diagnostics_call_parse_options(int out_fd, char **params, int params_size, struct diagnostics_call_options *dco)
+{
+       struct timespec cur_time;
+       clock_gettime(CLOCK_REALTIME, &cur_time);
+       dco->report_type = BR_UNKNOWN;
+       dco->from = 0;
+       dco->to = cur_time.tv_sec;
+       dco->last_set = false;
+       dco->from_set = false;
+       dco->to_set = false;
+
+       enum PARAM_NAME { PN_TYPE = 1, PN_LAST, PN_TO, PN_FROM};
+
+       struct option long_options[] = {
+               {"type", required_argument, NULL, PN_TYPE},
+               {"last", required_argument, NULL, PN_LAST},
+               {"to", required_argument, NULL, PN_TO},
+               {"from", required_argument, NULL, PN_FROM},
+       };
+
+       int opt;
+       optind = 0;
+
+       char **nparams = alloca((params_size+1)*sizeof(char*));
+       memcpy(&nparams[1], params, params_size*sizeof(char*));
+       nparams[0] = "n";
+       while ((opt = getopt_long_only(params_size+1, nparams, "", long_options, NULL)) != -1) {
+               switch(opt) {
+               case PN_TYPE:
+                       if (strcmp(optarg, "bugreport") == 0) {
+                               dco->report_type = BR_BUGREPORT;
+                       } else if (strcmp(optarg, "crash-info") == 0) {
+                               dco->report_type = BR_CRASHINFO;
+                       } else {
+                               _E("Incorrect report type: %s", optarg);
+                               dprintf(out_fd, "Incorrect report type: %s\n", optarg);
+                               return false;
+                       }
+                       break;
+               case PN_LAST:
+                       dco->last_set = true;
+                       dco->from = cur_time.tv_sec - strtol(optarg, NULL, 10);
+                       break;
+               case PN_FROM:
+                       dco->from_set = true;
+                       dco->from = strtol(optarg, NULL, 10);
+                       break;
+               case PN_TO:
+                       dco->to_set = true;
+                       dco->to = strtol(optarg, NULL, 10);
+                       break;
+               default:
+                       _E("Incorrect option: %s", (optind > 0 && optind + 1 < params_size) ? params[optind-1] : "<unknown>");
+                       dprintf(out_fd, "Incorrect option: %s\n", (optind > 0 && optind + 1 < params_size) ? params[optind-1] : "<unknown>");
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static void diagnostics_callback(diagnostics_data_h data, char **params, int params_size, diagnostics_ctx_h ctx, void *user_data)
+{
+       int fd;
+       int ret = diagnostics_data_get_fd(data, &fd);
+       if (ret < 0) {
+               _E("Get data file descriptor error: %d", ret);
+               return;
+       }
+       struct diagnostics_call_options dco;
+
+       if (!diagnostics_call_parse_options(fd, params, params_size, &dco))
+               return;
+
+       if ((dco.last_set && (dco.from_set || dco.from_set)) || (dco.to_set && !dco.from_set)) {
+               _E("Incorrect parameters set");
+               dprintf(fd, "Incorrect parameters set.\n");
+               return;
+       }
+
+       switch(dco.report_type) {
+       case BR_CRASHINFO:
+               write_crash_info(fd, dco.from, dco.to);
+               break;
+       case BR_BUGREPORT:
+               write_bugreport(fd, dco.from, dco.to);
+               break;
+       default:
+               _E("Unknown report type\n");
+       }
+}
+
+bool diagnostics_init()
+{
+       diagnostics_set_client_id("org.tizen.bugreport-service");
+       if (diagnostics_set_data_request_cb(&diagnostics_callback, NULL) != DIAGNOSTICS_ERROR_NONE) {
+               _E("Unable to register diagnostics callback");
+               return false;
+       }
+
+       return true;
+}
+
diff --git a/src/bugreport-service/diagnostics/diagnostics_dump.h b/src/bugreport-service/diagnostics/diagnostics_dump.h
new file mode 100644 (file)
index 0000000..a277463
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#define LOG_TAG "BUGREPORT-SERVICE"
+
+bool diagnostics_init();
diff --git a/src/bugreport-service/org.tizen.dumpsys.providers.org.tizen.bugreport-service.service b/src/bugreport-service/org.tizen.dumpsys.providers.org.tizen.bugreport-service.service
new file mode 100644 (file)
index 0000000..8317dea
--- /dev/null
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.tizen.dumpsys.providers.org.tizen.bugreport-service
+Exec=/bin/false
+SystemdService=bugreport.service