* limitations under the License.
*/
+#define _GNU_SOURCE
+
#include <stdio.h>
#include <stdlib.h>
+#include <stdbool.h>
#include <limits.h>
#include <unistd.h>
#include <libgen.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/smack.h>
+#include <sys/vfs.h>
#include <gio/gio.h>
+#include <iniparser.h>
#include <tzplatform_config.h>
#include <dlog.h>
#include "crash-manager.h"
#undef LOG_TAG
#define LOG_TAG "CRASH_MANAGER"
-#define DEBUGMODE_FILE tzplatform_mkpath(TZ_SYS_ETC, ".debugmode")
+#define DEBUGMODE_FILE tzplatform_mkpath(TZ_SYS_ETC, ".debugmode")
+#define LOCK_FILE CRASH_PATH"/.lock"
+#define LOCK_FILE_VALIDITY 10
+
+/* Parsing */
+#define CRASH_CONF_FILE tzplatform_mkpath(TZ_SYS_ETC, "crash-manager.conf")
+#define KEY_MAX 255
+#define CRASH_SECTION "CrashManager"
/* Crash-popup dbus */
#define POPUP_BUS_NAME "org.tizen.system.popup"
#define POPUP_INTERFACE_NAME POPUP_BUS_NAME".Crash"
#define POPUP_METHOD "PopupLaunch"
+/* Configuration default values */
+#define SYSTEM_MAX_USE 10240
+#define SYSTEM_KEEP_FREE 0
+#define MAX_RETENTION_SEC 1296000
+#define MAX_CRASH_DUMP 5
+#define ALLOW_ZIP true
+
+#define CRASH_CHECK_DISK_PATH "/opt/usr"
+
+struct file_info {
+ bool isdir;
+ int 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 bool allow_zip;
+
/* Paths and variables */
static struct crash_info {
char *cmd_info;
#endif
} crash_info;
+static void get_config(void)
+{
+ dictionary *ini = NULL;
+ char key[KEY_MAX];
+ int value;
+
+ system_max_use = SYSTEM_MAX_USE;
+ system_keep_free = SYSTEM_KEEP_FREE;
+ max_retention_sec = MAX_RETENTION_SEC;
+ max_crash_dump = MAX_CRASH_DUMP;
+ allow_zip = ALLOW_ZIP;
+
+ ini = iniparser_load(CRASH_CONF_FILE);
+ if (!ini) {
+ LOGE("Failed to load conf file");
+ return;
+ }
+
+ snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "SystemMaxUse");
+ value = iniparser_getint(ini, key, -1);
+ if (value < 0) {
+ LOGD("Invalid value for SystemMaxUse. Use default value [ %d kbyte]",
+ SYSTEM_MAX_USE);
+ } else {
+ LOGD("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) {
+ LOGD("Invalid value for SystemKeepFree. Use default value [ %d kbyte]",
+ SYSTEM_KEEP_FREE);
+ } else {
+ LOGD("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) {
+ LOGD("Invalid value for MaxRetentionSec. Use default value [ %d ]",
+ MAX_RETENTION_SEC);
+ } else {
+ LOGD("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) {
+ LOGD("Invalid value for MaxCrashDump. Use default value [ %d ]",
+ MAX_CRASH_DUMP);
+ } else {
+ LOGD("MaxCrashDump [ %d ]", value);
+ max_crash_dump = value;
+ }
+
+ snprintf(key, sizeof(key), "%s:%s", CRASH_SECTION, "AllowZip");
+ value = iniparser_getboolean(ini, key, -1);
+ if (value < 0) {
+ LOGD("Invalid value for AllowZip. Use default value [ %s ]",
+ ALLOW_ZIP ? "true" : "false" );
+ } else {
+ LOGD("AllowZip [ %s ]", value ? "true" : "false");
+ allow_zip = value;
+ }
+
+ iniparser_freedict(ini);
+}
+
static int make_dump_dir(void)
{
struct stat st;
goto rm_temp;
}
- ret = snprintf(crash_info.result_path, sizeof(crash_info.result_path),
- "%s/%s.tar.gz", CRASH_PATH, crash_info.name);
+ if (allow_zip)
+ ret = snprintf(crash_info.result_path,
+ sizeof(crash_info.result_path),
+ "%s/%s.tar.gz", CRASH_PATH, crash_info.name);
+ else
+ ret = snprintf(crash_info.result_path,
+ sizeof(crash_info.result_path),
+ "%s/%s", CRASH_PATH, crash_info.name);
if (ret < 0) {
LOGE("Failed to snprintf for result path");
goto rm_temp;
*/
}
+static void dump_lock(void)
+{
+ struct stat st;
+ time_t cur_time;
+ int fd;
+
+ while ((fd = open(LOCK_FILE, O_CREAT | O_EXCL, 0644)) < 0) {
+ if (stat(LOCK_FILE, &st) < 0) {
+ LOGE("Failed to stat lock file");
+ return;
+ }
+
+ cur_time = time(NULL);
+ if (st.st_mtime + LOCK_FILE_VALIDITY < cur_time) {
+ LOGI("Lock file validity over");
+ if (unlink(LOCK_FILE) < 0)
+ LOGE("Failed to unlink %s", LOCK_FILE);
+ return;
+ }
+ sleep(LOCK_FILE_VALIDITY / 2);
+ }
+ close(fd);
+}
+
+static void dump_unlock(void)
+{
+ if (unlink(LOCK_FILE) < 0)
+ LOGE("Failed to unlink %s", LOCK_FILE);
+}
+
static void compress(void)
{
int ret;
}
system_command(command);
- if (move_file(tar_path, crash_info.result_path) < 0)
+ dump_lock();
+ if (rename(tar_path, crash_info.result_path) < 0)
LOGE("Failed to move %s to %s",
tar_path, crash_info.result_path);
+ dump_unlock();
ret = remove_dir(crash_info.temp_dir, 1);
if (ret < 0)
LOGE("Failed to delete temp directory");
}
+static void move_dump_dir(void)
+{
+ int ret;
+
+ dump_lock();
+ ret = rename(crash_info.pfx, crash_info.result_path);
+ dump_unlock();
+ if (ret < 0) {
+ LOGE("Failed to move %s to %s",
+ crash_info.pfx, crash_info.result_path);
+ return;
+ }
+
+ ret = remove_dir(crash_info.temp_dir, 1);
+ if (ret < 0)
+ LOGE("Failed to delete temp directory");
+}
+
+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)
+{
+ 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_PATH, O_DIRECTORY)) < 0 ) {
+ LOGE("Failed to open %s", CRASH_PATH);
+ return -1;
+ }
+
+ scan_num = scandir(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) {
+ LOGE("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) {
+ LOGE("Failed to fstatat");
+ continue;
+ }
+
+ if (asprintf(&(temp_list[dump_num].path), "%s/%s",
+ CRASH_PATH, scan_list[i]->d_name) < 0) {
+ LOGE("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)
+ temp_list = (struct file_info *)realloc(temp_list,
+ dump_num * sizeof(struct file_info));
+
+ qsort(temp_list, dump_num, sizeof(struct file_info), mtime_cmp);
+
+ for (i = 0; i < dump_num; i++)
+ LOGD("[%d] path: %s(%s), size: %d kb, mtime: %s",
+ i,
+ temp_list[i].path,
+ temp_list[i].isdir ? "DIR" : "FILE",
+ temp_list[i].size / 1024,
+ ctime(&(temp_list[i].mtime)));
+ *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) {
+ LOGI("avail_size is (%d)", avail_size);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int remove_file(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;
+ int next = 0;
+ size_t usage = 0;
+ time_t cur_time;
+
+ dump_lock();
+
+ scan_num = scan_dump(&dump_list);
+ if (scan_num <= 0) {
+ dump_unlock();
+ return;
+ }
+ dump_num = scan_num;
+
+ /* Retention time check */
+ cur_time = time(NULL);
+ for (i = 0; i < scan_num; i++) {
+ usage += dump_list[i].size;
+ if (max_retention_sec &&
+ dump_list[i].mtime > 0 &&
+ dump_list[i].mtime + max_retention_sec < cur_time) {
+ if (remove_file(dump_list[i]) < 0) {
+ LOGE("Failed to remove %s", dump_list[i].path);
+ continue;
+ }
+ LOGI("Reached the maximum retention time %d, so remove (%s)",
+ max_retention_sec, dump_list[i].path);
+ dump_num--;
+ next = i + 1;
+ usage -= dump_list[i].size;
+ }
+ }
+
+ /* Check the number of dumps */
+ if (max_crash_dump &&
+ 0 < dump_num && max_crash_dump < dump_num) {
+ for (i = next; i < scan_num; i++) {
+ if (remove_file(dump_list[i]) < 0) {
+ LOGE("Failed to remove %s", dump_list[i].path);
+ continue;
+ }
+ LOGI("Reached the maximum number of dump %d/%d, so remove (%s)",
+ dump_num, max_crash_dump,
+ dump_list[i].path);
+ dump_num--;
+ next = i + 1;
+ usage -= dump_list[i].size;
+ if (dump_num <= 0 || dump_num <= max_crash_dump)
+ break;
+ }
+ }
+
+ /* Check the max system use size */
+ if (system_max_use &&
+ 0 < dump_num && system_max_use < usage / 1024) {
+ for (i = next; i < scan_num; i++) {
+ if (remove_file(dump_list[i]) < 0) {
+ LOGE("Failed to remove %s", dump_list[i].path);
+ dump_num--;
+ continue;
+ }
+ LOGI("Reached the maximum disk usage %d/%d kb, so remove (%s)",
+ usage / 1024, system_max_use,
+ dump_list[i].path);
+ dump_num--;
+ usage -= dump_list[i].size;
+ if (dump_num <= 0 || usage / 1024 <= system_max_use)
+ break;
+ }
+ }
+
+ /* Check disk free space to keep */
+ if (system_keep_free &&
+ check_disk_available(CRASH_CHECK_DISK_PATH,
+ system_keep_free) < 0) {
+ LOGI("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);
+
+ dump_unlock();
+}
+
int main(int argc, char *argv[])
{
prctl(PR_SET_DUMPABLE, 0);
+ /* Get Configuration */
+ get_config();
+
/* Create crash directories */
if (make_dump_dir() < 0)
exit(EXIT_FAILURE);
execute_crash_modules(argc, argv, DEBUG);
/* Tar compression */
- compress();
+ if (allow_zip)
+ compress();
+ else
+ move_dump_dir();
+
+ /* Check configured limits */
+ clean_dump();
return 0;
}