mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-06-21 21:19:35 +00:00
tools: acrn-crashlog: channel module for acrnprobe
The channel represents a way of detecting the system's events. So far, there are 3 channels: 1. oneshot, detect once while acrnprobe startup. 2. polling, run a detecting job with fixed time interval. 3. inotify, watch the change of file or dir. Signed-off-by: Liu Xinwu <xinwu.liu@intel.com> Reviewed-by: Zhang Yanmin <yanmin.zhang@intel.com> Reviewed-by: Liu Chuansheng <chuansheng.liu@intel.com> Reviewed-by: Zhao Yakui <yakui.zhao@intel.com> Reviewed-by: Geoffroy Van Cutsem <geoffroy.vancutsem@intel.com> Acked-by: Eddie Dong <Eddie.dong@intel.com>
This commit is contained in:
parent
95d6a57358
commit
9caa5d72cf
@ -3,9 +3,516 @@
|
||||
* 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 "log_sys.h"
|
||||
|
||||
int init_channels(void)
|
||||
#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, channel = &channels[0]; \
|
||||
i < (int)ARRAY_SIZE(channels); \
|
||||
i++, channel++)
|
||||
|
||||
/**
|
||||
* 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 len 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,
|
||||
char *channel, void *private,
|
||||
int watchfd, char *path, int len)
|
||||
{
|
||||
struct event_t *e;
|
||||
|
||||
if (len < 0)
|
||||
return NULL;
|
||||
|
||||
e = malloc(sizeof(*e) + len + 1);
|
||||
if (e) {
|
||||
memset(e, 0, sizeof(*e) + len + 1);
|
||||
e->watchfd = watchfd;
|
||||
e->channel = channel;
|
||||
e->private = (void *)private;
|
||||
e->event_type = event_type;
|
||||
if (path && (len > 0)) {
|
||||
e->len = len;
|
||||
strncpy(e->path, path, len);
|
||||
}
|
||||
} 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);
|
||||
|
||||
e = create_event(REBOOT, cname, NULL, 0, NULL, 0);
|
||||
if (e)
|
||||
event_enqueue(e);
|
||||
|
||||
|
||||
for_each_crash(id, crash, conf) {
|
||||
if (!crash || !is_root_crash(crash))
|
||||
continue;
|
||||
|
||||
if (strcmp(crash->channel, cname))
|
||||
continue;
|
||||
|
||||
if (crash->trigger &&
|
||||
!strcmp("file", crash->trigger->type) &&
|
||||
file_exists(crash->trigger->path)) {
|
||||
e = create_event(CRASH, cname, (void *)crash,
|
||||
0, crash->trigger->path,
|
||||
strlen(crash->trigger->path));
|
||||
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;
|
||||
|
||||
/**
|
||||
* Callback thread of a polling job.
|
||||
*/
|
||||
static void polling_vm(union sigval v __attribute__((unused)))
|
||||
{
|
||||
|
||||
struct event_t *e = create_event(VM, "polling", NULL, 0, NULL, 0);
|
||||
|
||||
if (e)
|
||||
event_enqueue(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ret;
|
||||
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 (strncmp(vm->channel, "polling", strlen(vm->channel)))
|
||||
continue;
|
||||
|
||||
vm_job.timer_val = atoi(vm->interval);
|
||||
}
|
||||
|
||||
LOGD("start polling job with %ds\n", vm_job.timer_val);
|
||||
vm_job.fn = polling_vm;
|
||||
vm_job.type = VM;
|
||||
ret = create_polling_job(&vm_job);
|
||||
if (ret < 0) {
|
||||
LOGE("create polling job failed\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;
|
||||
}
|
||||
|
@ -6,6 +6,19 @@
|
||||
#ifndef _CHANNELS_H
|
||||
#define _CHANNELS_H
|
||||
|
||||
#define BASE_DIR_MASK (IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MOVE_SELF)
|
||||
#define UPTIME_MASK IN_CLOSE_WRITE
|
||||
#define MAXEVENTS 15
|
||||
#define HEART_RATE (6 * 1000) /* ms */
|
||||
|
||||
struct channel_t {
|
||||
char *name;
|
||||
int fd;
|
||||
void (*channel_fn)(struct channel_t *);
|
||||
};
|
||||
extern int create_detached_thread(pthread_t *pid,
|
||||
void *(*fn)(void *), void *arg);
|
||||
extern int init_channels(void);
|
||||
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user