/* * crash-manager * * Copyright (c) 2016 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef LOG_TAG #define LOG_TAG "CRASH_MANAGER" #include "crash-manager.h" #include "dbus_notify.h" #include "shared/log.h" #include "shared/spawn.h" #include "shared/util.h" #include "so-info.h" /* Parsing */ #define KEY_MAX 255 #define CRASH_SECTION "CrashManager" /* Crash-popup dbus */ #define POPUP_BUS_NAME "org.tizen.system.popup" #define POPUP_OBJECT_PATH "/Org/Tizen/System/Popup/Crash" #define POPUP_INTERFACE_NAME POPUP_BUS_NAME".Crash" #define POPUP_METHOD "PopupLaunch" /* Configuration default values */ /* note: 0 means unlimited */ #define SYSTEM_MAX_USE 0 #define SYSTEM_KEEP_FREE 0 #define MAX_RETENTION_SEC 0 #define MAX_CRASH_DUMP 0 #define DUMP_CORE 1 #define ALLOW_ZIP true #define APPID_MAX 128 #define PKGNAME_MAX 128 #define CRASH_TEMP_SUBDIR "/temp/" #define CRASH_PATH_SUBDIR "/dump/" #define WAIT_FOR_OPT_TIMEOUT_SEC 60 #define MINICOREDUMPER_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS #define CRASH_STACK_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS #define ZIP_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS enum { RET_EXCEED = 1, NUM_EXCEED, USAGE_EXCEED }; enum { REP_TYPE_INFO = 0, REP_TYPE_FULL }; #define REP_DEFAULT_TYPE REP_TYPE_FULL #define REP_TYPE_FULL_STR "FULL" #define REP_TYPE_INFO_STR "INFO" struct file_info { bool isdir; size_t size; time_t mtime; char *path; }; /* Configuration variables */ static int system_max_use; static int system_keep_free; static int max_retention_sec; static int max_crash_dump; static int dump_core; static bool allow_zip; static char* crash_root_path; static char* crash_crash_path; static char* crash_temp_path; static int report_type; /* Paths and variables */ struct crash_info { pid_t pid_info; pid_t tid_info; int uid_info; int gid_info; int sig_info; char *cmd_line; char *cmd_path; time_t time_info; char *temp_dir; char *name; char *result_path; char *pfx; char *info_path; char *core_path; char *log_path; char appid[APPID_MAX]; char pkgid[PKGNAME_MAX]; #ifdef SYS_ASSERT char *sysassert_cs_path; bool have_sysassert_report; #endif int prstatus_fd; }; /* pkgmgrinfo filter list function for getting application ID */ static int appinfo_get_appid_func(pkgmgrinfo_appinfo_h handle, void *user_data) { char *str = NULL; int ret = pkgmgrinfo_appinfo_get_appid(handle, &str); if (ret == PMINFO_R_OK && str) (*(char **)user_data) = strdup(str); return ret; } /* get application ID by pkgmgrinfo filter */ static int get_appid(char *exepath, char *appid, int len) { pkgmgrinfo_appinfo_filter_h handle = NULL; int count, ret; char *aid = NULL; ret = pkgmgrinfo_appinfo_filter_create(&handle); if (ret != PMINFO_R_OK) { ret = -1; goto out; } ret = pkgmgrinfo_appinfo_filter_add_string(handle, PMINFO_APPINFO_PROP_APP_EXEC, exepath); if (ret != PMINFO_R_OK) { ret = -1; goto out_free; } ret = pkgmgrinfo_appinfo_filter_count(handle, &count); if (ret != PMINFO_R_OK) { ret = -1; goto out_free; } if (count < 1) { ret = -1; goto out_free; } ret = pkgmgrinfo_appinfo_filter_foreach_appinfo(handle, appinfo_get_appid_func, &aid); if (ret != PMINFO_R_OK) { ret = -1; goto out_free; } if (aid) { snprintf(appid, len, "%s", aid); ret = 0; free(aid); } out_free: pkgmgrinfo_appinfo_filter_destroy(handle); out: return ret; } /* get package ID by appid */ static int get_pkgid(char *appid, char *pkgid, int len) { pkgmgrinfo_appinfo_h handle = NULL; int ret; char *pkid = NULL; ret = pkgmgrinfo_appinfo_get_appinfo(appid, &handle); if (ret != PMINFO_R_OK) { ret = -1; goto out; } ret = pkgmgrinfo_appinfo_get_pkgid(handle, &pkid); if (ret != PMINFO_R_OK) { ret = -1; goto out_free; } snprintf(pkgid, len, "%s", pkid); out_free: pkgmgrinfo_appinfo_destroy_appinfo(handle); out: return ret; } static int prepare_paths(void) { int tmp_len; tmp_len = strlen(crash_root_path) + strlen(CRASH_PATH_SUBDIR); crash_crash_path = (char*)malloc(tmp_len + 1); if (crash_crash_path == NULL) { _E("Couldn't allocate memory for crash_crash_path: %m\n"); return 0; } snprintf(crash_crash_path, tmp_len + 1, "%s%s", crash_root_path, CRASH_PATH_SUBDIR); tmp_len = strlen(crash_root_path) + strlen(CRASH_TEMP_SUBDIR); crash_temp_path = (char*)malloc(tmp_len + 1); if (crash_temp_path == NULL) { _E("Couldn't allocate memory for crash_temp_path: %m\n"); return 0; } snprintf(crash_temp_path, tmp_len + 1, "%s%s", crash_root_path, CRASH_TEMP_SUBDIR); return 1; } static const char* report_type_to_str(const int report_type) { switch (report_type) { case REP_TYPE_INFO: return REP_TYPE_INFO_STR; break; case REP_TYPE_FULL: return REP_TYPE_FULL_STR; default: return NULL; break; } } static int report_type_from_str(const char* report_type_str) { if (report_type_str == NULL) return -1; if (strncmp(report_type_str, REP_TYPE_FULL_STR, strlen(REP_TYPE_FULL_STR)) == 0) return REP_TYPE_FULL; else if (strncmp(report_type_str, REP_TYPE_INFO_STR, strlen(REP_TYPE_INFO_STR)) == 0) return REP_TYPE_INFO; return -1; } static int get_config(void) { dictionary *ini = NULL; char key[KEY_MAX]; int value; int result = 1; char *value_str; system_max_use = SYSTEM_MAX_USE; system_keep_free = SYSTEM_KEEP_FREE; max_retention_sec = MAX_RETENTION_SEC; max_crash_dump = MAX_CRASH_DUMP; dump_core = DUMP_CORE; allow_zip = ALLOW_ZIP; crash_root_path = strdup(CRASH_ROOT_PATH); if (crash_root_path == NULL) { _E("strdup error: %m\n"); return -1; } report_type = REP_DEFAULT_TYPE; ini = iniparser_load(CRASH_MANAGER_CONFIG_PATH); if (!ini) { _E("Failed to load conf file %s", CRASH_MANAGER_CONFIG_PATH); return 0; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "SystemMaxUse"); value = iniparser_getint(ini, key, -1); if (value < 0) { _D("Invalid value for SystemMaxUse. Use default value [ %d kbyte]", SYSTEM_MAX_USE); } else { _D("SystemMaxUse [ %d kbyte]", value); system_max_use = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "SystemKeepFree"); value = iniparser_getint(ini, key, -1); if (value < 0) { _D("Invalid value for SystemKeepFree. Use default value [ %d kbyte]", SYSTEM_KEEP_FREE); } else { _D("SystemKeepFree [ %d kbyte]", value); system_keep_free = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "MaxRetentionSec"); value = iniparser_getint(ini, key, -1); if (value < 0) { _D("Invalid value for MaxRetentionSec. Use default value [ %d ]", MAX_RETENTION_SEC); } else { _D("MaxRetentionSec [ %d ]", value); max_retention_sec = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "MaxCrashDump"); value = iniparser_getint(ini, key, -1); if (value < 0) { _D("Invalid value for MaxCrashDump. Use default value [ %d ]", MAX_CRASH_DUMP); } else { _D("MaxCrashDump [ %d ]", value); max_crash_dump = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "DumpCore"); value = iniparser_getint(ini, key, -1); if (value != 0 && value != 1) { _D("Invalid value for DumpCore default value [ %d ]", DUMP_CORE); } else { _D("DumpCore [ %d ]", value); dump_core = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "AllowZip"); value = iniparser_getboolean(ini, key, -1); if (value < 0) { _D("Invalid value for AllowZip. Use default value [ %s ]", ALLOW_ZIP ? "true" : "false"); } else { _D("AllowZip [ %s ]", value ? "true" : "false"); allow_zip = value; } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "CrashRootPath"); value_str = iniparser_getstring(ini, key, NULL); if (value_str == NULL) { _D("Invalid value for CrashRootPath. Use default value [ %s ]", CRASH_ROOT_PATH); } else { _D("CrashRootPath [ %s ]", value_str); free(crash_root_path); crash_root_path = strdup(value_str); if (crash_root_path == NULL) { _E("strdup error: %m\n"); result = -1; goto out; } } snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "ReportType"); value_str = iniparser_getstring(ini, key, NULL); if (value_str == NULL) { _D("Invalid value for ReportType. Use default value [ %s ]", report_type_to_str(report_type)); } else { _D("ReportType [ %s ]", value_str); report_type = report_type_from_str(value_str); if (report_type < 0) { _E("Unknown ReportType %s. Fallback to default: %s", value_str, report_type_to_str(REP_DEFAULT_TYPE)); report_type = REP_DEFAULT_TYPE; } } out: iniparser_freedict(ini); return result; } static int make_dump_dir(void) { struct stat st; if (!stat(crash_crash_path, &st)) { if (!(st.st_mode & S_IFDIR)) { _E("%s (not DIR) is already exist", crash_crash_path); return -1; } } else { if (mkdir(crash_crash_path, 0775) < 0) { _E("Failed to mkdir for %s", crash_crash_path); return -1; } } if (!stat(crash_temp_path, &st)) { if (!(st.st_mode & S_IFDIR)) { _E("%s (not DIR) is already exist", crash_temp_path); return -1; } } else { if (mkdir(crash_temp_path, 0775) < 0) { _E("Failed to mkdir for %s", crash_temp_path); return -1; } } return 0; } static int get_cmd_info(struct crash_info *cinfo) { cinfo->cmd_line = get_cmd_line(cinfo->pid_info); cinfo->cmd_path = get_exe_path(cinfo->pid_info); return (cinfo->cmd_line != NULL && cinfo->cmd_path != NULL); } static int set_prstatus(struct crash_info *cinfo) { int ret; char prstatus_name[NAME_MAX+1]; ret = snprintf(prstatus_name, NAME_MAX, "/%d.prstatus", cinfo->pid_info); if (ret < 0) { _E("Failed to snprintf for prstatus path"); goto close_fd; } cinfo->prstatus_fd = shm_open(prstatus_name, O_RDWR | O_CREAT, 0600); if (cinfo->prstatus_fd < 0) { _E("shm_open: %m"); goto close_fd; } ret = shm_unlink(prstatus_name); if (ret < 0) { _E("shm_unlink: %m"); goto close_fd; } ret = fcntl(cinfo->prstatus_fd, F_GETFD); if (ret < 0) { _E("fcntl(): %m"); goto close_fd; } ret = fcntl(cinfo->prstatus_fd, F_SETFD, ret & ~FD_CLOEXEC); if (ret < 0) { _E("fcntl(): %m"); goto close_fd; } ret = ftruncate(cinfo->prstatus_fd, sizeof(struct elf_prstatus)); if (ret < 0) { _E("ftruncate(): %m"); goto close_fd; } return 0; close_fd: if (cinfo->prstatus_fd >= 0) close(cinfo->prstatus_fd); return -1; } static int set_crash_info(struct crash_info *cinfo, int argc, char *argv[]) { int ret; char *temp_dir_ret = NULL; char date[80]; struct tm loc_tm; cinfo->pid_info = strtol(argv[1], NULL, 10); cinfo->sig_info = atoi(argv[4]); if (argc > 6) cinfo->tid_info = strtol(argv[6], NULL, 10); else { cinfo->tid_info = find_crash_tid(cinfo->pid_info); if (cinfo->tid_info < 0) { _I("TID not found"); cinfo->tid_info = cinfo->pid_info; } } ret = get_cmd_info(cinfo); if (ret <= 0) { _E("Failed to get command info"); return -1; } cinfo->time_info = strtol(argv[5], NULL, 10); localtime_r(&cinfo->time_info, &loc_tm); strftime(date, sizeof(date), "%Y%m%d%H%M%S", &loc_tm); if (asprintf(&cinfo->temp_dir, "%s/crash.XXXXXX", crash_temp_path) == -1) { _E("Failed to asprintf for temp_dir"); cinfo->temp_dir = NULL; return -1; } temp_dir_ret = mkdtemp(cinfo->temp_dir); if (!temp_dir_ret || access(temp_dir_ret, F_OK)) { _E("Failed to mkdtemp for temp_dir"); return -1; } if (asprintf(&cinfo->name, "%s_%d_%s", basename(cinfo->cmd_line), cinfo->pid_info, date) == -1) { _E("Failed to snprintf for name"); cinfo->name = NULL; goto rm_temp; } if (allow_zip) ret = asprintf(&cinfo->result_path, "%s/%s.zip", crash_crash_path, cinfo->name); else ret = asprintf(&cinfo->result_path, "%s/%s", crash_crash_path, cinfo->name); if (ret == -1) { _E("Failed to asprintf for result path"); cinfo->result_path = NULL; goto rm_temp; } if (asprintf(&cinfo->pfx, "%s/%s", cinfo->temp_dir, cinfo->name) == -1) { _E("Failed to asprintf for pfx"); cinfo->pfx = NULL; goto rm_temp; } ret = mkdir(cinfo->pfx, 0775); if (ret < 0) { _E("Failed to mkdir for %s", cinfo->pfx); goto rm_temp; } if (asprintf(&cinfo->info_path, "%s/%s.info", cinfo->pfx, cinfo->name) == -1) { _E("Failed to asprintf for info path"); cinfo->info_path = NULL; goto rm_temp; } if (asprintf(&cinfo->core_path, "%s/%s.coredump", cinfo->pfx, cinfo->name) == -1) { _E("Failed to asprintf for core path"); cinfo->core_path = NULL; goto rm_temp; } if (asprintf(&cinfo->log_path, "%s/%s.log", cinfo->pfx, cinfo->name) == -1) { _E("Failed to asprintf for log path"); cinfo->log_path = NULL; goto rm_temp; } #ifdef SYS_ASSERT if (asprintf(&cinfo->sysassert_cs_path, "/tmp/crash_stack/%s_%d.info", basename(cinfo->cmd_line), cinfo->pid_info) == -1) { _E("Failed to snprintf for sys-assert callstack path"); cinfo->sysassert_cs_path = NULL; goto rm_temp; } #endif if (set_prstatus(cinfo) < 0) goto rm_temp; if (get_appid(cinfo->cmd_line, cinfo->appid, sizeof(cinfo->appid)) < 0) { snprintf(cinfo->appid, sizeof(cinfo->appid), "%s", basename(cinfo->cmd_line)); snprintf(cinfo->pkgid, sizeof(cinfo->pkgid), "%s", basename(cinfo->cmd_line)); } else { if (get_pkgid(cinfo->appid, cinfo->pkgid, sizeof(cinfo->pkgid)) < 0) snprintf(cinfo->pkgid, sizeof(cinfo->pkgid), "%s", cinfo->appid); } return 0; rm_temp: remove_dir(cinfo->temp_dir, 1); return -1; } #ifdef SYS_ASSERT static int get_sysassert_cs(struct crash_info *cinfo) { int ret; char move_path[PATH_MAX]; if (access(cinfo->sysassert_cs_path, F_OK)) { _E("The sys-assert cs file not found: %s", cinfo->sysassert_cs_path); cinfo->have_sysassert_report = 0; return -1; } else cinfo->have_sysassert_report = 1; ret = snprintf(move_path, sizeof(move_path), "%s/%s", cinfo->pfx, basename(cinfo->sysassert_cs_path)); if (ret < 0) { _E("Failed to snprintf for move path"); return -1; } if (move_file(move_path, cinfo->sysassert_cs_path) < 0) { _E("Failed to move %s to %s", cinfo->sysassert_cs_path, move_path); return -1; } return 0; } #endif static void launch_crash_popup(struct crash_info *cinfo) { GDBusConnection *conn; GVariantBuilder *builder; GVariant *parameters = NULL; GVariant *reply = NULL; GError *error = NULL; int ret; conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (error) { _E("Failed to get dbus: %s", error->message); g_error_free(error); return; } builder = g_variant_builder_new(G_VARIANT_TYPE("a{ss}")); g_variant_builder_add(builder, "{ss}", "_SYSPOPUP_CONTENT_", "crash"); g_variant_builder_add(builder, "{ss}", "_PROCESS_NAME_", basename(cinfo->cmd_line)); g_variant_builder_add(builder, "{ss}", "_EXEPATH_", cinfo->cmd_path); parameters = g_variant_new("(a{ss})", builder); g_variant_builder_unref(builder); reply = g_dbus_connection_call_sync(conn, POPUP_BUS_NAME, POPUP_OBJECT_PATH, POPUP_INTERFACE_NAME, POPUP_METHOD, parameters, G_VARIANT_TYPE("(i)"), G_DBUS_CALL_FLAGS_NONE, 120000, NULL, &error); if (error) { _E("Failed to get reply: %s", error->message); g_error_free(error); goto exit; } g_variant_get(reply, "(i)", &ret); _I("Crash_popup is launched: (%d)", ret); exit: if (reply) g_variant_unref(reply); if (parameters) g_variant_unref(parameters); g_object_unref(conn); } static bool dump_system_state(const struct crash_info *cinfo, pid_t *pid) { char *av[] = {"/usr/bin/dump_systemstate", "-d", "-k", "-j", "-p", "-f", cinfo->log_path, NULL}; return spawn(av, NULL, NULL, NULL, pid, NULL); } static void save_so_info(const struct crash_info *cinfo) { char maps_path[PATH_MAX]; char so_info_path[PATH_MAX]; snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", cinfo->pid_info); snprintf(so_info_path, sizeof(so_info_path), "%s/%s.%s", cinfo->pfx, cinfo->name, "so_info"); get_and_save_so_info(maps_path, so_info_path); } /* remove a file whose name begins with 'beggining_of_name'. This is needed * when we don't want to include coredump in report, but we only know the * beginning of the coredump file name. */ static int remove_file_in_dir(const char *directory, const char *beginning_of_name) { int ret = -1; int errno_unlink = 0; DIR *dir = opendir(directory); if (dir == NULL) goto end; int dir_fd = dirfd(dir); if (dir_fd == -1) { closedir(dir); goto end; } struct dirent* file_info; while ((file_info = readdir(dir)) != NULL) { if (strstr(file_info->d_name, beginning_of_name) == file_info->d_name) { ret = unlinkat(dir_fd, file_info->d_name, 0); errno_unlink = errno; break; } } closedir(dir); errno = errno_unlink; /* for %m */ end: return ret; } // These macros are used in execute_minicoredump() and execute_crash_stack() #define SNPRINTF_OR_EXIT_W(name, format, member) if (snprintf(name##_str, sizeof(name##_str), format, cinfo->member) < 0) goto out; #define SNPRINTF_OR_EXIT(name, format) SNPRINTF_OR_EXIT_W(name, format, name##_info) static bool execute_minicoredump(struct crash_info *cinfo, int *exit_code) { char *coredump_name = NULL; char *prstatus_fd_str = NULL; bool is_ok = false; if (asprintf(&coredump_name, "%s.coredump", cinfo->name) == -1 || asprintf(&prstatus_fd_str, "%d", cinfo->prstatus_fd) == -1) { _E("Unable to allocate memory"); goto out; } char pid_str[11], uid_str[11], gid_str[11], sig_str[11], time_str[11]; SNPRINTF_OR_EXIT(pid, "%d") SNPRINTF_OR_EXIT(uid, "%d") SNPRINTF_OR_EXIT(gid, "%d") SNPRINTF_OR_EXIT(sig, "%d") SNPRINTF_OR_EXIT(time, "%ld") /* Execute minicoredumper */ char *args[] = { MINICOREDUMPER_BIN_PATH, // minicoredumper filename path pid_str, // %p - pid uid_str, // %u - UID gid_str, // %g - GID sig_str, // %s - number of signal time_str, // %t - time of dump "localhost", // %h - hostname "core", // %e - exe name (need for result filename) MINICOREDUMPER_CONFIG_PATH, // config file "-d", cinfo->pfx, // temp dir "-o", coredump_name, // coredump filename "-P", prstatus_fd_str, NULL }; is_ok = spawn_wait(args, NULL, NULL, NULL, MINICOREDUMPER_TIMEOUT_MS, exit_code); /* Minicoredumper must be executed to dump at least PRSTATUS for other tools, coredump, however, might have been disabled. */ if (!dump_core) { if (remove_file_in_dir(cinfo->pfx, coredump_name) != 0) _E("Saving core disabled - removing coredump %s/%s failed: %m", cinfo->pfx, coredump_name); else _D("Saving core disabled - removed coredump %s/%s", cinfo->pfx, coredump_name); } out: free(coredump_name); free(prstatus_fd_str); return is_ok; } static bool execute_crash_stack(const struct crash_info *cinfo, int *exit_code) { char pid_str[11], tid_str[11], sig_str[11], prstatus_fd_str[11]; bool is_ok = false; SNPRINTF_OR_EXIT(pid, "%d") SNPRINTF_OR_EXIT(tid, "%d") SNPRINTF_OR_EXIT(sig, "%d") SNPRINTF_OR_EXIT_W(prstatus_fd, "%d", prstatus_fd) /* Execute crash-stack */ char *args[] = { CRASH_STACK_PATH, "--pid", pid_str, "--tid", tid_str, "--sig", sig_str, "--prstatus_fd", prstatus_fd_str, NULL }; int fd = open(cinfo->info_path, O_WRONLY | O_CREAT, 0600); if (fd < 0) { _E("open %s error: %m", cinfo->info_path); return false; } spawn_param_u param = { .int_val = fd }; is_ok = spawn_wait(args, NULL, spawn_setstdout, ¶m, CRASH_STACK_TIMEOUT_MS, exit_code); close(fd); out: return is_ok; } #undef SNPRINTF_OR_EXIT #undef SNPRINTF_OR_EXIT_W static bool execute_crash_modules(struct crash_info *cinfo) { int exit_code = 0; if (!execute_minicoredump(cinfo, &exit_code) || exit_code != 0) { _E("Failed to run minicoredumper - can not continue"); return false; } #ifdef SYS_ASSERT /* Use process_vm_readv() version as fallback if sys-assert * failed to generate report */ if (cinfo->have_sysassert_report) return false; #endif execute_crash_stack(cinfo, NULL); return true; } static int lock_dumpdir(void) { int fd; if ((fd = open(crash_crash_path, O_RDONLY | O_DIRECTORY)) < 0) { _E("Failed to open %s", crash_crash_path); return -1; } if (flock(fd, LOCK_EX) < 0) { _E("Failed to flock (LOCK)"); close(fd); return -1; } return fd; } static void unlock_dumpdir(int fd) { if (flock(fd, LOCK_UN) < 0) _E("Failed to unlink (UNLOCK)"); close(fd); } static int dump_filter(const struct dirent *de) { if (de->d_name[0] == '.') return 0; return 1; } static int mtime_cmp(const void *_a, const void *_b) { const struct file_info *a = _a; const struct file_info *b = _b; if (a->mtime < b->mtime) return -1; if (a->mtime > b->mtime) return 1; return 0; } static int scan_dump(struct file_info **dump_list, off_t *usage) { struct file_info *temp_list; struct dirent **scan_list = NULL; struct stat st; int i, scan_num, dump_num = 0; int fd; if ((fd = open(crash_crash_path, O_DIRECTORY)) < 0) { _E("Failed to open %s", crash_crash_path); return -1; } scan_num = scandir(crash_crash_path, &scan_list, &dump_filter, NULL); if (scan_num < 0) { close(fd); return -1; } temp_list = (struct file_info *)calloc(scan_num, sizeof(struct file_info)); if (!temp_list) { _E("Failed to calloc for dump list"); goto exit; } for (i = 0; i < scan_num; i++) { if (fstatat(fd, scan_list[i]->d_name, &st, 0) < 0) { _E("Failed to fstatat"); continue; } if (asprintf(&(temp_list[dump_num].path), "%s/%s", crash_crash_path, scan_list[i]->d_name) < 0) { _E("Failed to asprintf"); continue; } if (scan_list[i]->d_type == DT_DIR) { temp_list[dump_num].isdir = 1; temp_list[dump_num].size = get_directory_usage(temp_list[dump_num].path); } else { temp_list[dump_num].isdir = 0; temp_list[dump_num].size = st.st_size; } temp_list[dump_num].mtime = st.st_mtime; dump_num++; } if (dump_num <= 0) { free(temp_list); goto exit; } if (dump_num != scan_num) { struct file_info *const tmp = (struct file_info *)realloc(temp_list, dump_num * sizeof(struct file_info)); if (!tmp) { free(temp_list); dump_num = -1; goto exit; } temp_list = tmp; } qsort(temp_list, dump_num, sizeof(struct file_info), mtime_cmp); *usage = 0; for (i = 0; i < dump_num; i++) { _D("[%d] path: %s(%s), size: %zu kb, mtime: %s", i, temp_list[i].path, temp_list[i].isdir ? "DIR" : "FILE", temp_list[i].size / 1024, ctime(&(temp_list[i].mtime))); *usage += temp_list[i].size; } *dump_list = temp_list; exit: for (i = 0; i < scan_num; i++) free(scan_list[i]); free(scan_list); close(fd); return dump_num; } static int check_disk_available(const char *path, int check_size) { struct statfs lstatfs; int avail_size = 0; if (!path) return -1; if (statfs(path, &lstatfs) < 0) return -1; avail_size = (int)(lstatfs.f_bavail * (lstatfs.f_bsize / 1024)); if (check_size > avail_size) { _I("avail_size is (%d)", avail_size); return -1; } return 0; } static int remove_file_info(struct file_info file) { if (file.isdir) return remove_dir(file.path, 1); else return unlink(file.path); } static void clean_dump(void) { struct file_info *dump_list = NULL; int i, scan_num, dump_num, remove_flag; off_t usage = 0; time_t cur_time; scan_num = scan_dump(&dump_list, &usage); if (scan_num <= 0) return; dump_num = scan_num; cur_time = time(NULL); for (i = 0; i < scan_num; i++) { remove_flag = 0; /* Retention time check */ if (max_retention_sec && dump_list[i].mtime > 0 && dump_list[i].mtime + max_retention_sec < cur_time) remove_flag = RET_EXCEED; /* Check the number of dumps */ else if (max_crash_dump && 0 < dump_num && max_crash_dump < dump_num) remove_flag = NUM_EXCEED; /* Check the max system use size */ else if (system_max_use && 0 < dump_num && system_max_use < usage / 1024) remove_flag = USAGE_EXCEED; switch (remove_flag) { case RET_EXCEED: _I("Reached the maximum retention time %d, so remove (%s)", max_retention_sec, dump_list[i].path); break; case NUM_EXCEED: _I("Reached the maximum number of dump %d/%d, so remove (%s)", dump_num, max_crash_dump, dump_list[i].path); break; case USAGE_EXCEED: _I("Reached the maximum disk usage %" PRId64 "/%d kb, so remove (%s)", usage / 1024, system_max_use, dump_list[i].path); break; default: break; } if (!remove_flag) continue; if (remove_file_info(dump_list[i]) < 0) { _E("Failed to remove %s", dump_list[i].path); continue; } dump_num--; usage -= dump_list[i].size; } /* Check disk free space to keep */ if (system_keep_free && check_disk_available(crash_root_path, system_keep_free) < 0) { _I("Disk is not available! so set the maximum number of dump to 1"); max_crash_dump = 1; } for (i = 0; i < dump_num; i++) free(dump_list[i].path); free(dump_list); } static void compress(struct crash_info *cinfo) { int ret, lock_fd; char zip_path[PATH_MAX]; ret = snprintf(zip_path, sizeof(zip_path), "%s/report.zip", cinfo->temp_dir); if (ret < 0) { _E("Failed to snprintf for zip path"); return; } char *args[] = { "/bin/zip", "-y", "-r", zip_path, cinfo->name, NULL }; spawn_param_u param = { .char_ptr = cinfo->temp_dir }; (void)spawn_wait(args, NULL, spawn_chdir, ¶m, ZIP_TIMEOUT_MS, NULL); if ((lock_fd = lock_dumpdir()) < 0) return; if (!rename(zip_path, cinfo->result_path)) clean_dump(); else _E("Failed to move %s to %s", zip_path, cinfo->result_path); unlock_dumpdir(lock_fd); if (remove_dir(cinfo->temp_dir, 1) < 0) _E("Failed to delete temp directory"); } static void move_dump_data(const char *from_path, const struct crash_info *cinfo) { int lock_fd; if ((lock_fd = lock_dumpdir()) < 0) return; if (!rename(from_path, cinfo->result_path)) clean_dump(); else _E("Failed to move %s to %s", from_path, cinfo->result_path); unlock_dumpdir(lock_fd); if (remove_dir(cinfo->temp_dir, 1) < 0) _E("Failed to delete temp directory"); } static int wait_for_opt(unsigned int timeout) { unsigned int count = 0; while (check_disk_available(crash_root_path, 0) < 0 && count < timeout) { log_kmsg("crash-manager: path %s is not available\n", crash_root_path); sleep(1); count++; } if (count >= timeout) { log_kmsg("crash-manager: timeout (%ds) while waiting for %s." "Probably /opt is not mounted.\n", timeout, crash_root_path); return 0; } return 1; } static void free_crash_info(struct crash_info *cinfo) { free(cinfo->cmd_line); free(cinfo->cmd_path); free(cinfo->temp_dir); free(cinfo->result_path); free(cinfo->pfx); free(cinfo->info_path); free(cinfo->core_path); free(cinfo->log_path); #ifdef SYS_ASSERT free(cinfo->sysassert_cs_path); #endif } int main(int argc, char *argv[]) { struct crash_info cinfo = {.prstatus_fd = -1}; /* Execute dump_systemstate in parallel */ static pid_t dump_state_pid; int debug_mode = access(DEBUGMODE_PATH, F_OK) == 0; int res = 0; /* * prctl(PR_SET_DUMPABLE, 0) is not neccessary. Kernel runs the * crash-manager and sets RLIMIT_CORE to 1 for the process. This is special * value that prevents from running crash-manager recursively. */ /* Get Configuration */ if (get_config() < 0) { res = EXIT_FAILURE; goto exit; } /* Prepare paths */ if (!prepare_paths()) { res = EXIT_FAILURE; goto exit; } if (!wait_for_opt(WAIT_FOR_OPT_TIMEOUT_SEC)) { res = EXIT_FAILURE; goto exit; } /* Create crash directories */ if (make_dump_dir() < 0) { res = EXIT_FAILURE; goto exit; } /* Set crash info */ if (set_crash_info(&cinfo, argc, argv) < 0) { res = EXIT_FAILURE; goto exit; } #ifdef SYS_ASSERT /* Fetch callstack of sys-assert */ get_sysassert_cs(&cinfo); #endif if (report_type >= REP_TYPE_FULL) { /* Exec dump_systemstate */ if (!dump_system_state(&cinfo, &dump_state_pid)) { res = EXIT_FAILURE; _E("Failed to get system state report"); goto exit; } } /* Exec crash modules */ if (!execute_crash_modules(&cinfo)) { res = EXIT_FAILURE; _E("Failed to get basic crash information"); goto exit; } if (report_type >= REP_TYPE_FULL) { /* Save shared objects info (file names, bulid IDs, rpm package names) */ save_so_info(&cinfo); /* Wait dump_system_state */ wait_for_pid(dump_state_pid, NULL); /* Tar compression */ if (allow_zip) compress(&cinfo); else move_dump_data(cinfo.pfx, &cinfo); } else { free(cinfo.result_path); if (asprintf(&cinfo.result_path, "%s/%s.info", crash_crash_path, cinfo.name) == -1) { cinfo.result_path = NULL; _E("asprintf() error: %m"); res = EXIT_FAILURE; goto exit; } move_dump_data(cinfo.info_path, &cinfo); } struct NotifyParams notify_params = { .prstatus_fd = cinfo.prstatus_fd, .pid = cinfo.pid_info, .tid = cinfo.tid_info, .cmd_name = basename(cinfo.cmd_line), .cmd_path = cinfo.cmd_path, .report_path = cinfo.result_path, .appid = cinfo.appid, .pkgid = cinfo.pkgid }; send_notify(¬ify_params); /* Release the core pipe as passed by kernel, allowing another * coredump to be handled. * * Due to usage of core_pipe_limit there is limited number of * crash-manager processes that kernel is going to invoke * concurrently. As the next and last step is a _synchronous_ * call to crash-popup we close the descriptor here. * * Note: for VIP processes this will likely cause the system * to reboot without showing popup. */ close(STDIN_FILENO); /* launch crash-popup only if the .debugmode file exists */ if (debug_mode) launch_crash_popup(&cinfo); exit: _I("Exiting with exit code %d", res); if (cinfo.prstatus_fd >= 0) close(cinfo.prstatus_fd); free(crash_temp_path); free(crash_root_path); free(crash_crash_path); free_crash_info(&cinfo); return res; }