--- /dev/null
+/*
+ * 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)