Add PID information as a new type of bugreport
[platform/core/system/crash-worker.git] / src / bugreport-service / diagnostics / diagnostics_dump.c
index f1fb212..ff35141 100644 (file)
@@ -33,7 +33,7 @@
 #include "shared/log.h"
 #include "shared/util.h"
 
-enum BUGREPORT_REPORT_TYPE { BR_UNKNOWN, BR_BUGREPORT, BR_CRASHINFO };
+enum BUGREPORT_REPORT_TYPE { BR_UNKNOWN, BR_BUGREPORT, BR_CRASHINFO, BR_CRASHINFO_JSON, BR_COREDUMP_TAR, BR_FULLREPORT, BR_PID };
 
 struct report_file {
        char *file_name;
@@ -54,33 +54,50 @@ static bool get_reports_list(const char *path, struct report_file **list, size_t
                return false;
        }
 
+       char file_path[PATH_MAX];
+       bool ret = false;
+
        while ((ep = readdir(dp))) {
-               if (ep->d_type != 8)
+               if (ep->d_type != DT_REG)
                        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;
+               int r = snprintf(file_path, sizeof file_path, "%s/%s", path, ep->d_name);
+               if (r == -1 || r >= sizeof file_path) {
+                       _E("Error while preparing report path: %m");
+                       goto out;
                }
                if (stat(file_path, &st) != 0) {
                        _E("Get stats about %s error: %m", path);
-                       return false;
+                       goto out;
                }
                struct tm t;
                if (localtime_r(&(st.st_ctim.tv_sec), &t) == NULL) {
                        _E("localtime_r error: %m");
-                       return false;
+                       goto out;
+               }
+
+               struct report_file *tmp_list = realloc(*list, sizeof(struct report_file)*(*list_count + 1));
+               char *fname = strdup(ep->d_name);
+               char *fpath = strdup(file_path);
+               if (tmp_list == NULL || fname == NULL || fpath == NULL) {
+                       _E("Out of memory");
+                       free(fname);
+                       free(fpath);
+                       free(tmp_list);
+                       goto out;
                }
 
-               *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 = tmp_list;
+               (*list)[*list_count].file_name = fname;
+               (*list)[*list_count].file_path = fpath;
                (*list)[*list_count].ctime = st.st_ctim;
                (*list_count)++;
        }
+
+       ret = true;
+out:
        closedir(dp);
-       return true;
+       return ret;
 }
 
 static void free_record(struct report_file *record)
@@ -115,8 +132,10 @@ 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)
+static struct report_file* get_reports(size_t *count)
 {
+       assert(count);
+
        struct report_file *list = NULL;
        *count = 0;
 
@@ -132,12 +151,18 @@ static struct report_file* get_reports(long time_from, long time_to, size_t *cou
                }
        }
 
-       filter_time(list, count, time_from, time_to);
-
        qsort(list, *count, sizeof(struct report_file), ctime_compare);
        return list;
 }
 
+static struct report_file* get_reports_from_range(long time_from, long time_to, size_t *count)
+{
+       struct report_file *list = get_reports(count);
+
+       filter_time(list, count, time_from, time_to);
+       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);
@@ -167,11 +192,9 @@ 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");
+       struct report_file *list = get_reports_from_range(time_from, time_to, &list_count);
+       if (list == NULL)
                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) {
@@ -196,26 +219,37 @@ out:
        return result;
 }
 
-static bool write_crash_info(int fd, long time_from, long time_to)
+static bool write_crash_info(int fd, long time_from, long time_to, bool as_json)
 {
        bool result = true;
        size_t list_count = 0;
 
-       struct report_file *list = get_reports(time_from, time_to, &list_count);
+       struct report_file *list = get_reports_from_range(time_from, time_to, &list_count);
        if (list == NULL) {
-               dprintf(fd, "Internal error.\n");
+               if (as_json)
+                       dprintf(fd, "{\"error\": \"Internal error.\"}");
+               else
+                       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);
+
+       const enum diagnostics_entry entry_type = as_json ? INFO_JSON : INFO_FILE;
+       for (int i = 0, p = 0; i < list_count; i++) {
+               int nfd = diagnostics_report_get_file(list[i].file_path, entry_type);
                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) {
+               int res = 0;
+               if (as_json)
+                       res = dprintf(fd, "%c\n", (p == 0) ? '[' : ',');
+               else
+                       res = dprintf(fd, "%sBegin crash-info <%s>\n", (i > 0) ? "\n" : "", list[i].file_path);
+
+               if (res == -1) {
                        _E("Write error: %m");
                        result = false;
                        goto out;
@@ -227,10 +261,17 @@ static bool write_crash_info(int fd, long time_from, long time_to)
                        result = false;
                        goto out;
                }
+               p++;
 
                close(nfd);
 
-               if (dprintf(fd, "End crash-info <%s>\n", list[i].file_path) == -1) {
+               if (as_json) {
+                       if (i == list_count-1)
+                               res = dprintf(fd, "]\n");
+               } else {
+                       res = dprintf(fd, "End crash-info <%s>\n", list[i].file_path);
+               }
+               if (res == -1) {
                        _E("Write error: %m");
                        result = false;
                        goto out;
@@ -244,30 +285,271 @@ out:
        return result;
 }
 
+static struct report_file *find_report_file(const char *ident, struct report_file *list, size_t list_count)
+{
+       assert(ident);
+       assert(list);
+
+       for (size_t i = 0; i < list_count; i++) {
+               char *dot = rindex(list[i].file_name, '.');
+               if (dot == NULL)
+                       continue;
+               if (strncmp(ident, list[i].file_name, dot - list[i].file_name) == 0)
+                       return &list[i];
+       }
+       return NULL;
+}
+
+static bool parse_pid_from_report_file(int report_fd, char *str)
+{
+       FILE *fp;
+       char buff[1024];
+       int pid = -1;
+
+       fp = fdopen(report_fd, "r");
+
+       while (fgets(buff, sizeof(buff), fp)) {
+               if (sscanf(buff, "PID = %d", &pid) == 1)
+                       break;
+       }
+       if (pid < 0)
+               return false;
+
+       sprintf(str, "%d", pid);
+
+       return true;
+}
+
+static bool write_pid(int fd, int in_fd)
+{
+       char pid[10];
+
+       if (!parse_pid_from_report_file(in_fd, pid)) {
+               dprintf(fd, "Internal error\n");
+               return false;
+       }
+
+       if (write(fd, pid, strlen(pid)) < 0) {
+               dprintf(fd, "Internal error: %m\n");
+               return false;
+       }
+
+       return true;
+}
+
+static bool write_blank(int fd)
+{
+       if (write(fd, " ", 1) < 0) {
+               dprintf(fd, "Internal error: %m\n");
+               return false;
+       }
+
+       return true;
+}
+
+static bool write_pids(int fd, long time_from, long time_to)
+{
+       bool result = true;
+       size_t list_count = 0;
+       struct report_file *list = get_reports_from_range(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 (!write_pid(fd, nfd)) {
+                       _E("write_pid error");
+                       close(nfd);
+                       result = false;
+                       break;
+               }
+
+               close(nfd);
+
+               if (!write_blank(fd)) {
+                       _E("write_blank error");
+                       result = false;
+                       break;
+               }
+       }
+
+       for (int i = 0; i < list_count; i++)
+               free_record(&list[i]);
+       free(list);
+
+       return result;
+}
+
+static bool write_single_file(int fd, enum BUGREPORT_REPORT_TYPE report_type, const char *ident)
+{
+       assert(fd >= 0);
+       assert(ident);
+
+       bool result = true;
+       size_t list_count = 0;
+       struct report_file *list = get_reports(&list_count);
+       if (list == NULL) {
+               if (report_type == BR_CRASHINFO_JSON)
+                       dprintf(fd, "{\"error\": \"Internal error.\"}");
+               else
+                       dprintf(fd, "Internal error\n");
+               return false;
+       }
+
+       struct report_file *report = find_report_file(ident, list, list_count);
+       if (report == NULL) {
+               result = false;
+               goto out;
+       }
+
+       enum diagnostics_entry report_entry = INFO_FILE;
+       switch (report_type) {
+       case BR_BUGREPORT:
+               report_entry = LOG_FILE;
+               break;
+       case BR_CRASHINFO:
+               report_entry = INFO_FILE;
+               break;
+       case BR_CRASHINFO_JSON:
+               report_entry = INFO_JSON;
+               break;
+       case BR_COREDUMP_TAR:
+               report_entry = COREDUMP_TAR;
+               break;
+       case BR_FULLREPORT:
+               report_entry = FULL_REPORT;
+               break;
+       case BR_PID:
+               report_entry = INFO_PID;
+               break;
+       default:
+               _E("Unsupported report type: %d", report_type);
+               result = false;
+               goto out;
+       }
+
+       int in_fd = diagnostics_report_get_file(report->file_path, report_entry);
+       if (in_fd < 0) {
+               _E("Can not get report type: %d from: %s (report ident: %s)", report_type, report->file_path, ident);
+               result = false;
+               goto out;
+       }
+
+       if (report_entry == INFO_PID)
+               write_pid(fd, in_fd);
+       else if (copy_bytes(fd, in_fd, NULL) == -1) {
+               _E("Copy data error");
+               result = false;
+               goto out;
+       }
+out:
+       for (size_t 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;
+       char *arg;
 };
 
-static bool diagnostics_call_parse_options(int out_fd, char **params, int params_size, struct diagnostics_call_options *dco)
+static void diagnostics_print_help(int out_fd)
+{
+       dprintf(out_fd, "Usage:\n\n"
+       "  dumpsys org.tizen.bugreport-service [--type=<report_type>] [[--last <seconds> |--from <timestamp> [--to <timestamp>]]|[report_ident]]\n\n"
+       "  --type <report_type>    Specify the type of report. Available types: bugreport, crash-info, crash-info-json. Additional types are available\n"
+       "                          if single report is specified by <report_ident>: fullreport, coredumptar (default: bugreport).\n"
+       "  --last <seconds>        Get reports generated in the last <seconds>.\n"
+       "  --from <timestamp>      Get reports generated after specified time.\n"
+       "  --to <timestamp>        Get reports generated before specified time (default: current time).\n"
+       "  report_ident            Get a specified report.\n\n"
+       "  If no time range or report_ident is specified, all reports will be returned.\n\n"
+       );
+}
+
+static bool get_file_from_ctx(diagnostics_ctx_h ctx, char **report_file)
+{
+       assert(report_file);
+
+       if (ctx == NULL)
+               return false;
+
+       bool result = false;
+       char *event_name = NULL;
+       bundle *event_data = NULL;
+
+       int ret = diagnostics_get_event_name(ctx, &event_name);
+       if (ret != DIAGNOSTICS_ERROR_NONE) {
+               _E("Get event name error: %d", ret);
+               goto out;
+       }
+       if (strcmp(event_name, "BugreportCreated") != 0) {
+               _E("Unknown context: %s", event_name);
+               goto out;
+       }
+
+       ret = diagnostics_get_event_data(ctx, &event_data);
+       if (ret != DIAGNOSTICS_ERROR_NONE) {
+               _E("Get event data error: %d", ret);
+               goto out;
+       }
+
+       char *tmp_report_file;
+       ret = bundle_get_str(event_data, "report_file", &tmp_report_file);
+       if (ret != BUNDLE_ERROR_NONE) {
+               _E("Can not get report_file from event data: %d", ret);
+               goto out;
+       }
+
+       *report_file = strdup(tmp_report_file);
+       if (*report_file == NULL) {
+               _E("Out of memory: %m");
+               goto out;
+       }
+       _D("Report file from event data: %s", *report_file);
+
+       result = true;
+out:
+       if (event_name != NULL)
+               free(event_name);
+       if (event_data != NULL)
+               bundle_free(event_data);
+
+       return result;
+}
+
+static bool diagnostics_call_parse_options(int out_fd, char **params, int params_size, diagnostics_ctx_h ctx, struct diagnostics_call_options *dco)
 {
        struct timespec cur_time;
        clock_gettime(CLOCK_REALTIME, &cur_time);
-       dco->report_type = BR_UNKNOWN;
+       dco->report_type = BR_BUGREPORT;
        dco->from = 0;
        dco->to = cur_time.tv_sec;
        dco->last_set = false;
        dco->from_set = false;
        dco->to_set = false;
+       dco->arg = NULL;
 
-       enum PARAM_NAME { PN_TYPE = 1, PN_LAST, PN_TO, PN_FROM};
+       enum PARAM_NAME { PN_TYPE = 1, PN_LAST, PN_TO, PN_FROM, PN_HELP };
 
        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},
+               {"help", no_argument, NULL, PN_HELP},
+               {0, 0, 0, 0},
        };
 
        int opt;
@@ -277,13 +559,21 @@ static bool diagnostics_call_parse_options(int out_fd, char **params, int params
        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) {
+               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 {
+                       } else if (strcmp(optarg, "crash-info-json") == 0) {
+                               dco->report_type = BR_CRASHINFO_JSON;
+                       } else if (strcmp(optarg, "coredumptar") == 0) {
+                               dco->report_type = BR_COREDUMP_TAR;
+                       } else if (strcmp(optarg, "fullreport") == 0) {
+                               dco->report_type = BR_FULLREPORT;
+                       } else if (strcmp(optarg, "pid") == 0) {
+                               dco->report_type = BR_PID;
+                       }else {
                                _E("Incorrect report type: %s", optarg);
                                dprintf(out_fd, "Incorrect report type: %s\n", optarg);
                                return false;
@@ -301,13 +591,26 @@ static bool diagnostics_call_parse_options(int out_fd, char **params, int params
                        dco->to_set = true;
                        dco->to = strtol(optarg, NULL, 10);
                        break;
+               case PN_HELP:
+                       diagnostics_print_help(out_fd);
+                       return false;
                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>");
+                       _E("Incorrect option: %s", (optind > 0 && optind - 1 < params_size + 1) ? nparams[optind-1] : "<unknown>");
+                       dprintf(out_fd, "Incorrect option: %s\n", (optind > 0 && optind - 1 < params_size + 1) ? nparams[optind-1] : "<unknown>");
                        return false;
                }
        }
 
+       if (optind < params_size+1) {
+               dco->arg = strdup(nparams[optind]);
+               if (dco->arg == NULL) {
+                       _E("Out of memory: %m");
+                       return false;
+               }
+       } else {
+               get_file_from_ctx(ctx, &dco->arg);
+       }
+
        return true;
 }
 
@@ -321,25 +624,50 @@ static void diagnostics_callback(diagnostics_data_h data, char **params, int par
        }
        struct diagnostics_call_options dco;
 
-       if (!diagnostics_call_parse_options(fd, params, params_size, &dco))
+       if (!diagnostics_call_parse_options(fd, params, params_size, ctx, &dco))
                return;
 
-       if ((dco.last_set && (dco.from_set || dco.from_set)) || (dco.to_set && !dco.from_set)) {
+       if ((dco.last_set && (dco.from_set || dco.to_set)) || (dco.to_set && !dco.from_set)) {
                _E("Incorrect parameters set");
                dprintf(fd, "Incorrect parameters set.\n");
-               return;
+               goto out;
+       }
+
+       if (dco.arg != NULL) {
+               if (dco.last_set || dco.from_set || dco.to_set) {
+                       _E("Incorrect parameters set.");
+                       dprintf(fd, "Incorrect parameters set.\n");
+                       goto out;
+               }
+               write_single_file(fd, dco.report_type, dco.arg);
+               goto out;
        }
 
        switch(dco.report_type) {
        case BR_CRASHINFO:
-               write_crash_info(fd, dco.from, dco.to);
+               write_crash_info(fd, dco.from, dco.to, false);
+               break;
+       case BR_CRASHINFO_JSON:
+               write_crash_info(fd, dco.from, dco.to, true);
                break;
        case BR_BUGREPORT:
                write_bugreport(fd, dco.from, dco.to);
                break;
+       case BR_COREDUMP_TAR:
+               dprintf(fd, "Report type \"coredumptar\" requires a specific report with report_ident argument.\n");
+               break;
+       case BR_FULLREPORT:
+               dprintf(fd, "Report type \"fullreport\" requires a specific report with report_ident argument.\n");
+               break;
+       case BR_PID:
+               write_pids(fd, dco.from, dco.to);
+               break;
        default:
                _E("Unknown report type\n");
        }
+out:
+       if (dco.arg != NULL)
+               free(dco.arg);
 }
 
 bool diagnostics_init()