--- /dev/null
+/*
+ * 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, ¤t_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;
+}
+