acrn-hypervisor/misc/debug_tools/acrn_crashlog/acrnprobe/channels.c
Xie, nanlin 0ab5db9cf9 tools: rename and category content into debug tools and services
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>
2021-01-27 11:08:28 +08:00

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;
}