Configurable crash-manager 46/99646/6
authorSunmin Lee <sunm.lee@samsung.com>
Fri, 18 Nov 2016 02:20:20 +0000 (11:20 +0900)
committerSunmin Lee <sunm.lee@samsung.com>
Tue, 6 Dec 2016 10:30:42 +0000 (19:30 +0900)
crash-manager can manage dump output according to configured value
in config file (crash-manager.conf).

The config values
 SystemMaxUse   : The maximum usage of partition that crash dump can be generated
 SystemKeepFree : The free space should be kept
 MaxRetentionSec: The retention time of each dump file
 MaxCrashDump   : The maximum number of dump file to be preserved
 AllowZip       : Whether compressing dump or not

Change-Id: Ib0c3cd9445338e469040b07aebf81bf6705fd29e
Signed-off-by: Sunmin Lee <sunm.lee@samsung.com>
packaging/crash-worker.spec
src/crash-manager/CMakeLists.txt
src/crash-manager/crash-manager.c
src/crash-manager/crash-manager.conf [new file with mode: 0644]
src/shared/util.c

index 2d7c68f63a8ad21784ab46ca2b62ce847014271d..7e111425415d1c3edec0154a380971bca99e9df1 100644 (file)
@@ -11,6 +11,7 @@ Source1001:    crash-worker.manifest
 BuildRequires:  pkgconfig(dlog)
 BuildRequires:  pkgconfig(libsmack)
 BuildRequires:  pkgconfig(libtzplatform-config)
+BuildRequires:  pkgconfig(iniparser)
 BuildRequires:  pkgconfig(capi-system-info)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  cmake
@@ -77,6 +78,7 @@ export CFLAGS+=" -Werror"
           -DTMP_FILES_DIR=%{_sysconfdir}/tmpfiles.d \
           -DARCH=%{ARCH} \
           -DARCH_BIT=%{ARCH_BIT} \
+          -DTZ_SYS_ETC=%{TZ_SYS_ETC} \
           -DTZ_SYS_BIN=%{TZ_SYS_BIN} \
           -DCRASH_ROOT_PATH=%{crash_root_path} \
           -DCRASH_PATH=%{crash_path} \
@@ -129,7 +131,7 @@ fi
 /usr/bin/chsmack -a "System::Shared" -t %{crash_temp}
 /usr/bin/chsmack -a "System::Shared" -t %{crash_dump_gen}
 /usr/bin/chsmack -a "System::Shared" -t %{crash_dump_gen}/module.d
-/usr/bin/chsmack -d %{crash_dump_gen}/module.d/*
+/usr/bin/chsmack -a "_"  %{crash_dump_gen}/module.d/*
 
 %postun
 %if "%{?sys_assert}" == "on"
@@ -151,6 +153,7 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload
 %attr(0755,root,root) %{_bindir}/*
 %attr(0644,root,system) %{_unitdir}/tizen-debug-on.service
 %attr(0644,root,system) %{_unitdir}/tizen-debug-off.service
+%{TZ_SYS_ETC}/crash-manager.conf
 %{_prefix}/lib/sysctl.d/99-crash-manager.conf
 %{_datadir}/dbus-1/system-services/org.tizen.system.crash.service
 
index 23f7570f6902e6d48dabe6ffd3187676907a579b..361ac3495c8118c8a5ee3eb8cda4846be6d7283f 100644 (file)
@@ -12,6 +12,7 @@ pkg_check_modules(crash-manager_pkgs REQUIRED
        dlog
        libsmack
        libtzplatform-config
+       iniparser
        gio-2.0
        )
 
@@ -32,6 +33,10 @@ INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin
 CONFIGURE_FILE(99-${PROJECT_NAME}.conf.in 99-${PROJECT_NAME}.conf @ONLY)
 CONFIGURE_FILE(500.${PROJECT_NAME}-upgrade.sh.in 500.${PROJECT_NAME}-upgrade.sh @ONLY)
 
+INSTALL(FILES ${CMAKE_SOURCE_DIR}/src/${PROJECT_NAME}/crash-manager.conf
+               DESTINATION ${TZ_SYS_ETC}
+               PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
+
 INSTALL(FILES ${CMAKE_SOURCE_DIR}/src/${PROJECT_NAME}/99-${PROJECT_NAME}.conf
                DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/sysctl.d
                PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
index 35f8fb6f3ab6262b5e7c3e693fbce85f97803bb8..d0142df0f7d053951d5b9a8835619dd62ec79a96 100644 (file)
  * 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;
@@ -59,6 +95,78 @@ static struct crash_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;
@@ -132,8 +240,14 @@ static int set_crash_info(char *argv[])
                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;
@@ -347,6 +461,36 @@ static void execute_crash_modules(int argc, char *argv[], int debug)
        */
 }
 
+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;
@@ -369,19 +513,256 @@ static void compress(void)
        }
        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);
@@ -406,7 +787,13 @@ int main(int argc, char *argv[])
        execute_crash_modules(argc, argv, DEBUG);
 
        /* Tar compression */
-       compress();
+       if (allow_zip)
+               compress();
+       else
+               move_dump_dir();
+
+       /* Check configured limits */
+       clean_dump();
 
        return 0;
 }
diff --git a/src/crash-manager/crash-manager.conf b/src/crash-manager/crash-manager.conf
new file mode 100644 (file)
index 0000000..3489165
--- /dev/null
@@ -0,0 +1,6 @@
+[CrashManager]
+SystemMaxUse=10240
+SystemKeepFree=0
+MaxRetentionSec=1296000
+MaxCrashDump=1
+AllowZip=yes
index 6d3cf2a600921811fe147548bfbf6fb4d8161800..01445fdfd40e18b7d515827f753197599900c20b 100644 (file)
@@ -438,6 +438,7 @@ int get_directory_usage(char *path)
        struct stat st;
        size_t usage = 0;
        int fd = -1;
+       int ret;
 
        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
        if (fd < 0)
@@ -448,10 +449,7 @@ int get_directory_usage(char *path)
                return -1;
        }
 
-       if ((readdir_r(dir, &e, &de)) != 0)
-               de = NULL;
-
-       while (de) {
+       while ((ret = readdir_r(dir, &e, &de)) == 0 && de) {
                if (!strncmp(de->d_name, ".", 2) || !strncmp(de->d_name, "..", 3))
                        continue;
                if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {