Add critical-log module 80/212680/4
authorYoungjae Cho <y0.cho@samsung.com>
Mon, 26 Aug 2019 05:39:11 +0000 (14:39 +0900)
committerHyotaek Shim <hyotaek.shim@samsung.com>
Tue, 27 Aug 2019 07:34:42 +0000 (07:34 +0000)
Change-Id: Ic719ce9f2156bdaebedb5cda60643e7462dce792
Signed-off-by: Youngjae Cho <y0.cho@samsung.com>
CMakeLists.txt
conf/critical-log.conf [new file with mode: 0644]
packaging/deviced.spec
src/core/device-notifier.h
src/core/log.c
src/core/log.h
src/critical-log/critical-log.c [new file with mode: 0644]

index 072b6c6..e68f5bb 100644 (file)
@@ -153,8 +153,13 @@ IF(TIZEN_FEATURE_USBHOST_TEST STREQUAL on)
 ENDIF()
 
 IF(DEVICE_BOARD_MODULE STREQUAL on)
-       ADD_SOURCE(src/board PRODUCT_BOARD_SRCS)
-       SET(SRCS ${SRCS} ${PRODUCT_BOARD_SRCS})
+       ADD_SOURCE(src/board BOARD_SRCS)
+       SET(SRCS ${SRCS} ${BOARD_SRCS})
+ENDIF()
+
+IF(CRITICAL_LOG_MODULE STREQUAL on)
+       ADD_SOURCE(src/critical-log CRITICAL_LOG_SRCS)
+       SET(SRCS ${SRCS} ${CRITICAL_LOG_SRCS})
 ENDIF()
 
 INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
@@ -265,6 +270,10 @@ IF(TIZEN_FEATURE_CPU_MODULE STREQUAL on)
        INSTALL_CONF(conf cpu)
 ENDIF()
 
+IF(CRITICAL_LOG_MODULE STREQUAL on)
+       INSTALL_CONF(conf critical-log)
+ENDIF()
+
 CONFIGURE_FILE(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY)
 INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
 
diff --git a/conf/critical-log.conf b/conf/critical-log.conf
new file mode 100644 (file)
index 0000000..0f76445
--- /dev/null
@@ -0,0 +1,13 @@
+#Critical Log will be saved under the /var/log/ghost/boot/#booting_count
+#Critical Log Format is "Section|String Value"
+#Limit means Last Critical Log Logging Count
+[Common]
+Limit=100
+[Battery]
+Limit=20
+[BatteryHealth]
+Limit=10
+[PowerHandle]
+Limit=10
+[CoolDown]
+Limit=10
index f56037b..ea01b19 100644 (file)
@@ -163,6 +163,7 @@ Plugin libraries for IoT devices
        -DTOUCH_SENSITIVITY_MODULE=on \
        -DDUMP_MODULE=on \
        -DDEVICE_BOARD_MODULE=on \
+       -DCRITICAL_LOG_MODULE=on \
        #eol
 
 %build
@@ -286,6 +287,7 @@ mv %{_libdir}/iot-display.so %{_libdir}/deviced/display.so
 %license LICENSE.Apache-2.0
 %defattr(-,root,root,-)
 %config %{_sysconfdir}/deviced/mobile-display.conf
+%config %{_sysconfdir}/deviced/critical-log.conf
 %{_libdir}/mobile-display.so
 
 %files plugin-profile-wearable
@@ -293,6 +295,7 @@ mv %{_libdir}/iot-display.so %{_libdir}/deviced/display.so
 %license LICENSE.Apache-2.0
 %defattr(-,root,root,-)
 %config %{_sysconfdir}/deviced/wearable-display.conf
+%config %{_sysconfdir}/deviced/critical-log.conf
 %{_libdir}/wearable-display.so
 
 %files plugin-profile-tv
index 3196468..fe89950 100644 (file)
@@ -72,6 +72,7 @@ enum device_notifier_type {
        DEVICE_NOTIFIER_BEZEL_WAKEUP,
        DEVICE_NOTIFIER_LONGKEY_RESTORE,
        DEVICE_NOTIFIER_ULTRAPOWERSAVING,
+       DEVICE_NOTIFIER_CRITICAL_LOG,
        DEVICE_NOTIFIER_MAX,
 };
 
index 86310cd..bd629a8 100644 (file)
@@ -17,7 +17,9 @@
  */
 
 #include <stdio.h>
+#include <stdarg.h>
 #include "log.h"
+#include "device-notifier.h"
 
 #ifdef DEBUG
 void __cyg_profile_func_enter(void *, void *)
@@ -37,3 +39,15 @@ void __cyg_profile_func_exit(void *func, void *caller)
        g_trace_depth--;
 }
 #endif
+
+void critical_log_internal(const char *func, const char *fmt, ...)
+{
+       va_list ap;
+       char buf[256];
+
+       va_start(ap, fmt);
+       vsnprintf(buf, 256, fmt, ap);
+       va_end(ap);
+       _I("%s:%s", func, buf);
+       device_notify(DEVICE_NOTIFIER_CRITICAL_LOG, (void *)buf);
+}
index 9684f38..915e4a9 100644 (file)
@@ -31,3 +31,7 @@
 #define LOG_TAG "DEVICED"
 #include "shared/log-macro.h"
 #endif
+
+void critical_log_internal(const char *caller, const char *fmt, ...);
+
+#define critical_log(fmt, arg...) critical_log_internal(__func__, fmt, ##arg)
diff --git a/src/critical-log/critical-log.c b/src/critical-log/critical-log.c
new file mode 100644 (file)
index 0000000..154859a
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * deviced
+ *
+ * Copyright (c) 2018 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 <fcntl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "core/log.h"
+#include "core/devices.h"
+#include "core/common.h"
+#include "core/config-parser.h"
+#include "core/device-notifier.h"
+#include "battery/power-supply.h"
+#include "power/power-handler.h"
+
+#define CLOG_SAVE_FILE  "/var/log/critical-log.log"
+#define CLOG_BACKUP_FILE  "/var/log/critical-log.1.log"
+#define CLOG_CONF_FILE  "/etc/deviced/critical-log.conf"
+#define CLOG_TIME_FORMAT "%y-%m-%d/%H:%M:%S"
+#define CLOG_MIN_SIZE   3
+#define CLOG_MSG_MAX    512
+#define CLOG_PATH_MAX   256
+#define BOOT_REASON_PATH "/sys/class/system_stats/rbrs"
+#define CLOG_FILE_MAX 51200
+
+#define CLOG_INDENTED_TAG "%*s"
+
+struct clog_tag_info {
+       char *name;
+       int name_len;
+       int type;
+       int count;
+       int limit;
+};
+
+struct clog_msg_info {
+       int type;
+       char *log;
+};
+
+static dd_list *clog_tags;
+static dd_list *clog_msgs;
+static int max_name_len;
+
+static void free_clog_data(void)
+{
+       dd_list *n, *next;
+       struct clog_tag_info *tag;
+       struct clog_msg_info *msg;
+
+       DD_LIST_FOREACH_SAFE(clog_tags, n, next, tag) {
+               DD_LIST_REMOVE(clog_tags, tag);
+               if (tag->name) {
+                       free(tag->name);
+                       tag->name = NULL;
+               }
+               free(tag);
+               tag = NULL;
+       }
+       DD_LIST_FOREACH_SAFE(clog_msgs, n, next, msg) {
+               DD_LIST_REMOVE(clog_msgs, msg);
+               if (msg->log) {
+                       free(msg->log);
+                       msg->log = NULL;
+               }
+               free(msg);
+               msg = NULL;
+       }
+}
+
+static int clog_load_config(struct parse_result *result, void *user_data)
+{
+       static int type;
+       struct clog_tag_info *tag = NULL;
+       int ret;
+
+       if (!result || !result->section) {
+               _E("There is no section.");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       tag = calloc(1, sizeof(struct clog_tag_info));
+       if (!tag) {
+               _E("Failed to allocate memory.");
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       tag->name = strdup(result->section);
+       if (!tag->name) {
+               _E("Failed to allocate memory.");
+               ret = -ENOMEM;
+               goto out;
+       }
+       type++;
+       tag->type = type;
+       tag->name_len = strlen(tag->name);
+       if (max_name_len < tag->name_len)
+               max_name_len = tag->name_len;
+       if (result->name && result->value && MATCH(result->name, "Limit"))
+               tag->limit = strtol(result->value, NULL, 10);
+       else
+               tag->limit = CLOG_MIN_SIZE;
+
+       DD_LIST_APPEND(clog_tags, tag);
+
+       _D("[%d]%s count %d limit %d.", tag->type, tag->name, tag->count, tag->limit);
+       return 0;
+out:
+       if (tag)
+               free(tag);
+       free_clog_data();
+       return ret;
+}
+
+static int clog_execute(void *data)
+{
+       dd_list *n, *next;
+       struct clog_tag_info *tag;
+       struct clog_msg_info *msg;
+       char *input, *check, *tag_name, *name = NULL;
+       int name_len, ret;
+       struct timespec tspec;
+       struct tm tm_now;
+       char str_buff[CLOG_MSG_MAX] = {0,};
+       char str_time[32] = {0,};
+       char str_msec[6] = {0,};
+
+       if (!data)
+               return -EINVAL;
+
+       msg = calloc(1, sizeof(struct clog_msg_info));
+       if (!msg) {
+               _E("Failed to allocate memory.");
+               return -EINVAL;
+       }
+
+       input = (char *)data;
+       check = strchr(input, '|');
+       if (!check) {
+               _E("There is no tag name separator(|).");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       *check = '\0';
+       tag_name = input;
+       while (*tag_name == ' ')
+               tag_name++;
+       name = strdup(tag_name);
+       if (!name) {
+               _E("Failed to allocate memory.");
+               ret = -ENOMEM;
+               goto out;
+       }
+       *check = '|';
+
+       name_len = strlen(name);
+       DD_LIST_FOREACH_SAFE(clog_tags, n, next, tag) {
+               if (tag->name_len != name_len)
+                       continue;
+               if (strncmp(tag->name, name, name_len))
+                       continue;
+               break;
+       }
+       if (!tag) {
+               _E("There is no matched tag name.");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       clock_gettime(CLOCK_REALTIME, &tspec);
+       localtime_r((time_t *)&tspec.tv_sec, &tm_now);
+       strftime(str_time, sizeof(str_time), CLOG_TIME_FORMAT, &tm_now);
+       snprintf(str_msec, sizeof(str_msec), ":%03ld|", tspec.tv_nsec / 1000000);
+       snprintf(str_buff, sizeof(str_buff), "%s%s[%d]%s BAT(P:%s S:%s C:%d M:%d Chg_ON:%d)\n",
+               str_time, str_msec, getpid(), input,
+               battery.power_source_s,
+               battery.status_s,
+               battery.capacity,
+               battery.misc,
+               battery.charger_charging);
+       _D("%s", str_buff);
+       msg->log = strdup(str_buff);
+       msg->type = tag->type;
+       DD_LIST_APPEND(clog_msgs, msg);
+       tag->count++;
+
+       if (tag->count > tag->limit) {
+               DD_LIST_FOREACH_SAFE(clog_msgs, n, next, msg) {
+                       if (tag->type != msg->type)
+                               continue;
+                       DD_LIST_REMOVE(clog_msgs, msg);
+                       if (msg->log)
+                               free(msg->log);
+                       free(msg);
+                       tag->count--;
+                       break;
+               }
+       }
+       if (name)
+               free(name);
+       return 0;
+out:
+       if (name)
+               free(name);
+       if (msg) {
+               if (msg->log)
+                       free(msg->log);
+               free(msg);
+       }
+       return ret;
+}
+
+static int clog_battery_health(void *data)
+{
+       char clog[CLOG_MSG_MAX] = {0,};
+       static int old = HEALTH_GOOD;
+       int health;
+
+       if (!data) {
+               _E("There is no data.");
+               return -EINVAL;
+       }
+
+       health = *(int *)data;
+       if (old == health)
+               return 0;
+
+       old = health;
+       snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|%s", max_name_len, "BatteryHealth", battery.health_s);
+
+       return clog_execute((void *)clog);
+}
+
+static int clog_battery_present(void *data)
+{
+       char clog[CLOG_MSG_MAX] = {0,};
+       static int old = PRESENT_NORMAL;
+       int present;
+
+       if (!data) {
+               _E("There is no data.");
+               return -EINVAL;
+       }
+
+       present = *(int *)data;
+       if (old == present)
+               return 0;
+
+       old = present;
+       snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|%s", max_name_len, "Battery", (present == PRESENT_ABNORMAL) ? "CFOpened" : "CFNormal");
+
+       return clog_execute((void *)clog);
+}
+
+static void clog_save_log(void)
+{
+       dd_list *n, *next;
+       struct clog_tag_info *tag;
+       struct clog_msg_info *msg;
+       struct stat st;
+       int fd;
+       int ret;
+
+       fd = open(CLOG_SAVE_FILE, O_RDONLY);
+       if (fd != -1) {
+               if (fstat(fd, &st) >= 0 && st.st_size > CLOG_FILE_MAX) {
+                       if (remove(CLOG_BACKUP_FILE) < 0)
+                               _E("Failed to remove %d.", errno);
+                       if (rename(CLOG_SAVE_FILE, CLOG_BACKUP_FILE) < 0)
+                               _E("Failed to backup %d.", errno);
+               }
+               close(fd);
+       }
+
+       fd = open(CLOG_SAVE_FILE, O_CREAT | O_EXCL | O_WRONLY, 0644);
+       if (fd == -1) {
+               fd = open(CLOG_SAVE_FILE, O_APPEND | O_WRONLY);
+               if (fd == -1) {
+                       _E("Fail to open clog file.");
+                       return;
+               }
+       }
+       DD_LIST_FOREACH_SAFE(clog_msgs, n, next, msg) {
+               ret = write(fd, msg->log, strlen(msg->log));
+               if (ret < 0)
+                       _E("Failed to write %d", errno);
+       }
+       close(fd);
+
+       DD_LIST_FOREACH_SAFE(clog_tags, n, next, tag)
+               tag->count = 0;
+       DD_LIST_FOREACH_SAFE(clog_msgs, n, next, msg) {
+               DD_LIST_REMOVE(clog_msgs, msg);
+               if (msg->log) {
+                       free(msg->log);
+                       msg->log = NULL;
+               }
+               free(msg);
+               msg = NULL;
+       }
+}
+
+static int clog_common_log(void *data)
+{
+       char clog[CLOG_MSG_MAX] = {0,};
+
+       if (data)
+               snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|%s", max_name_len, "Common", (char *)data);
+       else
+               snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|LogDump", max_name_len, "Common");
+
+       clog_execute((void *)clog);
+
+       if (!data)
+               clog_save_log();
+       return 0;
+}
+
+static int clog_power_off(void *data)
+{
+       char clog[CLOG_MSG_MAX] = {0,};
+       struct power_option *opt = (struct power_option *)data;
+
+       if (!opt || (opt->type != POWEROFF_TYPE_DIRECT && opt->type != POWEROFF_TYPE_RESTART))
+               goto save_log;
+
+       if (opt->type == POWEROFF_TYPE_DIRECT)
+               snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|%s ", max_name_len, "PowerHandle", "PowerOff");
+       else
+               snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|%s with %s ", max_name_len, "PowerHandle",
+                               "Reboot", (opt->option) ? opt->option : "NULL");
+
+       clog_execute((void *)clog);
+
+save_log:
+       clog_save_log();
+       return 0;
+}
+
+static int clog_booting_done(void *data)
+{
+       DIR *dir;
+       struct dirent *result;
+       static int done;
+       char clog[CLOG_MSG_MAX] = {0,};
+       char node[CLOG_PATH_MAX] = {0,};
+       char val[CLOG_PATH_MAX] = {0,};
+       char elem;
+       int prefix;
+       int fd;
+       unsigned int cnt;
+       int ret;
+
+       if (data == NULL)
+               return done;
+       done = *(int *)data;
+       if (done == 0)
+               return 0;
+
+       snprintf(clog, CLOG_MSG_MAX, CLOG_INDENTED_TAG"|Boot", max_name_len, "PowerHandle");
+
+       dir = opendir(BOOT_REASON_PATH);
+       if (!dir) {
+               _E("Failed to opendir %s.", BOOT_REASON_PATH);
+               goto out;
+       }
+
+       prefix = strlen(clog);
+       while ((result = readdir(dir))) {
+               if (strncmp(result->d_name, "summary", 7) != 0)
+                       continue;
+               snprintf(node, sizeof(node), BOOT_REASON_PATH"/%s", result->d_name);
+               fd = open(node, O_RDONLY);
+               if (fd < 0)
+                       continue;
+               ret = 0;
+               cnt = 0;
+               while (read(fd, &elem, 1) != 0) {
+                       if (cnt >= 4 || ret >= CLOG_PATH_MAX - 1)
+                               break;
+                       if (elem != '\n') {
+                               val[ret++] = elem;
+                       } else {
+                               val[ret++] = ' ';
+                               cnt++;
+                       }
+               }
+               val[ret] = '\0';
+               snprintf(clog + prefix, CLOG_MSG_MAX - prefix, "|%s(%s) ", result->d_name, val);
+               close(fd);
+               closedir(dir);
+               goto out;
+       }
+
+       while ((result = readdir(dir))) {
+               if (result->d_name[0] == '.' ||
+                   result->d_type == DT_DIR ||
+                   strncmp(result->d_name, "uevent", 6) == 0 ||
+                   strncmp(result->d_name, "power", 5) == 0 ||
+                   strncmp(result->d_name, "subsystem", 9) == 0)
+                       continue;
+               snprintf(node, sizeof(node), BOOT_REASON_PATH"/%s", result->d_name);
+               fd = open(node, O_RDONLY);
+               if (fd < 0)
+                       continue;
+               ret = read(fd, val, CLOG_PATH_MAX);
+               if (ret < 0 || ret >= CLOG_PATH_MAX) {
+                       cnt = UINT_MAX;
+               } else {
+                       val[ret] = '\0';
+                       cnt = strtoul(val, NULL, 10);
+               }
+               snprintf(clog + prefix, CLOG_MSG_MAX - prefix, "|%s%u ", result->d_name, cnt);
+               close(fd);
+               prefix = strlen(clog);
+       }
+       if (dir)
+               closedir(dir);
+out:
+       clog_execute((void *)clog);
+       clog_save_log();
+       unregister_notifier(DEVICE_NOTIFIER_BOOTING_DONE, clog_booting_done);
+       return done;
+}
+
+static void clog_init(void *none)
+{
+       register_notifier(DEVICE_NOTIFIER_BOOTING_DONE, clog_booting_done);
+       register_notifier(DEVICE_NOTIFIER_BATTERY_HEALTH, clog_battery_health);
+       register_notifier(DEVICE_NOTIFIER_BATTERY_PRESENT, clog_battery_present);
+       register_notifier(DEVICE_NOTIFIER_CRITICAL_LOG, clog_common_log);
+       register_notifier(DEVICE_NOTIFIER_POWEROFF, clog_power_off);
+       if (config_parse(CLOG_CONF_FILE, clog_load_config, NULL) < 0)
+               _E("Fail to init configuration.");
+}
+
+static void clog_exit(void *data)
+{
+       unregister_notifier(DEVICE_NOTIFIER_BATTERY_HEALTH, clog_battery_health);
+       unregister_notifier(DEVICE_NOTIFIER_BATTERY_PRESENT, clog_battery_present);
+       unregister_notifier(DEVICE_NOTIFIER_CRITICAL_LOG, clog_common_log);
+       unregister_notifier(DEVICE_NOTIFIER_POWEROFF, clog_power_off);
+       free_clog_data();
+}
+
+static const struct device_ops clog_device_ops = {
+       .name    = "clog",
+       .init    = clog_init,
+       .exit    = clog_exit,
+       .execute = clog_execute,
+};
+
+DEVICE_OPS_REGISTER(&clog_device_ops)