mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-05-09 17:07:00 +00:00
1. Rename folder name with "_" 2. Category acrn_crashlog, acrn_log, acrn_trace into debug_tools folder and acrn_bridge, life_mngr, acrn_manager into services. Tracked-On: #5644 Signed-off-by: Xie, nanlin <nanlin.xie@intel.com>
583 lines
13 KiB
C
583 lines
13 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <errno.h>
|
|
#include <sys/inotify.h>
|
|
#include <sys/epoll.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include "load_conf.h"
|
|
#include "event_queue.h"
|
|
#include "fsutils.h"
|
|
#include "strutils.h"
|
|
#include "channels.h"
|
|
#include "startupreason.h"
|
|
#include "probeutils.h"
|
|
#include "log_sys.h"
|
|
#include "android_events.h"
|
|
#include "crash_reclassify.h"
|
|
|
|
#define POLLING_TIMER_SIG 0xCEAC
|
|
|
|
static void channel_oneshot(struct channel_t *cnl);
|
|
static void channel_polling(struct channel_t *cnl);
|
|
static void channel_inotify(struct channel_t *cnl);
|
|
|
|
/**
|
|
* @brief structure containing implementation of each channel.
|
|
*
|
|
* This structure describes all channels, all channel_* functions would
|
|
* called by main thread in order.
|
|
*/
|
|
static struct channel_t channels[] = {
|
|
{"oneshot", -1, channel_oneshot},
|
|
{"polling", -1, channel_polling},
|
|
{"inotify", -1, channel_inotify},
|
|
};
|
|
|
|
#define for_each_channel(i, channel) \
|
|
for (i = 0; \
|
|
(i < (int)ARRAY_SIZE(channels)) && (channel = &channels[i]); \
|
|
i++)
|
|
|
|
/**
|
|
* Helper function to create a event and fill event_t structure.
|
|
*
|
|
* @param event_type Type of this event.
|
|
* @param channel Channel where the event comes from.
|
|
* @param private The corresponding configuration info to the event.
|
|
* @param watchfd For watch channel, so far, only used by inotify.
|
|
* @param path File which trigger this event.
|
|
* @param plen The length of path.
|
|
*
|
|
* @return a pointer to the filled event if successful,
|
|
* or NULL on error.
|
|
*/
|
|
static struct event_t *create_event(enum event_type_t event_type,
|
|
const char *channel, void *private,
|
|
int watchfd, const char *path, size_t plen)
|
|
{
|
|
struct event_t *e;
|
|
size_t path_len = 0;
|
|
|
|
if (path) {
|
|
path_len = plen;
|
|
if (path_len > PATH_MAX) {
|
|
LOGE("invalid path, drop event.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
e = malloc(sizeof(*e) + path_len + 1);
|
|
if (e) {
|
|
memset(e, 0, sizeof(*e) + path_len + 1);
|
|
e->watchfd = watchfd;
|
|
e->channel = channel;
|
|
e->private = private;
|
|
e->event_type = event_type;
|
|
if (path_len > 0) {
|
|
e->len = path_len;
|
|
*(char *)(mempcpy(e->path, path, path_len)) = '\0';
|
|
}
|
|
} else {
|
|
LOGE("malloc failed, error (%s)\n", strerror(errno));
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* Only check once when process startup
|
|
*
|
|
* @param cnl Structure of channel.
|
|
*/
|
|
static void channel_oneshot(struct channel_t *cnl)
|
|
{
|
|
int id;
|
|
struct crash_t *crash;
|
|
struct info_t *info;
|
|
struct event_t *e;
|
|
char *cname = cnl->name;
|
|
|
|
LOGD("initializing channel %s ...\n", cname);
|
|
|
|
if (!is_boot_id_changed())
|
|
return;
|
|
|
|
for_each_crash(id, crash, conf) {
|
|
if (!crash || !is_root_crash(crash))
|
|
continue;
|
|
|
|
if (strcmp(crash->channel, cname))
|
|
continue;
|
|
|
|
if (!crash->trigger)
|
|
continue;
|
|
|
|
if (!strcmp("file", crash->trigger->type) ||
|
|
!strcmp("node", crash->trigger->type)) {
|
|
if (!crash_match_filefmt(crash, crash->trigger->path))
|
|
continue;
|
|
|
|
e = create_event(CRASH, cname, (void *)crash,
|
|
0, crash->trigger->path,
|
|
crash->trigger->path_len);
|
|
if (e)
|
|
event_enqueue(e);
|
|
} else if (!strcmp("rebootreason", crash->trigger->type)) {
|
|
char rreason[REBOOT_REASON_SIZE];
|
|
|
|
read_startupreason(rreason, sizeof(rreason));
|
|
if (!strcmp(rreason, crash->content[0])) {
|
|
e = create_event(CRASH, cname, (void *)crash,
|
|
0, crash->trigger->path,
|
|
crash->trigger->path_len);
|
|
if (e)
|
|
event_enqueue(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
e = create_event(REBOOT, cname, NULL, 0, NULL, 0);
|
|
if (e)
|
|
event_enqueue(e);
|
|
|
|
for_each_info(id, info, conf) {
|
|
if (!info)
|
|
continue;
|
|
|
|
if (strcmp(info->channel, cname))
|
|
continue;
|
|
|
|
if (info->trigger &&
|
|
!strcmp("file", info->trigger->type) &&
|
|
file_exists(info->trigger->path)) {
|
|
e = create_event(INFO, cname, (void *)info,
|
|
0, NULL, 0);
|
|
if (e)
|
|
event_enqueue(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* TODO: implement multiple polling jobs */
|
|
static struct polling_job_t {
|
|
timer_t timerid;
|
|
uint32_t timer_val;
|
|
|
|
enum event_type_t type;
|
|
void (*fn)(union sigval v);
|
|
} vm_job;
|
|
|
|
static int create_vm_event(const char *msg, size_t len, const struct vm_t *vm)
|
|
{
|
|
struct vm_event_t *vme = malloc(sizeof(*vme));
|
|
struct event_t *e;
|
|
|
|
if (!vme)
|
|
return VMEVT_DEFER;
|
|
|
|
vme->vm_msg = strndup(msg, len);
|
|
if (!vme->vm_msg) {
|
|
free(vme);
|
|
return VMEVT_DEFER;
|
|
}
|
|
|
|
vme->vm_msg_len = len;
|
|
vme->vm = vm;
|
|
|
|
e = create_event(VM, "polling", (void *)vme, 0, NULL, 0);
|
|
if (e) {
|
|
event_enqueue(e);
|
|
return VMEVT_HANDLED;
|
|
}
|
|
|
|
free(vme->vm_msg);
|
|
free(vme);
|
|
return VMEVT_DEFER;
|
|
}
|
|
|
|
/**
|
|
* Callback thread of a polling job.
|
|
*/
|
|
static void polling_vm(union sigval v __attribute__((unused)))
|
|
{
|
|
refresh_vm_history(get_sender_by_name("crashlog"), create_vm_event);
|
|
}
|
|
|
|
/**
|
|
* Setup a timer with specific loop time. The callback fn will be performed
|
|
* after timer expire.
|
|
*
|
|
* @param pjob Polling_job filled by caller.
|
|
*
|
|
* @return 0 if successful, or -1 if not.
|
|
*/
|
|
static int create_polling_job(struct polling_job_t *pjob)
|
|
{
|
|
struct sigevent sig_evt;
|
|
struct itimerspec timer_val;
|
|
|
|
memset(&sig_evt, 0, sizeof(struct sigevent));
|
|
sig_evt.sigev_value.sival_int = POLLING_TIMER_SIG;
|
|
sig_evt.sigev_notify = SIGEV_THREAD;
|
|
sig_evt.sigev_notify_function = pjob->fn;
|
|
|
|
if (timer_create(CLOCK_REALTIME, &sig_evt, &pjob->timerid) == -1) {
|
|
LOGE("timer_create failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(&timer_val, 0, sizeof(struct itimerspec));
|
|
timer_val.it_value.tv_sec = pjob->timer_val;
|
|
timer_val.it_interval.tv_sec = pjob->timer_val;
|
|
|
|
if (timer_settime(pjob->timerid, 0, &timer_val, NULL) == -1) {
|
|
LOGE("timer_settime failed.\n");
|
|
timer_delete(pjob->timerid);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Setup polling jobs. These jobs running with fixed time interval.
|
|
*
|
|
* @param cnl Structure of channel.
|
|
*/
|
|
static void channel_polling(struct channel_t *cnl)
|
|
{
|
|
int id;
|
|
int jt;
|
|
struct vm_t *vm;
|
|
char *cname = cnl->name;
|
|
|
|
LOGD("initializing channel %s ...\n", cname);
|
|
|
|
/* one job for all vm polling*/
|
|
for_each_vm(id, vm, conf) {
|
|
if (!vm)
|
|
continue;
|
|
|
|
if (strcmp(vm->channel, "polling"))
|
|
continue;
|
|
|
|
if (cfg_atoi(vm->interval, vm->interval_len,
|
|
&jt) == -1) {
|
|
LOGE("invalid interval (%s) in config file, exiting\n",
|
|
vm->interval);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (jt <= 0) {
|
|
LOGE("interval (%s) must be greater than 0, exiting\n",
|
|
vm->interval);
|
|
exit(EXIT_FAILURE);
|
|
} else
|
|
vm_job.timer_val = (uint32_t)jt;
|
|
|
|
}
|
|
|
|
LOGD("start polling job with %ds\n", vm_job.timer_val);
|
|
vm_job.fn = polling_vm;
|
|
vm_job.type = VM;
|
|
if (create_polling_job(&vm_job) == -1) {
|
|
LOGE("failed to create polling job\n, error (%s)\n",
|
|
strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup inotify, watch the changes of dir/file.
|
|
*
|
|
* @param cnl Structure of channel.
|
|
*/
|
|
static void channel_inotify(struct channel_t *cnl)
|
|
{
|
|
int inotify_fd;
|
|
int id;
|
|
struct crash_t *crash;
|
|
struct sender_t *sender;
|
|
struct uptime_t *uptime;
|
|
struct trigger_t *trigger;
|
|
char *cname = cnl->name;
|
|
|
|
LOGD("initializing channel %s ...\n", cname);
|
|
|
|
/* use this func to get "return 0" from read */
|
|
inotify_fd = inotify_init1(IN_NONBLOCK);
|
|
if (inotify_fd < 0) {
|
|
LOGE("inotify init fail, %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
for_each_crash(id, crash, conf) {
|
|
if (!crash || !is_root_crash(crash))
|
|
continue;
|
|
|
|
if (strcmp(crash->channel, cname))
|
|
continue;
|
|
|
|
if (!crash->trigger)
|
|
continue;
|
|
|
|
trigger = crash->trigger;
|
|
if (!strcmp("dir", trigger->type)) {
|
|
if (directory_exists(trigger->path)) {
|
|
crash->wd = inotify_add_watch(inotify_fd,
|
|
trigger->path,
|
|
BASE_DIR_MASK);
|
|
if (crash->wd < 0) {
|
|
LOGE("add %s failed, error (%s)\n",
|
|
trigger->path, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
LOGI("add %s succuessed\n", trigger->path);
|
|
} else {
|
|
LOGW("path to watch (%s) isn't exsits\n",
|
|
trigger->path);
|
|
}
|
|
}
|
|
/* TODO: else for other types to use channel inotify */
|
|
}
|
|
/* add uptime path for each sender */
|
|
for_each_sender(id, sender, conf) {
|
|
if (!sender)
|
|
continue;
|
|
|
|
uptime = sender->uptime;
|
|
uptime->wd = inotify_add_watch(inotify_fd, uptime->path,
|
|
UPTIME_MASK);
|
|
if (uptime->wd < 0) {
|
|
LOGE("add %s failed, error (%s)\n",
|
|
uptime->path, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
LOGI("add %s succuessed\n", uptime->path);
|
|
}
|
|
|
|
cnl->fd = inotify_fd;
|
|
}
|
|
|
|
/**
|
|
* Handle inotify events, read out all events and enqueue.
|
|
*
|
|
* @param channel Channel structure of inotify.
|
|
*
|
|
* @return 0 if successful, or -1 if not.
|
|
*/
|
|
static int receive_inotify_events(struct channel_t *channel)
|
|
{
|
|
int len;
|
|
int read_left;
|
|
char buf[256];
|
|
char *p;
|
|
struct event_t *e;
|
|
struct inotify_event *ievent;
|
|
enum event_type_t event_type;
|
|
void *private;
|
|
|
|
read_left = 0;
|
|
while (1) {
|
|
len = read(channel->fd, (char *)&buf[read_left],
|
|
(int)sizeof(buf) - read_left);
|
|
if (len < 0) {
|
|
if (errno == EAGAIN)
|
|
break;
|
|
LOGE("read fail with (%d, %p, %d), error: %s\n",
|
|
channel->fd, (char *)&buf[read_left],
|
|
(int)sizeof(buf) - read_left, strerror(errno));
|
|
return -1;
|
|
}
|
|
if (len == 0)
|
|
break;
|
|
|
|
for (p = buf; p < buf + read_left + len;) {
|
|
if (p + sizeof(struct inotify_event) >
|
|
&buf[0] + len + read_left) {
|
|
/* we dont recv the entire inotify_event yet */
|
|
break;
|
|
}
|
|
|
|
/* then we can get len */
|
|
ievent = (struct inotify_event *)p;
|
|
if (p + sizeof(struct inotify_event) + ievent->len >
|
|
&buf[0] + len + read_left) {
|
|
/* we dont recv the entire
|
|
* inotify_event + name
|
|
*/
|
|
break;
|
|
}
|
|
/* we have a entire event, send it... */
|
|
event_type = get_conf_by_wd(ievent->wd, &private);
|
|
if (event_type == UNKNOWN) {
|
|
LOGE("get a unknown event\n");
|
|
} else {
|
|
e = create_event(event_type, channel->name,
|
|
private, channel->fd,
|
|
ievent->name, ievent->len);
|
|
if (e)
|
|
event_enqueue(e);
|
|
}
|
|
/* next event start */
|
|
p += sizeof(struct inotify_event) + ievent->len;
|
|
}
|
|
/* move the bytes that have been read out to the head of buf,
|
|
* and let the rest of the buf do recv continually
|
|
*/
|
|
read_left = &buf[0] + len + read_left - p;
|
|
memmove(buf, p, read_left);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Enqueue a HEART_BEAT event to event_queue.
|
|
*/
|
|
static void heart_beat(void)
|
|
{
|
|
struct event_t *e = create_event(HEART_BEAT, NULL, NULL, 0, NULL, 0);
|
|
|
|
if (e)
|
|
event_enqueue(e);
|
|
}
|
|
|
|
/**
|
|
* Wait events asynchronously for all watch needed channels.
|
|
*/
|
|
static void *wait_events(void *unused __attribute__((unused)))
|
|
{
|
|
int epfd;
|
|
int id;
|
|
int ret;
|
|
struct channel_t *channel;
|
|
struct epoll_event ev, *events;
|
|
|
|
epfd = epoll_create(MAXEVENTS + 1);
|
|
if (epfd < 0) {
|
|
LOGE("epoll_create failed, exiting\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ev.events = EPOLLIN | EPOLLET;
|
|
for_each_channel(id, channel) {
|
|
if (channel->fd <= 0)
|
|
continue;
|
|
|
|
ev.data.fd = channel->fd;
|
|
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, channel->fd, &ev);
|
|
if (ret < 0) {
|
|
LOGE("epoll_ctl failed, exiting\n");
|
|
exit(EXIT_FAILURE);
|
|
} else
|
|
LOGD("add (%d) to epoll for (%s)\n", channel->fd,
|
|
channel->name);
|
|
}
|
|
|
|
events = calloc(MAXEVENTS, sizeof(ev));
|
|
if (events == NULL) {
|
|
LOGE("calloc failed, error (%s)\n", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
while (1) {
|
|
int i;
|
|
int n;
|
|
|
|
n = epoll_wait(epfd, events, MAXEVENTS, HEART_RATE);
|
|
heart_beat();
|
|
for (i = 0; i < n; i++) {
|
|
for_each_channel(id, channel)
|
|
if (channel->fd == events[i].data.fd) {
|
|
if (events[i].events & EPOLLERR ||
|
|
!(events[i].events & EPOLLIN)) {
|
|
LOGE("error ev, channel:%s\n",
|
|
channel->name);
|
|
continue;
|
|
}
|
|
/* Until now, we only have
|
|
* inotify channel to wait
|
|
*/
|
|
receive_inotify_events(channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a detached thread.
|
|
* Once these thread exit, their resources would be released immediately.
|
|
*
|
|
* @param[out] pid Pid of new thread.
|
|
* @param fn The entry of new thread.
|
|
* @param arg The arg of fn.
|
|
*
|
|
* @return 0 if successful, or errno if not.
|
|
*/
|
|
int create_detached_thread(pthread_t *pid, void *(*fn)(void *), void *arg)
|
|
{
|
|
int ret;
|
|
pthread_attr_t attr;
|
|
|
|
ret = pthread_attr_init(&attr);
|
|
if (ret) {
|
|
LOGE("pthread attr init failed\n, error (%s)\n",
|
|
strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
if (ret) {
|
|
LOGE("pthread attr setdetachstate failed\n, error (%s)\n",
|
|
strerror(ret));
|
|
goto fail;
|
|
}
|
|
|
|
ret = pthread_create(pid, &attr, fn, arg);
|
|
if (ret) {
|
|
LOGE("pthread create failed\n, error (%s)\n",
|
|
strerror(ret));
|
|
}
|
|
fail:
|
|
pthread_attr_destroy(&attr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Initailize all channels, performing channel_* in channels one by one.
|
|
*
|
|
* @return 0 if successful, or errno if not.
|
|
*/
|
|
int init_channels(void)
|
|
{
|
|
pthread_t pid;
|
|
int id;
|
|
int ret;
|
|
struct channel_t *channel;
|
|
|
|
for_each_channel(id, channel) {
|
|
channel->channel_fn(channel);
|
|
}
|
|
|
|
ret = create_detached_thread(&pid, &wait_events, NULL);
|
|
if (ret) {
|
|
LOGE("create wait_events fail, ret (%s)\n", strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|