HV:Acrn-hypvervisor Root Directory Clean-up and create misc/ folder for Acrn daemons, services and tools.

This patch is to clean-up acrn-hypervisor root directory, targt only 5 folders under acrn-hypervisor:1.hypervisor,2.devicemodel,3.misc,4.doc,5.build

Tracked-On: #3482
Signed-off-by: Terry Zou <terry.zou@intel.com>
Acked-by: Eddie Dong <eddie.dong@intel.com>
This commit is contained in:
Terry Zou
2019-07-29 12:21:54 +08:00
committed by Xie, Nanlin
parent 555a03db99
commit a9c38a5cfb
119 changed files with 62 additions and 57 deletions

View File

@@ -0,0 +1,83 @@
MAJOR_VERSION=1
MINOR_VERSION=0
VERSION_H = $(BUILDDIR)/include/acrnprobe/version.h
LIBS = -lpthread -lxml2 -lcrypto -lrt -lblkid -lext2fs -lcom_err \
$(EXTRA_LIBS)
INCLUDE += -I $(CURDIR)/include -I $(SYSROOT)/usr/include/libxml2
INCLUDE += -I $(BUILDDIR)/include/acrnprobe
CFLAGS += $(INCLUDE)
CFLAGS += -fdata-sections
LDFLAGS += $(LIBS) -Wl,--gc-sections
TARGET = $(BUILDDIR)/acrnprobe/bin/acrnprobe
.PHONY: all check_dirs
all: $(VERSION_H) check_dirs $(TARGET)
rm -f $(VERSION_H)
$(BUILDDIR)/acrnprobe/obj/%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
$(BUILDDIR)/acrnprobe/bin/acrnprobe: $(BUILDDIR)/acrnprobe/obj/main.o \
$(BUILDDIR)/common/obj/log_sys.o \
$(BUILDDIR)/common/obj/cmdutils.o \
$(BUILDDIR)/common/obj/fsutils.o \
$(BUILDDIR)/common/obj/strutils.o \
$(BUILDDIR)/acrnprobe/obj/load_conf.o \
$(BUILDDIR)/acrnprobe/obj/channels.o \
$(BUILDDIR)/acrnprobe/obj/event_queue.o \
$(BUILDDIR)/acrnprobe/obj/event_handler.o \
$(BUILDDIR)/acrnprobe/obj/crash_reclassify.o \
$(BUILDDIR)/acrnprobe/obj/sender.o \
$(BUILDDIR)/acrnprobe/obj/startupreason.o \
$(BUILDDIR)/acrnprobe/obj/property.o \
$(BUILDDIR)/acrnprobe/obj/probeutils.o \
$(BUILDDIR)/acrnprobe/obj/history.o \
$(BUILDDIR)/acrnprobe/obj/android_events.o \
$(BUILDDIR)/acrnprobe/obj/loop.o \
$(BUILDDIR)/acrnprobe/obj/vmrecord.o
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean
clean:
@echo "Clean objects and binaries"
@if [ -e $(VERSION_H) ]; then \
$(RM) -f $(VERSION_H); \
fi
@if [ -d $(BUILDDIR)/acrnprobe/obj ]; then \
find $(BUILDDIR)/acrnprobe/obj -name "*.o" -exec $(RM) {} \; 2>&1 || exit 0; \
fi
@if [ -d $(BUILDDIR)/acrnprobe/bin ]; then \
$(RM) -r $(BUILDDIR)/acrnprobe/bin ; \
fi
@if [ -d $(BUILDDIR)/acrnprobe/obj ]; then \
$(RM) -r $(BUILDDIR)/acrnprobe/obj ; \
fi
$(VERSION_H):
@if [ ! -d $(BUILDDIR)/include/acrnprobe ]; then \
mkdir -p $(BUILDDIR)/include/acrnprobe ; \
fi
touch $(VERSION_H)
@COMMIT=`git log -1 --pretty=format:%h . 2>/dev/null`;\
DIRTY=`git diff --name-only $(CURDIR)`;\
if [ -n "$$DIRTY" ];then PATCH="$$COMMIT-dirty";else PATCH="$$COMMIT";fi;\
TIME=`date "+%Y-%m-%d %H:%M:%S"`;\
USER=`id -u -n`; \
cat $(CURDIR)/../license_header > $(VERSION_H);\
echo "#define AP_MAJOR_VERSION $(MAJOR_VERSION)" >> $(VERSION_H);\
echo "#define AP_MINOR_VERSION $(MINOR_VERSION)" >> $(VERSION_H);\
echo "#define AP_BUILD_VERSION "\""$$PATCH"\""" >> $(VERSION_H);\
echo "#define AP_BUILD_TIME "\""$$TIME"\""" >> $(VERSION_H);\
echo "#define AP_BUILD_USER "\""$$USER"\""" >> $(VERSION_H)
check_dirs:
@if [ ! -d $(BUILDDIR)/acrnprobe/bin ]; then \
mkdir -p $(BUILDDIR)/acrnprobe/bin ; \
fi
@if [ ! -d $(BUILDDIR)/acrnprobe/obj ]; then \
mkdir -p $(BUILDDIR)/acrnprobe/obj ; \
fi

View File

@@ -0,0 +1,193 @@
.. _acrnprobe_doc:
acrnprobe
#########
Description
***********
The ``acrnprobe`` is a tool to detect all critical events on the platform and
collect specific information for them. The collected information would be saved
as logs. The log path would be delivered to `telemetrics-client`_ as a record if
telemetrics-client exists on the system. In this case ``acrnprobe`` works as a
*probe* of telemetrics-client. If telemetrics-client doesn't exist on the
system, ``acrnprobe`` provides ``history_event`` (under ``/var/log/crashlog/``
by default) to manage the crash and events records on the platform instead of
``telem_journal``. But in this case, the records can't be delivered to the
backend.
Usage
*****
The ``acrnprobe`` is launched as a service at boot. Also, it provides some basic
options:
Specify a configuration file for ``acrnprobe``. If this option is unused,
``acrnprobe`` will use the configuration file located in CUSTOM CONFIGURATION
PATH or INSTALLATION PATH (see `CONFIGURATION FILES`_).
.. code-block:: none
$ acrnprobe -c [configuration_path]
To see the version of ``acrnprobe``.
.. code-block:: none
$ acrnprobe -V
Architecture
************
Terms
=====
- channel :
Channel represents a way of detecting the system's events. There are 3
channels:
+ oneshot: detect once while ``acrnprobe`` startup.
+ polling: run a detecting job with fixed time interval.
+ inotify: monitor the change of file or dir.
- trigger :
Essentially, trigger represents one section of content. It could be
a file's content, a directory's content, or a memory's content which can be
obtained. By monitoring it ``acrnprobe`` could detect certain events which
happened in the system.
- crash :
A subtype of event. It often corresponds to a crash of programs, system, or
hypervisor. ``acrnprobe`` detects it and reports it as ``CRASH``.
- info :
A subtype of event. ``acrnprobe`` detects it and reports it as ``INFO``.
- event queue :
There is a global queue to receive all events detected.
Generally, events are enqueued in channel, and dequeued in event handler.
- event handler :
Event handler is a thread to handle events detected by channel.
It's awakened by an enqueued event.
- sender :
The sender corresponds to an exit of event.
There are two senders:
+ Crashlog is responsible for collecting logs and saving it locally.
+ Telemd is responsible for sending log records to telemetrics client.
Description
===========
As a log collection mechanism to record critical events on the platform,
``acrnprobe`` provides these functions:
1. detect event
From experience, the occurrence of an system event is usually accompanied
by some effects. The effects could be a generated file, an error message in
kernel's log, or a system reboot. To get these effects, for some of them we
can monitor a directory, for other of them we might need to do a detection
in a time loop.
*So we implement the channel, which represents a common method of detection.*
2. analyze event and determine the event type
Generally, a specific effect correspond to a particular type of events.
However, it is the icing on the cake for analyzing the detailed event types
according to some phenomena. *Crash reclassify is implemented for this
purpose.*
3. collect information for detected events
This is for debug purpose. Events without information are meaningless,
and developers need to use this information to improve their system. *Sender
crashlog is implemented for this purpose.*
4. archive these information as logs, and generate records
There must be a central place to tell user what happened in system.
*Sender telemd is implemented for this purpose.*
Diagram
=======
::
+---------------------------------------------+
| channel: |oneshot| |polling| |inotify| |
+--------------------------------------+------+
|
+---------------------+ +-----+ |
| event queue +<---+event+<----+
+-+-------------------+ +-----+
|
v
+-+---------------------------------------------------------------------------+
| event handler: |
| |
| event handler will handle internal event |
| +----------+ +------------+ |
| |heart beat+--->+fed watchdog| |
| +----------+ +------------+ |
| |
| call sender for other types |
| +--------+ +----------------+ +------------+ +------------------+ |
| |crashlog+-->+crash reclassify+-->+collect logs+-->+generate crashfile| |
| +--------+ +----------------+ +------------+ +------------------+ |
| |
| +------+ +------------------+ |
| |telemd+--->+telemetrics client| |
| +------+ +------------------+ |
+-----------------------------------------------------------------------------+
Source files
************
- main.c
Entry of ``acrnprobe``.
- channel.c
The implementation of *channel* (see `Terms`_).
- crash_reclassify.c
Analyzing the detailed types for crash event.
- probeutils.c
Provide some utils ``acrnprobe`` needs.
- event_queue.c
The implementation of *event queue* (see `Terms`_).
- event_handler.c
The implementation of *event handler* (see `Terms`_).
- history.c
There is a history_event file to manage all logs that ``acrnprobe`` archived.
"history.c" provides the interfaces to modify the file in fixed format.
- load_conf.c
Parse and load the configuration file.
- property.c
The ``acrnprobe`` needs to know some HW/SW properties, such as board version,
build version. These properties are managed centrally in this file.
- sender.c
The implementation of *sender* (see `Terms`_).
- startupreason.c
This file provides the function to get system reboot reason from kernel
command line.
- android_events.c
Sync events detected by android crashlog.
- loop.c
This file provides interfaces to read from image.
Configuration files
*******************
* ``/usr/share/defaults/telemetrics/acrnprobe.xml``
If no custom configuration file is found, ``acrnprobe`` uses the settings in
this file.
* ``/etc/acrnprobe.xml``
Custom configuration file that ``acrnprobe`` reads.
For details about configuration file, please refer to :ref:`acrnprobe-conf`.
.. _`telemetrics-client`: https://github.com/clearlinux/telemetrics-client

View File

@@ -0,0 +1,494 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include <openssl/sha.h>
#include <signal.h>
#include <limits.h>
#include <stdlib.h>
#include "android_events.h"
#include "strutils.h"
#include "cmdutils.h"
#include "log_sys.h"
#include "fsutils.h"
#include "history.h"
#include "loop.h"
#include "vmrecord.h"
#define VM_WARNING_LINES 2000
#define ANDROID_DATA_PAR_NAME "data"
#define ANDROID_EVT_KEY_LEN 20
/* TODO: hardcoding the img path here means that only one Android Guest OS
* is supoorted at this moment. To support multiple Android Guest OS, this
* path should be moved to structure vm_t and configurable.
*/
static const char *android_img = "/data/android/android.img";
static const char *android_histpath = "logs/history_event";
char *loop_dev;
/* Find the next event that needs to be synced.
* There is a history_event file in UOS side, it records UOS's events in
* real-time. Generally, the cursor point to the first unsynchronized line.
*/
static char *next_vm_event(const char *cursor, const char *data,
size_t dlen, const struct vm_t *vm)
{
char *line_to_sync = (char *)~(0);
const char *syncevent;
int id;
if (!cursor || !vm)
return NULL;
/* find all syncing types start from cursor,
* focus the event with smaller address.
*/
for_each_syncevent_vm(id, syncevent, vm) {
char *p;
char *new;
char *type;
int tlen;
size_t len;
if (!syncevent)
continue;
tlen = asprintf(&type, "\n%s ", syncevent);
if (tlen == -1) {
LOGE("out of memory\n");
return NULL;
}
/* a sync event may be configured as type/subtype */
p = strchr(type, '/');
if (p) {
char *subtype;
int stlen;
tlen = p - type;
stlen = asprintf(&subtype, " %s", p + 1);
if (stlen == -1) {
free(type);
LOGE("out of memory\n");
return NULL;
}
new = get_line(subtype, (size_t)stlen, data, dlen,
cursor, &len);
free(subtype);
/*
* ignore the result if 'line' does not start with
* 'type'.
*/
if (!new || memcmp(new, type + 1, tlen - 1) ||
*(new + tlen - 1) != ' ') {
free(type);
continue;
}
} else {
new = get_line(type, (size_t)tlen, data, dlen,
cursor, &len);
}
if (new)
line_to_sync = MIN(line_to_sync, new);
free(type);
}
if (line_to_sync == (char *)~(0))
return NULL;
return line_to_sync;
}
static int get_vms_history(const struct sender_t *sender)
{
struct vm_t *vm;
unsigned long size;
int ret;
int id;
for_each_vm(id, vm, conf) {
if (!vm)
continue;
if (e2fs_open(loop_dev, &vm->datafs) == -1)
continue;
if (e2fs_read_file_by_fpath(vm->datafs, android_histpath,
(void **)&vm->history_data,
&size) == -1) {
LOGE("failed to get vm_history from (%s).\n", vm->name);
vm->history_data = NULL;
e2fs_close(vm->datafs);
vm->datafs = NULL;
continue;
}
e2fs_close(vm->datafs);
vm->datafs = NULL;
if (!size) {
LOGE("empty vm_history from (%s).\n", vm->name);
vm->history_data = NULL;
continue;
}
/* warning large history file once */
if (size == vm->history_size[sender->id])
continue;
ret = strcnt(vm->history_data, '\n');
if (ret > VM_WARNING_LINES)
LOGW("File too large, (%d) lines in (%s) of (%s)\n",
ret, android_histpath, vm->name);
vm->history_size[sender->id] = size;
}
return 0;
}
/**
* There are 2 stages in vm events sync.
* Stage1: detect new vm events and record them into log_vmrecordid file.
* Stage2: push the recorded events to event_queue, the senders will do
* the corresponding process.
*
* The design reason is giving UOS some time to get log stored.
*/
static void detect_new_events(struct sender_t *sender)
{
int id;
struct vm_t *vm;
for_each_vm(id, vm, conf) {
char *data;
size_t data_size;
char *start;
char *last_key;
char *line_to_sync;
if (!vm || !vm->history_data)
continue;
data = vm->history_data;
data_size = vm->history_size[sender->id];
last_key = &vm->last_evt_detected[sender->id][0];
if (*last_key) {
start = strstr(data, last_key);
if (start == NULL) {
LOGW("no synced id (%s), sync from head\n",
last_key);
start = data;
} else {
start = strchr(start, '\n');
}
} else {
start = data;
}
while ((line_to_sync = next_vm_event(start, data, data_size,
vm))) {
/* It's possible that log's content isn't ready
* at this moment, so we postpone the fn until
* the next loop
*/
//fn(line_to_sync, vm);
char vmkey[ANDROID_WORD_LEN];
ssize_t len;
const char * const vm_format =
IGN_ONEWORD ANDROID_KEY_FMT IGN_RESTS;
len = strlinelen(line_to_sync,
data + data_size - line_to_sync);
if (len == -1)
break;
start = strchr(line_to_sync, '\n');
if (str_split_ere(line_to_sync, len + 1, vm_format,
strlen(vm_format), vmkey,
sizeof(vmkey)) != 1) {
LOGE("get an invalid line from (%s), skip\n",
vm->name);
continue;
}
if ((strnlen(vmkey, sizeof(vmkey)) !=
ANDROID_EVT_KEY_LEN) ||
!strcmp(vmkey, "00000000000000000000")) {
LOGE("invalid key (%s) from (%s)\n",
vmkey, vm->name);
continue;
}
LOGD("stage1 %s\n", vmkey);
*(char *)(mempcpy(vm->last_evt_detected[sender->id],
vmkey, ANDROID_EVT_KEY_LEN)) = '\0';
if (vmrecord_new(&sender->vmrecord, vm->name,
vmkey) == -1)
LOGE("failed to new vm record\n");
}
}
}
static char *next_record(const struct mm_file_t *file, const char *fstart,
size_t *len)
{
const char *tag = " " VMRECORD_TAG_WAITING_SYNC;
size_t tlen = strlen(tag);
return get_line(tag, tlen, file->begin, file->size, fstart, len);
}
static void fire_detected_events(struct sender_t *sender,
int (*fn)(const char*, size_t, const struct vm_t *))
{
struct mm_file_t *recos;
char *record;
size_t recolen;
pthread_mutex_lock(&sender->vmrecord.mtx);
recos = mmap_file(sender->vmrecord.path);
if (!recos) {
LOGE("failed to mmap %s, %s\n", sender->vmrecord.path,
strerror(errno));
pthread_mutex_unlock(&sender->vmrecord.mtx);
return;
}
if (!recos->size ||
mm_count_lines(recos) < VMRECORD_HEAD_LINES) {
LOGE("(%s) invalid\n", sender->vmrecord.path);
goto out;
}
sender->vmrecord.recos = recos;
for (record = next_record(recos, recos->begin, &recolen); record;
record = next_record(recos, record + recolen, &recolen)) {
const char * const record_fmt =
VM_NAME_FMT ANDROID_KEY_FMT IGN_RESTS;
char *hist_line;
size_t len;
char vm_name[32];
char vmkey[ANDROID_WORD_LEN];
struct vm_t *vm;
int res;
/* VMNAME xxxxxxxxxxxxxxxxxxxx <== */
if (str_split_ere(record, recolen,
record_fmt, strlen(record_fmt),
vm_name, sizeof(vm_name),
vmkey, sizeof(vmkey)) != 2) {
LOGE("failed to parse vm record\n");
continue;
}
vm = get_vm_by_name((const char *)vm_name);
if (!vm || !vm->history_data)
continue;
hist_line = get_line(vmkey, strnlen(vmkey, sizeof(vmkey)),
vm->history_data,
vm->history_size[sender->id],
vm->history_data, &len);
if (!hist_line) {
vmrecord_mark(&sender->vmrecord, vmkey,
strnlen(vmkey, sizeof(vmkey)), NOT_FOUND);
continue;
}
res = fn(hist_line, len + 1, vm);
if (res == VMEVT_HANDLED)
vmrecord_mark(&sender->vmrecord, vmkey,
strnlen(vmkey, sizeof(vmkey)), ON_GOING);
}
out:
unmap_file(recos);
pthread_mutex_unlock(&sender->vmrecord.mtx);
}
/* This function only for initialization */
static void get_last_evt_detected(struct sender_t *sender)
{
int id;
struct vm_t *vm;
for_each_vm(id, vm, conf) {
char vmkey[ANDROID_WORD_LEN];
if (!vm)
continue;
/* generally only exec for each vm once */
if (vm->last_evt_detected[sender->id][0])
continue;
if (vmrecord_last(&sender->vmrecord, vm->name, vm->name_len,
vmkey, sizeof(vmkey)) == -1)
continue;
if (strnlen(vmkey, sizeof(vmkey)) != ANDROID_EVT_KEY_LEN) {
LOGE("get an invalid vm event (%s) in (%s)\n",
vmkey, sender->vmrecord.path);
continue;
}
*(char *)(mempcpy(vm->last_evt_detected[sender->id], vmkey,
ANDROID_EVT_KEY_LEN)) = '\0';
}
}
static char *setup_loop_dev(void)
{
/* Currently UOS image(/data/android/android.img) mounted by
* launch_UOS.sh, we need mount its data partition to loop device
*/
char loop_dev_tmp[32];
int i;
int res;
int devnr;
if (!file_exists(android_img)) {
LOGW("img(%s) is not available\n", android_img);
return NULL;
}
devnr = loopdev_num_get_free();
if (devnr < 0) {
LOGE("failed to get free loop device\n");
return NULL;
}
for (i = 0; i < devnr; i++) {
res = snprintf(loop_dev_tmp, ARRAY_SIZE(loop_dev_tmp),
"/dev/loop%d", i);
if (s_not_expect(res, ARRAY_SIZE(loop_dev_tmp)))
return NULL;
if (loopdev_check_parname(loop_dev_tmp,
ANDROID_DATA_PAR_NAME)) {
loop_dev = strdup(loop_dev_tmp);
if (!loop_dev) {
LOGE("out of memory\n");
return NULL;
}
return loop_dev;
}
}
res = asprintf(&loop_dev, "/dev/loop%d", devnr);
if (res == -1) {
LOGE("out of memory\n");
return NULL;
}
res = loopdev_set_img_par(loop_dev, android_img, ANDROID_DATA_PAR_NAME);
if (res == -1) {
LOGE("failed to setup loopdev.\n");
free(loop_dev);
loop_dev = NULL;
return NULL;
}
return loop_dev;
}
/* This function searches all android vms' new events and call the fn for
* each event.
*
* Note that: fn should return VMEVT_HANDLED to indicate event has been handled.
* fn will be called in a time loop if it returns VMEVT_DEFER.
*/
void refresh_vm_history(struct sender_t *sender,
int (*fn)(const char*, size_t, const struct vm_t *))
{
struct vm_t *vm;
int id;
if (!sender)
return;
if (!loop_dev) {
loop_dev = setup_loop_dev();
if (!loop_dev)
return;
LOGI("setup loop dev successful\n");
}
if (vmrecord_gen_ifnot_exists(&sender->vmrecord) == -1) {
LOGE("failed to create vmrecord\n");
return;
}
get_last_evt_detected(sender);
get_vms_history(sender);
/* read events from vmrecords and mark them as ongoing */
fire_detected_events(sender, fn);
/* add events to vmrecords */
detect_new_events(sender);
for_each_vm(id, vm, conf) {
if (!vm)
continue;
if (vm->history_data) {
free(vm->history_data);
vm->history_data = NULL;
}
}
}
int android_event_analyze(const char *msg, size_t len, char **result,
size_t *rsize)
{
char *data;
char *tail;
size_t data_len;
char event[ANDROID_WORD_LEN];
char longtime[ANDROID_WORD_LEN];
char type[ANDROID_WORD_LEN];
char rest[PATH_MAX];
char vmkey[ANDROID_WORD_LEN];
const char * const format =
ANDROID_ENEVT_FMT ANDROID_KEY_FMT ANDROID_LONGTIME_FMT
ANDROID_TYPE_FMT ANDROID_LINE_REST_FMT;
if (str_split_ere(msg, len, format, strlen(format), event,
sizeof(event), vmkey, sizeof(vmkey), longtime,
sizeof(longtime), type, sizeof(type), rest,
sizeof(rest)) != 5) {
LOGE("try to analyze an invalid line (%s), skip\n", msg);
return -1;
}
data_len = strnlen(vmkey, sizeof(vmkey)) + 1;
data_len += strnlen(event, sizeof(event)) + 1;
data_len += strnlen(type, sizeof(type)) + 1;
data_len += strnlen(rest, sizeof(rest)) + 1;
data = malloc(data_len);
if (!data)
return -1;
tail = (char *)mempcpy(data, vmkey, strnlen(vmkey, sizeof(vmkey)));
*(tail++) = '\0';
tail = (char *)mempcpy(tail, event, strnlen(event, sizeof(event)));
*(tail++) = '\0';
tail = (char *)mempcpy(tail, type, strnlen(type, sizeof(type)));
*(tail++) = '\0';
*(char *)mempcpy(tail, rest, strnlen(rest, sizeof(rest))) = '\0';
*result = data;
*rsize = data_len;
return 0;
}

View File

@@ -0,0 +1,582 @@
/*
* 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;
}

View File

@@ -0,0 +1,374 @@
.. _acrnprobe-conf:
acrnprobe Configuration
#######################
Description
***********
``acrnprobe`` uses XML as the format of its configuration file, namely
``acrnprobe.xml``, following the `XML standard`_.
Layout
******
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<conf>
Root node of configuration.
<senders>
Configuration section of senders.
<sender id='1'>Configuration of sender 1</sender>
<sender id='2'>Configuration of sender 2</sender>
</senders>
<triggers>
Configuration section of triggers.
<trigger id='1'>Configuration of trigger 1</trigger>
<trigger id='2'>Configuration of trigger 2</trigger>
</triggers>
<vms>
Configuration section of virtual machines.
<vm id='1'>Configuration of vm 1</vm>
<vm id='2'>Configuration of vm 2</vm>
</vms>
<logs>
Configuration section of logs.
<log id='1'>Configuration of log 1</log>
<log id='2'>Configuration of log 2</log>
</logs>
<crashes>
Configuration section of crashes.
Note that this section must be configured after triggers and logs, as
crashes depend on these two sections.
<crash id='1'>Configuration of crash 1</crash>
<crash id='2'>Configuration of crash 2</crash>
</crashes>
<infos>
Configuration section of infos.
Note that this section must be configured after triggers and logs, as
infos depend on these two sections.
<info id='1'>Configuration of info 1</info>
<info id='2'>Configuration of info 2</info>
</infos>
</conf>
As for the definition of ``sender``, ``trigger``, ``crash`` and ``info``
please refer to :ref:`acrnprobe_doc`.
Properties of group members
***************************
``acrnprobe`` defined different groups in configuration file, which are
``senders``, ``triggers``, ``crashes`` and ``infos``.
Common properties
=================
- ``id``:
The index, which grows from 1 consecutively, in its group.
- ``enable``:
This group member will be ignored if the value is NOT ``true``.
Other properties
================
- ``inherit``:
Specify a parent for a certain crash.
The child crash will inherit all configurations from the specified (by id)
crash. These inherited configurations could be overwritten by new ones.
Also, this property helps build the crash tree in ``acrnprobe``.
- ``expression``:
See `Crash`_.
Crash tree in acrnprobe
***********************
There could be a parent-child relationship between crashes. Refer to the
diagrams below, crash B and D are the children of crash A, because crash B and
D inherit from crash A, and crash C is the child of crash B.
Build crash tree in configuration
=================================
.. graphviz:: images/crash-config.dot
:name: crash-config
:align: center
:caption: Build crash tree in configuration
Match crash at runtime
======================
In order to find a more specific type, if one crash type matches
successfully ``acrnprobe`` will do a match for each child of it (if it has any)
continually, and return the last successful one.
About how to determine a match is successful, please refer to the ``content`` of
`Crash`_.
Supposing these crash trees are like the diagram above at runtime:
If a crash E is triggered, crash E will be returned immediately.
If a crash A is triggered, then the candidates are crash A, B, C and D.
The following diagram describes what ``acrnprobe`` will do if the matched
result is crash D.
.. graphviz:: images/crash-match.dot
:name: crash-match
:align: center
:caption: Match crash at runtime
Sections
********
Sender
======
Example:
.. code-block:: xml
<sender id="1" enable="true">
<name>crashlog</name>
<outdir>/var/log/crashlog</outdir>
<maxcrashdirs>1000</maxcrashdirs>
<maxlines>5000</maxlines>
<spacequota>90</spacequota>
<uptime>
<name>UPTIME</name>
<frequency>5</frequency>
<eventhours>6</eventhours>
</uptime>
</sender>
* ``name``:
Name of sender. ``acrnprobe`` uses this label to distinguish different
senders.
For more information about sender, please refer to :ref:`acrnprobe_doc`.
* ``outdir``:
Directory to store generated files of sender. ``acrnprobe`` will create
this directory if it doesn't exist.
* ``maxcrashdirs``:
The maximum serial number of generated ``crash directories``,
``stat directories`` and ``vmevent directories``. The serial number will be
reset to 0 if it reaches the specified maximum (``maxcrashdirs``).
Only used by sender crashlog.
* ``maxlines``:
If the number of lines in the ``history_event`` file reaches the specified
``maxlines``, the ``history_event`` file will be renamed to
``history_event.bak`` and logging will continue with a now empty
``history_event`` file.
* ``spacequota``:
``acrnprobe`` will stop collecting logs if
``(used space / total space) * 100 > spacequota``. Only used by sender
crashlog.
* ``uptime``:
Configuration to trigger ``UPTIME`` event.
sub-nodes:
+ ``name``:
The name of event.
+ ``frequency``:
Time interval in seconds to trigger ``uptime`` event.
+ ``eventhours``:
Time interval in hours to generate a record.
Trigger
=======
Example:
.. code-block:: xml
<trigger id="1" enable="true">
<name>t_pstore</name>
<type>node</type>
<path>/sys/fs/pstore/console-ramoops-0</path>
</trigger>
<trigger id="2" enable="true">
<name>t_acrnlog_last</name>
<type>file</type>
<path>/tmp/acrnlog/acrnlog_last.[*]</path>
</trigger>
* ``name``:
The name of trigger. It's used by crash and info configuration module.
* ``type`` and ``path``:
These two labels are used to get the content of trigger files.
``type`` have been implemented:
+ ``node``:
It means that ``path`` is a device node on virtual file system, which cannot
support ``mmap(2)-like`` operations. ``acrnprobe`` can use only ``read(2)``
to get its content.
+ ``file``:
It means that ``path`` is a regular file which supports ``mmap(2)-like``
operations.
+ ``dir``:
It means that ``path`` is a directory.
+ ``rebootreason``:
It means that the trigger's content is the reboot reason of system. The
content of ``rebootreason`` is not obtained in a common way. So, it doesn't
work with ``path``.
+ ``cmd``:
It means that ``path`` is a command which will be launched by ``execvp(3)``.
Some programs often use format ``string%d`` instead of static file name to
generate target file dynamically. So ``path`` supports simple formats for
these cases:
+ /.../dir/string[*] --> all files with prefix "string" under dir.
+ /.../dir/string[0] --> the first file of files, sorted by ``alphasort(3)``,
with prefix "string" under dir.
+ /.../dir/string[-1] --> the last file of files, sorted by ``alphasort(3)``,
with prefix "string" under dir.
Example of formats:
If there are 4 files under ``/tmp``:
``acrnlog_last.1`` ``acrnlog_last.2`` ``acrnlog_last.3`` ``other.txt``
+ ``/tmp/acrnlog_last.[-1]`` indicates ``acrnlog_last.3``.
+ ``/tmp/acrnlog_last.[0]`` indicates ``acrnlog_last.1``.
+ ``/tmp/acrnlog_last.[*]`` indicates the file set including
``acrnlog_last.1``, ``acrnlog_last.2`` and ``acrnlog_last.3``.
Vm
==
Example:
.. code-block:: xml
<vm id="1" enable="true">
<name>VM1</name>
<channel>polling</channel>
<interval>60</interval>
<syncevent id="1">CRASH/TOMBSTONE</syncevent>
<syncevent id="2">CRASH/UIWDT</syncevent>
<syncevent id="3">CRASH/IPANIC</syncevent>
<syncevent id="4">REBOOT</syncevent>
</vm>
* ``name``:
The name of virtual machine.
* ``channel``:
The ``channel`` name to get the virtual machine events.
* ``interval``:
Time interval in seconds of polling vm's image.
* ``syncevent``:
Event type ``acrnprobe`` will synchronize from virtual machine's ``crashlog``.
User could specify different types by id. The event type can also be
indicated by ``type/subtype``.
Log
===
Example:
.. code-block:: xml
<log id="1" enable="true">
<name>pstore</name>
<type>node</type>
<path>/sys/fs/pstore/console-ramoops-0</path>
</log>
* ``name``:
By default, ``acrnprobe`` will take this ``name`` as generated log's name in
``outdir`` of sender crashlog.
If ``path`` is specified by simple formats (includes [*], [0] or [-1]) the
file name of generated logs will be the same as original. More details about
simple formats, see `Trigger`_.
* ``type`` and ``path``:
Same as `Trigger`_.
* ``lines``:
By default, all contents in the original will be copied to generated log.
If this label is configured, only the ``lines`` at the end in the original
will be copied to the generated log. It takes effect only when the ``type`` is
``file``.
Crash
=====
Example:
.. code-block:: xml
<crash id='1' inherit='0' enable='true'>
<name>UNKNOWN</name>
<trigger>t_rebootreason</trigger>
<channel>oneshot</channel>
<content id='1'>WARM</content>
<log id='1'>pstore</log>
<log id='2'>acrnlog_last</log>
</crash>
<crash id='2' inherit='1' enable='true'>
<name>IPANIC</name>
<trigger>t_pstore</trigger>
<content id='1'> </content>
<mightcontent expression='1' id='1'>Kernel panic - not syncing:</mightcontent>
<mightcontent expression='1' id='2'>BUG: unable to handle kernel</mightcontent>
<data id='1'>kernel BUG at</data>
<data id='2'>EIP is at</data>
<data id='3'>Comm:</data>
</crash>
* ``name``:
The type of the ``crash``.
* ``trigger``:
The trigger name of the crash.
* ``channel``:
The name of channel crash use.
* ``content`` and ``mightcontent``:
They're used to match crash type. The match is successful if all the
following conditions are met:
a. All ``contents`` with different ``ids`` are included in trigger's
content.
b. One of ``mightcontents`` with the same ``expression`` is included in
trigger's content at least.
c. If there are ``mightcontents`` with different ``expressions``, each group
with the same ``expression`` should meet condition b.
* ``log``:
The log to be collected. The value is the configured ``name`` in log module.
User could specify different logs by ``id``.
* ``data``:
It is used to generate ``DATA`` fields in ``crashfile``. ``acrnprobe`` will
copy the line which starts with configured ``data`` in trigger's content
to ``DATA`` fields. There are 3 fields in ``crashfile`` and they could be
specified by ``id`` 1, 2, 3.
Info
=====
Example:
.. code-block:: xml
<info id='1' enable='true'>
<name>BOOT_LOGS</name>
<trigger>t_boot</trigger>
<channel>oneshot</channel>
<log id='1'>kmsg</log>
<log id='2'>cmdline</log>
<log id='3'>acrnlog_cur</log>
<log id='4'>acrnlog_last</log>
</info>
* ``name``:
The type of info.
* ``trigger``:
The trigger name of the info.
* ``channel``:
The name of channel info use.
* ``log``:
The log to be collected. The value is the configured name in log module. User
could specify different logs by id.
.. _`XML standard`: http://www.w3.org/TR/REC-xml

View File

@@ -0,0 +1,371 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "load_conf.h"
#include "fsutils.h"
#include "strutils.h"
#include "log_sys.h"
#include "crash_reclassify.h"
/**
* Check if file contains content or not.
* This function couldn't use for binary file.
*
* @param file Starting address of file cache.
* @param content String to be searched.
*
* @return 1 if find the same string, or 0 if not.
*/
static int has_content(const char *file, const char *content)
{
if (content && strstr(file, content))
return 1;
return 0;
}
/**
* Check if file contains all configured contents or not.
* This function couldn't use for binary file.
*
* @param crash Crash need checking.
* @param file Starting address of file cache.
*
* @return 1 if all configured strings were found, or 0 if not.
*/
static int crash_has_all_contents(const struct crash_t *crash,
const char *file)
{
int id;
int ret = 1;
const char *content;
for_each_content_crash(id, content, crash) {
if (!content)
continue;
if (!has_content(file, content)) {
ret = 0;
break;
}
}
return ret;
}
/**
* Might content is a 2-D array, write as mc[exp][cnt]
* This function implements the following algorithm:
*
* r_mc[exp] = has_content(mc[exp][0]) || has_content(mc[exp][1]) || ...
* result = r_mc[0] && r_mc[1] && ...
*
* This function couldn't use for binary file.
*
* @param crash Crash need checking.
* @param file Starting address of file cache.
*
* @return 1 if result is true, or 0 if false.
*/
static int crash_has_mightcontents(const struct crash_t *crash,
const char *file)
{
int ret = 1;
int ret_exp;
int expid, cntid;
const char * const *exp;
const char *content;
for_each_expression_crash(expid, exp, crash) {
if (!exp || !exp_valid(exp))
continue;
ret_exp = 0;
for_each_content_expression(cntid, content, exp) {
if (!content)
continue;
if (has_content(file, content)) {
ret_exp = 1;
break;
}
}
if (ret_exp == 0) {
ret = 0;
break;
}
}
return ret;
}
/**
* Judge the type of crash, according to configured content/mightcontent.
* This function couldn't use for binary file.
*
* @param crash Crash need checking.
* @param file Starting address of file cache.
*
* @return 1 if file matches these strings configured in crash, or 0 if not.
*/
static int crash_match_content(const struct crash_t *crash, const char *file)
{
return crash_has_all_contents(crash, file) &&
crash_has_mightcontents(crash, file);
}
static int _get_data(const char *file, const struct crash_t *crash,
char **data, size_t *dsize, const int index)
{
const char *search_key;
char *value;
char *end;
char *data_new;
ssize_t size;
const size_t max_size = 255;
search_key = crash->data[index];
if (!search_key)
goto empty;
value = strrstr(file, search_key);
if (!value)
goto empty;
end = strchr(value, '\n');
if (!end)
goto empty;
size = MIN(max_size, (size_t)(end - value));
if (!size)
goto empty;
data_new = realloc(*data, *dsize + size + 1);
if (!data_new) {
LOGE("failed to realloc\n");
return -1;
}
strncpy(data_new + *dsize, value, size);
*(data_new + *dsize + size) = 0;
*data = data_new;
*dsize += size;
return 0;
empty:
data_new = realloc(*data, *dsize + 1);
if (!data_new) {
LOGE("failed to realloc\n");
return -1;
}
*(data_new + *dsize) = 0;
*data = data_new;
*dsize += 1;
return 0;
}
/**
* Get segment from file, according to 'data' configuread in crash.
* This function couldn't use for binary file.
*
* @param file Starting address of file cache.
* @param crash Crash need checking.
* @param[out] data Searched result, according to 'data' configuread in crash.
*
* @return 0 if successful, or -1 if not.
*/
static int get_data(const char *file, const struct crash_t *crash,
char **r_data, size_t *r_dsize)
{
char *data = NULL;
size_t dsize = 0;
int i;
/* to find strings which match conf words */
for (i = 0; i < DATA_MAX; i++) {
if (_get_data(file, crash, &data, &dsize, i) == -1)
goto fail;
}
*r_data = data;
*r_dsize = dsize;
return 0;
fail:
if (data)
free(data);
return -1;
}
static int crash_match_file(const struct crash_t *crash, const char *filename)
{
size_t size;
void *cnt;
if (read_file(filename, &size, &cnt) == -1) {
LOGE("read %s failed, error (%s)\n", filename, strerror(errno));
return 0;
}
if (!size)
return 0;
if (crash_match_content(crash, cnt)) {
free(cnt);
return 1;
}
free(cnt);
return 0;
}
int crash_match_filefmt(const struct crash_t *crash, const char *filefmt)
{
int count;
int i;
int ret = 0;
char **files;
count = config_fmt_to_files(filefmt, &files);
if (count <= 0)
return ret;
for (i = 0; i < count; i++) {
if (crash_match_file(crash, files[i])) {
ret = 1;
break;
}
}
for (i = 0; i < count; i++)
free(files[i]);
free(files);
return ret;
}
static struct crash_t *crash_find_matched_child(const struct crash_t *crash,
const char *rtrfmt)
{
struct crash_t *child;
struct crash_t *matched_child = NULL;
const char *trfile_fmt;
if (!crash)
return NULL;
for_crash_children(child, crash) {
if (!child->trigger)
continue;
if (!strcmp(child->trigger->type, "dir"))
trfile_fmt = rtrfmt;
else
trfile_fmt = child->trigger->path;
if (crash_match_filefmt(child, trfile_fmt)) {
matched_child = child;
break;
}
}
/* It returns the first matched crash */
return matched_child;
}
/**
* Judge the crash type. We only got a root crash from channel, sometimes,
* we need to calculate a more specific type.
* This function reclassify the crash type by searching trigger file's content.
* This function couldn't use for binary file.
*
* @param rcrash Root crash obtained from channel.
* @param rtrfile_fmt Path fmt of trigger file of root crash.
* @param[out] data Searched result, according to 'data' configuread in crash.
*
* @return a pointer to the calculated crash structure if successful,
* or NULL if not.
*/
static struct crash_t *crash_reclassify_by_content(const struct crash_t *rcrash,
const char *rtrfile_fmt,
char **data, size_t *dsize)
{
int count;
const struct crash_t *crash;
const struct crash_t *ret_crash = rcrash;
const char *trfile_fmt;
char **trfiles;
void *content;
unsigned long size;
int i;
if (!rcrash || !data || !dsize)
return NULL;
crash = rcrash;
while (1) {
crash = crash_find_matched_child(crash, rtrfile_fmt);
if (!crash)
break;
ret_crash = crash;
}
if (!strcmp(ret_crash->trigger->type, "dir"))
trfile_fmt = rtrfile_fmt;
else
trfile_fmt = ret_crash->trigger->path;
/* trfile may not be specified */
if (!trfile_fmt)
return (struct crash_t *)ret_crash;
count = config_fmt_to_files(trfile_fmt, &trfiles);
if (count <= 0)
return (struct crash_t *)ret_crash;
/* get data from last file */
if (read_file(trfiles[count - 1], &size, &content) == -1) {
LOGE("failed to read %s, error (%s)\n",
trfiles[count - 1], strerror(errno));
goto free_files;
}
if (!size)
goto free_files;
if (get_data(content, ret_crash, data, dsize) == -1)
LOGE("failed to get data\n");
free(content);
free_files:
for (i = 0; i < count; i++)
free(trfiles[i]);
free(trfiles);
return (struct crash_t *)ret_crash;
}
/**
* Initailize crash reclassify, we only got a root crash from channel,
* sometimes, we need to get a more specific type.
*/
void init_crash_reclassify(void)
{
int id;
struct crash_t *crash;
for_each_crash(id, crash, conf) {
if (!crash)
continue;
crash->reclassify = crash_reclassify_by_content;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/time.h>
#include <malloc.h>
#include <stdlib.h>
#include "event_queue.h"
#include "load_conf.h"
#include "channels.h"
#include "fsutils.h"
#include "cmdutils.h"
#include "log_sys.h"
#include "event_handler.h"
#include "startupreason.h"
#include "android_events.h"
/* Watchdog timeout in second*/
#define WDT_TIMEOUT 300
static struct event_t *last_e;
static int event_processing;
/**
* Handle watchdog expire.
*
* @param signal Signal which triggered this function.
*/
static void wdt_timeout(int signal)
{
struct event_t *e;
struct crash_t *crash;
struct info_t *info;
int count;
if (signal == SIGALRM) {
LOGE("haven't received heart beat(%ds) for %ds, killing self\n",
HEART_BEAT, WDT_TIMEOUT);
if (event_processing) {
LOGE("event (%d, %s) processing...\n",
last_e->event_type, last_e->path);
free(last_e);
}
count = events_count();
LOGE("total %d unhandled events :\n", count);
while (count-- && (e = event_dequeue())) {
switch (e->event_type) {
case CRASH:
crash = (struct crash_t *)e->private;
LOGE("CRASH (%s, %s)\n", (char *)crash->name,
e->path);
break;
case INFO:
info = (struct info_t *)e->private;
LOGE("INFO (%s)\n", (char *)info->name);
break;
case UPTIME:
LOGE("UPTIME\n");
break;
case HEART_BEAT:
LOGE("HEART_BEAT\n");
break;
case REBOOT:
LOGE("REBOOT\n");
break;
default:
LOGE("error event type %d\n", e->event_type);
}
free(e);
}
raise(SIGKILL);
}
}
/**
* Fed watchdog.
*
* @param timeout in second When the watchdog expire next time.
*/
static void watchdog_fed(int timeout)
{
struct itimerval new_value;
int ret;
memset(&new_value, 0, sizeof(new_value));
new_value.it_value.tv_sec = timeout;
ret = setitimer(ITIMER_REAL, &new_value, NULL);
if (ret < 0) {
LOGE("setitimer failed, error (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
/**
* Initialize watchdog. This watchdog is used to monitor event handler.
*
* @param timeout in second When the watchdog expire next time.
*/
static void watchdog_init(int timeout)
{
struct itimerval new_value;
int ret;
sighandler_t ohdlr;
ohdlr = signal(SIGALRM, wdt_timeout);
if (ohdlr == SIG_ERR) {
LOGE("signal failed, error (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
memset(&new_value, 0, sizeof(new_value));
new_value.it_value.tv_sec = timeout;
ret = setitimer(ITIMER_REAL, &new_value, NULL);
if (ret < 0) {
LOGE("setitimer failed, error (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
/**
* Process each event in event queue.
* Note that currently event handler is single threaded.
*/
static void *event_handle(void *unused __attribute__((unused)))
{
int id;
struct sender_t *sender;
struct event_t *e;
struct vm_event_t *vme;
while ((e = event_dequeue())) {
/* here we only handle internal event */
if (e->event_type == HEART_BEAT) {
watchdog_fed(WDT_TIMEOUT);
free(e);
continue;
}
/* last_e is allocated for debug purpose, the information
* will be dumped if watchdog expire.
*/
last_e = malloc(sizeof(*e) + e->len);
if (last_e == NULL) {
LOGE("malloc failed, error (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
event_processing = 1;
memcpy(last_e, e, sizeof(*e) + e->len);
for_each_sender(id, sender, conf) {
if (!sender)
continue;
if (sender->send)
sender->send(e);
}
if (e->event_type == REBOOT) {
char reason[REBOOT_REASON_SIZE];
read_startupreason(reason, sizeof(reason));
if (!strcmp(reason, "WARM") ||
!strcmp(reason, "WATCHDOG"))
if (exec_out2file(NULL, "reboot") == -1)
break;
}
if (e->event_type == VM) {
vme = (struct vm_event_t *)e->private;
if (vme && vme->vm_msg)
free(vme->vm_msg);
if (vme)
free(vme);
}
if ((e->dir))
free(e->dir);
free(e);
event_processing = 0;
free(last_e);
}
LOGE("failed to reboot system, %s exit\n", __func__);
return NULL;
}
/**
* Initialize event handler.
*/
int init_event_handler(void)
{
int ret;
pthread_t pid;
watchdog_init(WDT_TIMEOUT);
ret = create_detached_thread(&pid, &event_handle, NULL);
if (ret) {
LOGE("create event handler failed (%s)\n", strerror(errno));
return -1;
}
return 0;
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <sys/queue.h>
#include <pthread.h>
#include "event_queue.h"
#include "log_sys.h"
const char *etype_str[] = {"CRASH", "INFO", "UPTIME", "HEART_BEAT",
"REBOOT", "VM", "UNKNOWN"};
static pthread_mutex_t eq_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;
TAILQ_HEAD(, event_t) event_q;
/**
* Enqueue an event to event_queue.
*
* @param event Event to process.
*/
void event_enqueue(struct event_t *event)
{
pthread_mutex_lock(&eq_mtx);
TAILQ_INSERT_TAIL(&event_q, event, entries);
pthread_cond_signal(&pcond);
LOGD("enqueue %d, (%d)%s\n", event->event_type, event->len,
event->path);
pthread_mutex_unlock(&eq_mtx);
}
/**
* Count the number of events in event_queue.
*
* @return count.
*/
int events_count(void)
{
struct event_t *e;
int count = 0;
pthread_mutex_lock(&eq_mtx);
TAILQ_FOREACH(e, &event_q, entries)
count++;
pthread_mutex_unlock(&eq_mtx);
return count;
}
/**
* Dequeue an event from event_queue.
*
* @return the dequeued event.
*/
struct event_t *event_dequeue(void)
{
struct event_t *e;
pthread_mutex_lock(&eq_mtx);
while (TAILQ_EMPTY(&event_q))
pthread_cond_wait(&pcond, &eq_mtx);
e = TAILQ_FIRST(&event_q);
TAILQ_REMOVE(&event_q, e, entries);
LOGD("dequeue %d, (%d)%s\n", e->event_type, e->len, e->path);
pthread_mutex_unlock(&eq_mtx);
return e;
}
/**
* Initailize event_queue.
*/
void init_event_queue(void)
{
TAILQ_INIT(&event_q);
}

View File

@@ -0,0 +1,450 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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 <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include "fsutils.h"
#include "load_conf.h"
#include "history.h"
#include "log_sys.h"
#include "probeutils.h"
#include "strutils.h"
#define HISTORY_FIRST_LINE_FMT \
"#V1.0 CURRENTUPTIME %-24s\n"
#define HISTORY_BLANK_LINE2 \
"#EVENT ID DATE TYPE\n"
struct history_entry {
const char *event;
const char *type;
const char *log;
const char *lastuptime; /* for uptime */
const char *key;
const char *eventtime;
};
char *history_file;
static int current_lines;
#define EVENT_COUNT_FILE_NAME "all_events"
static char *all_events_cnt;
static size_t all_events_size;
static int event_count_file_path(char *path, size_t size)
{
struct sender_t *crashlog = get_sender_by_name("crashlog");
int res;
if (!crashlog || !path || !size)
return -1;
res = snprintf(path, size, "%s/%s", crashlog->outdir,
EVENT_COUNT_FILE_NAME);
if (s_not_expect(res, size))
return -1;
return 0;
}
static void update_event_count_file(struct history_entry *entry)
{
char path[PATH_MAX];
char line[MAXLINESIZE];
char *update_line;
char *all_events_new;
int len;
if (!entry->event)
return;
if (entry->type)
len = snprintf(line, sizeof(line), "%s-%s: ", entry->event,
entry->type);
else
len = snprintf(line, sizeof(line), "%s: ", entry->event);
if (s_not_expect(len, sizeof(line)))
return;
update_line = strstr(all_events_cnt, line);
if (!update_line) {
*(char *)(mempcpy(line + len, "1\n", 2)) = '\0';
len += 2;
all_events_new = realloc(all_events_cnt, all_events_size +
len + 1);
if (!all_events_new)
return;
*(char *)(mempcpy(all_events_new + all_events_size,
line, len)) = '\0';
all_events_cnt = all_events_new;
all_events_size += len;
} else {
char *s = strstr(update_line, ": ");
char *e = strchr(update_line, '\n');
const char *fmt = "%*[: ]%[[0-9]*]";
char num_str[16];
int num;
char *ne;
char *replace;
if (!s || !e)
return;
if (str_split_ere(s, e - s, fmt, strlen(fmt), num_str,
sizeof(num_str)) != 1)
return;
if (cfg_atoi(num_str, strnlen(num_str, sizeof(num_str)),
&num) == -1)
return;
if (strspn(num_str, "9") == strnlen(num_str, sizeof(num_str))) {
all_events_new = realloc(all_events_cnt,
all_events_size + 1 + 1);
if (!all_events_new)
return;
ne = all_events_new + (e - all_events_cnt);
memmove(ne + 1, ne,
all_events_cnt + all_events_size - e + 1);
replace = all_events_new + (s - all_events_cnt) + 2;
all_events_cnt = all_events_new;
all_events_size++;
} else {
replace = s + 2;
}
len = snprintf(num_str, sizeof(num_str), "%u", num + 1);
if (s_not_expect(len, sizeof(num_str)))
return;
memcpy(replace, num_str, len);
}
if (event_count_file_path(path, sizeof(path)) == -1)
return;
if (overwrite_file(path, all_events_cnt)) {
LOGE("failed to write %s, %s\n", path,
strerror(errno));
return;
}
return;
}
static int init_event_count_file(void)
{
char path[PATH_MAX];
if (event_count_file_path(path, sizeof(path)) == -1)
return -1;
if (!file_exists(path)) {
if (overwrite_file(path, "Total:\n")) {
LOGE("failed to prepare %s, %s\n", path,
strerror(errno));
return -1;
}
}
if (read_file(path, &all_events_size,
(void *)&all_events_cnt) == -1) {
LOGE("failed to read %s, %s\n", path,
strerror(errno));
return -1;
}
return 0;
}
static int entry_to_history_line(struct history_entry *entry,
char *newline, size_t size)
{
const char *general_event_with_msg = "%-8s%-22s%-20s%-16s %s\n";
const char *general_event_without_msg = "%-8s%-22s%-20s%-16s\n";
const char *simple_event = "%-8s%-22s%-20s%s\n";
int len;
if (!entry || !entry->event || !entry->key || !entry->eventtime)
return -1;
if (entry->type) {
const char *fmt;
const char *msg;
if (entry->log || entry->lastuptime) {
fmt = general_event_with_msg;
msg = entry->log ? entry->log : entry->lastuptime;
len = snprintf(newline, size, fmt,
entry->event, entry->key,
entry->eventtime, entry->type, msg);
} else {
fmt = general_event_without_msg;
len = snprintf(newline, size, fmt,
entry->event, entry->key,
entry->eventtime, entry->type);
}
} else if (entry->lastuptime) {
len = snprintf(newline, size, simple_event,
entry->event, entry->key,
entry->eventtime, entry->lastuptime);
} else
return -1;
if (s_not_expect(len, size))
return -1;
return 0;
}
static void backup_history(void)
{
int ret;
char *des;
ret = asprintf(&des, "%s.bak", history_file);
if (ret < 0) {
LOGE("compute string failed, out of memory\n");
return;
}
ret = do_mv(history_file, des);
if (ret < 0) {
LOGE("backup %s failed, error (%s)\n", history_file,
strerror(errno));
goto free;
}
ret = prepare_history();
if (ret < 0) {
LOGE("Prepare new history_file failed, exit\n");
exit(EXIT_FAILURE);
}
free:
free(des);
}
void hist_raise_event(const char *event, const char *type, const char *log,
const char *lastuptime, const char *key)
{
char line[MAXLINESIZE];
char eventtime[LONG_TIME_SIZE];
struct sender_t *crashlog;
int maxlines;
struct history_entry entry = {
.event = event,
.type = type,
.log = log,
.lastuptime = lastuptime,
.key = key
};
/* here means user have configured the crashlog sender */
crashlog = get_sender_by_name("crashlog");
if (!crashlog)
return;
update_event_count_file(&entry);
if (cfg_atoi(crashlog->maxlines, crashlog->maxlines_len,
&maxlines) == -1)
return;
if (++current_lines >= maxlines) {
LOGW("lines of (%s) meet quota %d, backup... Pls clean!\n",
history_file, maxlines);
backup_history();
}
if (get_current_time_long(eventtime) <= 0)
return;
entry.eventtime = eventtime;
if (entry_to_history_line(&entry, line, sizeof(line)) == -1) {
LOGE("failed to generate new line\n");
return;
}
if (append_file(history_file, line, strnlen(line, MAXLINESIZE)) <= 0) {
LOGE("failed to append (%s) to (%s)\n", line, history_file);
return;
}
}
void hist_raise_uptime(char *lastuptime)
{
char boot_time[UPTIME_SIZE];
char firstline[MAXLINESIZE];
int hours;
int ret;
char *key;
static int loop_uptime_event = 1;
struct sender_t *crashlog;
struct uptime_t *uptime;
static int uptime_hours;
/* user have configured the crashlog sender */
crashlog = get_sender_by_name("crashlog");
if (!crashlog)
return;
uptime = crashlog->uptime;
if (cfg_atoi(uptime->eventhours, uptime->eventhours_len,
&uptime_hours) == -1)
return;
if (lastuptime)
hist_raise_event(uptime->name, NULL, NULL, lastuptime,
"00000000000000000000");
else {
ret = get_uptime_string(boot_time, &hours);
if (ret < 0) {
LOGE("cannot get uptime - %s\n", strerror(-ret));
return;
}
ret = snprintf(firstline, sizeof(firstline),
HISTORY_FIRST_LINE_FMT, boot_time);
if (s_not_expect(ret, sizeof(firstline))) {
LOGE("failed to construct the firstline\n");
return;
}
replace_file_head(history_file, firstline);
if (hours / uptime_hours >= loop_uptime_event) {
loop_uptime_event = (hours / uptime_hours) + 1;
key = generate_event_id((const char *)uptime->name,
uptime->name_len,
NULL, 0, KEY_SHORT);
if (key == NULL) {
LOGE("generate event id failed, error (%s)\n",
strerror(errno));
return;
}
hist_raise_event(uptime->name, NULL, NULL,
boot_time, key);
free(key);
}
}
}
void hist_raise_infoerror(const char *type, size_t tlen)
{
char *key;
key = generate_event_id("ERROR", 5, type, tlen, KEY_SHORT);
if (key == NULL) {
LOGE("generate event id failed, error (%s)\n",
strerror(errno));
return;
}
hist_raise_event("ERROR", type, NULL, NULL, key);
free(key);
}
static int get_time_from_firstline(char *buffer, size_t size)
{
char lasttime[MAXLINESIZE];
const char *prefix = "#V1.0 CURRENTUPTIME ";
int len;
len = file_read_key_value(lasttime, MAXLINESIZE, history_file, prefix,
strlen(prefix));
if (len <= 0) {
LOGW("failed to read value from %s, error %s\n",
history_file, strerror(-len));
return -1;
}
if ((size_t)len >= size)
return -1;
*(char *)mempcpy(buffer, lasttime, len) = '\0';
return 0;
}
int prepare_history(void)
{
int ret;
int llen;
struct sender_t *crashlog;
char linebuf[MAXLINESIZE];
crashlog = get_sender_by_name("crashlog");
if (!crashlog)
return 0;
if (init_event_count_file() == -1)
return -1;
if (!history_file) {
ret = asprintf(&history_file, "%s/%s", crashlog->outdir,
HISTORY_NAME);
if (ret < 0) {
LOGE("compute string failed, out of memory\n");
return -ENOMEM;
}
}
ret = get_time_from_firstline(linebuf, MAXLINESIZE);
if (ret == 0) {
current_lines = count_lines_in_file(history_file);
hist_raise_uptime(linebuf);
} else {
/* new history */
LOGW("new history\n");
llen = snprintf(linebuf, sizeof(linebuf),
HISTORY_FIRST_LINE_FMT, "0000:00:00");
if (s_not_expect(llen, sizeof(linebuf))) {
LOGE("failed to construct the fristline\n");
return -EINVAL;
}
ret = overwrite_file(history_file, linebuf);
if (ret < 0) {
LOGE("Write (%s, %s) failed, error (%s)\n",
history_file, linebuf,
strerror(errno));
return ret;
}
ret = append_file(history_file, HISTORY_BLANK_LINE2,
sizeof(HISTORY_BLANK_LINE2) - 1);
if (ret < 0) {
LOGE("Write (%s, %s) failed, error (%s)\n",
history_file, HISTORY_BLANK_LINE2,
strerror(-ret));
return ret;
}
current_lines = count_lines_in_file(history_file);
}
return 0;
}

View File

@@ -0,0 +1,21 @@
digraph {
{
node [shape=plaintext];
"level 1" -> "level 2" -> "level 3";
}
node [shape=box;style="rounded,filled";color=AntiqueWhite;];
c1 [ label="crash A\nid 1\ncrash root" ];
c2 [ label="crash B\nid 2" ];
c3 [ label="crash C\nid 3\ncrash leaf" ];
c4 [ label="crash D\nid 4\ncrash leaf" ];
c5 [ label="crash E\nid 5\ncrash root\ncrash leaf" ];
{ rank = same; "level 1"; c1; c5;}
{ rank = same; "level 2"; c2; c4;}
{ rank = same; "level 3"; c3;}
node [shape=box;color="transparent";];
"None" -> {c1 c5} [ label="inherit 0" ];
c1 -> {c2 c4} [ label="inherit 1" ];
c2 -> c3 [ label="inherit 2" ];
}

View File

@@ -0,0 +1,26 @@
digraph {
{
node [shape=plaintext];
"level 1" -> "level 2" -> "level 3";
}
node [shape=box;style="rounded,filled";color=AntiqueWhite;];
c1 [ label="crash A\nid 1\ncrash root" ];
c2 [ label="crash B\nid 2" ];
c3 [ label="crash C\nid 3\ncrash leaf" ];
c4 [ label="crash D\nid 4\ncrash leaf" ];
{ rank = same; "level 1"; c1;}
{ rank = same; "level 2"; c2; c4;}
{ rank = same; "level 3"; c3;}
node [shape=box;style="rounded,dashed";];
exp1 [ label="crash B matches fail\nmatch for the next child\nof crash A"];
exp2 [ label="crash D matches successfully\nreturn crash D"];
node [shape=box;style="invis";];
"channel" -> c1 [ label="trigger" ]
c1 -> {exp1 exp2}
exp1 -> c2 -> c3 [ style=dashed dir=none]
exp2 -> c4
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __ANDROID_EVENTS_H__
#define __ANDROID_EVENTS_H__
#include "load_conf.h"
extern char *loop_dev;
#define VMEVT_HANDLED 0
#define VMEVT_DEFER -1
struct vm_event_t {
char *vm_msg;
size_t vm_msg_len;
const struct vm_t *vm;
};
#define ANDROID_LOGS_DIR "/logs/"
#define IGN_SPACES "%*[[[:space:]]*]"
#define IGN_RESTS "%*[[.]*]"
#define IGN_ONEWORD "%*[[^[:space:]]*]" IGN_SPACES
#define VM_NAME_FMT "%[[A-Z0-9]{3}]" IGN_SPACES
/* These below macros were defined to obtain strings from
* andorid history_event
*/
#define ANDROID_WORD_LEN 32
/* Strings are constructed by A-Z, len < 8, e.g., CRASH REBOOT */
#define ANDROID_ENEVT_FMT "%[[A-Z]{1,7}]" IGN_SPACES
/* Hashkeys are constructed by 0-9&a-z, len = 20, e.g., 0b34ae1afba54aee5cd0. */
#define ANDROID_KEY_FMT "%[[0-9a-z]{20}]" IGN_SPACES
/* Strings, e.g., 2017-11-11/03:12:59 */
#define ANDROID_LONGTIME_FMT "%[[0-9:/-]{15,20}]" IGN_SPACES
/* It's a time or a subtype of event, e.g., JAVACRASH POWER-ON 424874:19:56 */
#define ANDROID_TYPE_FMT "%[[A-Z0-9_:-]{3,16}]" IGN_SPACES
#define ANDROID_LINE_REST_FMT "%[[^\n]*]" IGN_RESTS
void refresh_vm_history(struct sender_t *sender,
int (*fn)(const char*, size_t, const struct vm_t *));
int android_event_analyze(const char *msg, size_t len, char **result,
size_t *rsize);
#endif

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#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

View File

@@ -0,0 +1,8 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
extern int crash_match_filefmt(const struct crash_t *crash,
const char *filefmt);
extern void init_crash_reclassify(void);

View File

@@ -0,0 +1,6 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
extern int init_event_handler(void);

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __EVENT_QUEUE_H__
#define __EVENT_QUEUE_H__
#include <sys/queue.h>
enum event_type_t {
CRASH,
INFO,
UPTIME,
HEART_BEAT,
REBOOT,
VM,
UNKNOWN
};
extern const char *etype_str[];
__extension__
struct event_t {
int watchfd;
enum event_type_t event_type;
const char *channel;
void *private;
TAILQ_ENTRY(event_t) entries;
/* dir to storage logs */
char *dir;
size_t dlen;
int len;
char path[0]; /* keep this at tail*/
};
void event_enqueue(struct event_t *event);
int events_count(void);
struct event_t *event_dequeue(void);
void init_event_queue(void);
#endif

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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.
*/
#ifndef __HISTORY_H__
#define __HISTORY_H__
#define HISTORY_NAME "history_event"
extern char *history_file;
int prepare_history(void);
void hist_raise_infoerror(const char *type, size_t tlen);
void hist_raise_uptime(char *lastuptime);
void hist_raise_event(const char *event, const char *type, const char *log,
const char *lastuptime, const char *key);
#endif

View File

@@ -0,0 +1,257 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __LOAD_CONF_H__
#define __LOAD_CONF_H__
#include <stdio.h>
#include <sys/queue.h>
#include <ext2fs/ext2fs.h>
#include "event_queue.h"
#include "probeutils.h"
#include "vmrecord.h"
#define CONTENT_MAX 10
#define EXPRESSION_MAX 5
#define LOG_MAX 20
#define TRIGGER_MAX 20
#define SENDER_MAX 3
#define DATA_MAX 3
#define CRASH_MAX 20
#define INFO_MAX 20
#define VM_MAX 4
#define VM_EVENT_TYPE_MAX 20
struct trigger_t {
int id;
const char *name;
size_t name_len;
const char *type;
size_t type_len;
const char *path;
size_t path_len;
};
struct vm_t {
int id;
const char *name;
size_t name_len;
const char *channel;
size_t channel_len;
const char *interval;
size_t interval_len;
const char *syncevent[VM_EVENT_TYPE_MAX];
size_t syncevent_len[VM_EVENT_TYPE_MAX];
ext2_filsys datafs;
unsigned long history_size[SENDER_MAX];
char *history_data;
char last_evt_detected[SENDER_MAX][SHORT_KEY_LENGTH + 1];
};
struct log_t {
int id;
const char *name;
size_t name_len;
const char *type;
size_t type_len;
const char *path;
size_t path_len;
const char *lines;
size_t lines_len;
const char *deletesource;
size_t deletesource_len;
const char *sizelimit;
size_t sizelimit_len;
void (*get)(struct log_t *, void *);
};
struct crash_t {
int id;
const char *name;
size_t name_len;
const char *channel;
size_t channel_len;
const char *interval;
size_t interval_len;
struct trigger_t *trigger;
const char *content[CONTENT_MAX];
size_t content_len[CONTENT_MAX];
const char *mightcontent[EXPRESSION_MAX][CONTENT_MAX];
size_t mightcontent_len[EXPRESSION_MAX][CONTENT_MAX];
struct log_t *log[LOG_MAX];
const char *data[DATA_MAX];
size_t data_len[DATA_MAX];
struct crash_t *parents;
TAILQ_ENTRY(crash_t) entries;
TAILQ_HEAD(, crash_t) children;
int wd;
int level;
struct crash_t *(*reclassify)(const struct crash_t *, const char*,
char**, size_t *);
};
struct info_t {
int id;
const char *name;
size_t name_len;
const char *channel;
size_t channel_len;
const char *interval;
size_t interval_len;
struct trigger_t *trigger;
struct log_t *log[LOG_MAX];
};
struct uptime_t {
const char *name;
size_t name_len;
const char *frequency;
size_t frequency_len;
const char *eventhours;
size_t eventhours_len;
int wd;
char *path;
};
struct sender_t {
int id;
const char *name;
size_t name_len;
const char *outdir;
size_t outdir_len;
const char *maxcrashdirs;
size_t maxcrashdirs_len;
const char *maxlines;
size_t maxlines_len;
const char *spacequota;
size_t spacequota_len;
const char *foldersize;
size_t foldersize_len;
struct uptime_t *uptime;
void (*send)(struct event_t *);
struct vmrecord_t vmrecord;
size_t outdir_blocks_size;
int sw_updated; /* each sender has their own record */
};
struct conf_t {
struct sender_t *sender[SENDER_MAX];
struct vm_t *vm[VM_MAX];
struct trigger_t *trigger[TRIGGER_MAX];
struct log_t *log[LOG_MAX];
struct crash_t *crash[CRASH_MAX];
struct info_t *info[INFO_MAX];
};
struct conf_t conf;
#define for_each_sender(id, sender, conf) \
for (id = 0; \
id < SENDER_MAX && (sender = conf.sender[id]); \
id++)
#define for_each_trigger(id, trigger, conf) \
for (id = 0; \
id < TRIGGER_MAX && (trigger = conf.trigger[id]); \
id++)
#define for_each_vm(id, vm, conf) \
for (id = 0; \
id < VM_MAX && (vm = conf.vm[id]); \
id++)
#define for_each_syncevent_vm(id, event, vm) \
for (id = 0; \
id < VM_EVENT_TYPE_MAX && (event = vm->syncevent[id]); \
id++)
#define for_each_info(id, info, conf) \
for (id = 0; \
id < INFO_MAX && (info = conf.info[id]); \
id++)
#define for_each_log(id, log, conf) \
for (id = 0; \
id < LOG_MAX && (log = conf.log[id]); \
id++)
#define for_each_crash(id, crash, conf) \
for (id = 0; \
id < CRASH_MAX && (crash = conf.crash[id]); \
id++)
#define for_each_log_collect(id, log, type) \
for (id = 0; \
id < LOG_MAX && (log = type->log[id]); \
id++)
#define for_each_content_crash(id, content, crash) \
for (id = 0; \
id < CONTENT_MAX && (content = crash->content[id]); \
id++)
#define for_each_content_expression(id, content, exp) \
for (id = 0; \
id < CONTENT_MAX && (content = exp[id]); \
id++)
#define exp_valid(exp) \
(__extension__ \
({ \
int _ret = 0; \
int _id; \
const char *content; \
for_each_content_expression(_id, content, exp) { \
if (content) \
_ret = 1; \
} \
_ret; \
}) \
)
#define for_each_expression_crash(id, exp, crash) \
for (id = 0; \
id < EXPRESSION_MAX && (exp = crash->mightcontent[id]); \
id++)
#define for_crash_children(crash, tcrash) \
TAILQ_FOREACH(crash, &tcrash->children, entries)
#define is_leaf_crash(crash) \
(crash && TAILQ_EMPTY(&crash->children))
#define is_root_crash(crash) \
(crash && crash->parents == NULL)
#define to_collect_logs(type) \
(__extension__ \
({ \
int _id; \
int _ret = 0; \
for (_id = 0; _id < LOG_MAX; _id++) \
if (type->log[_id]) \
_ret = 1; \
_ret; \
}) \
)
int load_conf(const char *path);
struct trigger_t *get_trigger_by_name(const char *name);
struct log_t *get_log_by_name(const char *name);
struct vm_t *get_vm_by_name(const char *name);
struct sender_t *get_sender_by_name(const char *name);
enum event_type_t get_conf_by_wd(int wd, void **private);
struct crash_t *get_crash_by_wd(int wd);
int crash_depth(struct crash_t *tcrash);
int cfg_atoi(const char *a, size_t alen, int *i);
#endif

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <ext2fs/ext2fs.h>
int loopdev_num_get_free(void);
int loopdev_set_img_par(const char *loopdev, const char *img_path,
const char *parname);
int loopdev_check_parname(const char *loopdev, const char *parname);
int e2fs_dump_file_by_fpath(ext2_filsys fs, const char *in_fp,
const char *out_fp);
int e2fs_read_file_by_fpath(ext2_filsys fs, const char *in_fp,
void **out_data, unsigned long *size);
int e2fs_dump_dir_by_dpath(ext2_filsys fs, const char *in_dp,
const char *out_dp, int *count);
int e2fs_open(const char *dev, ext2_filsys *outfs);
void e2fs_close(ext2_filsys fs);

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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.
*/
#ifndef __PROBEUTILS_H__
#define __PROBEUTILS_H__
#define UPTIME_SIZE 24
#define LONG_TIME_SIZE 32
#define SHORT_KEY_LENGTH 20
#define LONG_KEY_LENGTH 32
enum e_dir_mode {
MODE_CRASH = 0,
MODE_STATS,
MODE_VMEVENT,
};
enum key_type {
KEY_SHORT = 0,
KEY_LONG,
};
int get_uptime_string(char newuptime[24], int *hours);
int get_current_time_long(char buf[32]);
unsigned long long get_uptime(void);
char *generate_event_id(const char *seed1, size_t slen1, const char *seed2,
size_t slen2, enum key_type type);
void generate_crashfile(const char *dir, const char *event, size_t elen,
const char *hashkey, size_t hlen,
const char *type, size_t tlen, const char *data0,
size_t d0len, const char *data1, size_t d1len,
const char *data2, size_t d2len);
char *generate_log_dir(enum e_dir_mode mode, char *hashkey, size_t *dlen);
int is_boot_id_changed(void);
#endif

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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.
*/
#ifndef __PROPERTY_H__
#define __PROPERTY_H__
#include "load_conf.h"
#define VERSION_SIZE 256
/* UUID_SIZE contains the UUID number, dashes and some buffer*/
#define UUID_SIZE 48
/* General BUILD_VERSION like 23690 */
#define BUILD_VERSION_SIZE 16
char guuid[UUID_SIZE];
char gbuildversion[BUILD_VERSION_SIZE];
int init_properties(struct sender_t *sender);
int swupdated(struct sender_t *sender);
#endif

View File

@@ -0,0 +1,6 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
extern int init_sender(void);

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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.
*/
#define REBOOT_REASON_SIZE 32
extern void read_startupreason(char *startupreason, const size_t limit);

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __VMRECORD_H__
#define __VMRECORD_H__
#include "pthread.h"
#define VMRECORD_HEAD_LINES 10
#define VMRECORD_TAG_LEN 9
#define VMRECORD_TAG_WAITING_SYNC " <=="
#define VMRECORD_TAG_NOT_FOUND "NOT_FOUND"
#define VMRECORD_TAG_MISS_LOG "MISS_LOGS"
#define VMRECORD_TAG_ON_GOING " ON_GOING"
#define VMRECORD_TAG_NO_RESOURCE "NO_RESORC"
#define VMRECORD_TAG_SUCCESS " "
enum vmrecord_mark_t {
SUCCESS,
NOT_FOUND,
WAITING_SYNC,
ON_GOING,
NO_RESRC,
MISS_LOG
};
struct vmrecord_t {
char *path;
pthread_mutex_t mtx;
struct mm_file_t *recos;
};
int vmrecord_last(struct vmrecord_t *vmrecord, const char *vm_name,
size_t nlen, char *vmkey, size_t ksize);
int vmrecord_mark(struct vmrecord_t *vmrecord, const char *vmkey,
size_t klen, enum vmrecord_mark_t type);
int vmrecord_open_mark(struct vmrecord_t *vmrecord, const char *vmkey,
size_t klen, enum vmrecord_mark_t type);
int vmrecord_gen_ifnot_exists(struct vmrecord_t *vmrecord);
int vmrecord_new(struct vmrecord_t *vmrecord, const char *vm_name,
const char *key);
#endif

View File

@@ -0,0 +1,773 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <string.h>
#include "load_conf.h"
#include "event_queue.h"
#include "log_sys.h"
#include "strutils.h"
static void print(void)
{
int id, id2;
int i, j;
struct sender_t *sender;
struct trigger_t *trigger;
struct vm_t *vm;
struct log_t *log;
struct info_t *info;
struct crash_t *crash;
struct crash_t *crash_tmp;
#define print_id_item(item, root, id) \
LOGD("%-8s(%d): %-15s:(%s)\n", #root, id, #item, root->item)
#define print_2id_item(item, root, id, tid) \
LOGD("%-8s(%d): %-15s(%d):(%s)\n", \
#root, id, #item, tid, root->item[tid])
for_each_sender(id, sender, conf) {
if (!sender)
continue;
print_id_item(name, sender, id);
print_id_item(outdir, sender, id);
print_id_item(maxcrashdirs, sender, id);
print_id_item(maxlines, sender, id);
print_id_item(spacequota, sender, id);
print_id_item(foldersize, sender, id);
if (sender->uptime) {
print_id_item(uptime->name, sender, id);
print_id_item(uptime->path, sender, id);
print_id_item(uptime->frequency, sender, id);
print_id_item(uptime->eventhours, sender, id);
}
}
for_each_trigger(id, trigger, conf) {
if (!trigger)
continue;
print_id_item(name, trigger, id);
print_id_item(type, trigger, id);
print_id_item(path, trigger, id);
}
for_each_vm(id, vm, conf) {
if (!vm)
continue;
print_id_item(name, vm, id);
print_id_item(channel, vm, id);
print_id_item(interval, vm, id);
for (i = 0; i < VM_EVENT_TYPE_MAX; i++) {
if (vm->syncevent[i])
print_2id_item(syncevent, vm, id, i);
}
}
for_each_log(id, log, conf) {
if (!log)
continue;
print_id_item(name, log, id);
print_id_item(type, log, id);
print_id_item(deletesource, log, id);
print_id_item(lines, log, id);
print_id_item(path, log, id);
print_id_item(sizelimit, log, id);
}
for_each_info(id, info, conf) {
if (!info)
continue;
print_id_item(name, info, id);
print_id_item(channel, info, id);
print_id_item(interval, info, id);
print_id_item(trigger->name, info, id);
for_each_log_collect(id2, log, info) {
if (!log)
continue;
LOGD("%-8s(%d): %-15s(%c):(%s)\n",
"info", id, "log", 'x', log->name);
}
}
for_each_crash(id, crash, conf) {
char buf[512];
char *tail;
int len;
if (!crash)
continue;
print_id_item(name, crash, id);
memset(buf, 0, sizeof(buf));
LOGD("%-8s(%d): properties: %s, %s\n", "crash", id,
is_root_crash(crash) ? "root" : "non-root",
is_leaf_crash(crash) ? "leaf" : "non-leaf");
len = snprintf(buf, sizeof(buf), "%-8s(%d): children: ",
"crash", id);
if (s_not_expect(len, sizeof(buf))) {
LOGE("failed to construct the children of crash\n");
continue;
}
tail = buf + len;
for_crash_children(crash_tmp, crash) {
if (len + crash_tmp->name_len + 2 >= sizeof(buf)) {
LOGE("names of children too long - truncate\n");
break;;
}
tail = mempcpy(tail, crash_tmp->name,
crash_tmp->name_len);
*tail++ = ' ';
len += crash_tmp->name_len + 1;
}
*tail = '\0';
LOGD("%s\n", buf);
print_id_item(trigger->name, crash, id);
print_id_item(channel, crash, id);
print_id_item(interval, crash, id);
for (i = 0; i < CONTENT_MAX; i++)
if (crash->content[i])
print_2id_item(content, crash, id, i);
for (i = 0; i < EXPRESSION_MAX; i++)
for (j = 0; j < CONTENT_MAX; j++)
if (crash->mightcontent[i][j])
LOGD("%-8s(%d): %-15s(%d,%d):(%s)\n",
"crash", id, "mightcontent", i, j,
crash->mightcontent[i][j]);
for (i = 0; i < DATA_MAX; i++)
if (crash->data[i])
print_2id_item(data, crash, id, i);
}
}
static int get_prop_int(xmlNodePtr cur, const char *key, const int max)
{
xmlChar *prop;
int value;
prop = xmlGetProp(cur, (const xmlChar *)key);
if (!prop) {
LOGE("get prop (%s) failed\n", key);
return -1;
}
if (cfg_atoi((const char *)prop, xmlStrlen(prop), &value) == -1)
return -1;
xmlFree(prop);
if (value > max) {
LOGE("prop (%s) exceeds MAX (%d)\n", prop, max);
return -1;
}
return value;
}
static int get_id_index(xmlNodePtr cur, const int max)
{
int value = get_prop_int(cur, "id", max);
if (value <= 0)
return -1;
/* array index = value - 1 */
return value - 1;
}
static int get_expid_index(xmlNodePtr cur, const int max)
{
int value = get_prop_int(cur, "expression", max);
if (value <= 0)
return -1;
/* array index = value - 1 */
return value - 1;
}
#define load_cur_content(cur, type, mem) \
(__extension__ \
({ \
xmlChar *load##mem; \
int _ret = -1; \
load##mem = xmlNodeGetContent(cur); \
if (load##mem) { \
type->mem = (const char *)load##mem; \
type->mem##_len = xmlStrlen(load##mem); \
_ret = 0; \
} \
_ret; \
}) \
)
#define load_cur_content_with_id(cur, type, mem, max) \
(__extension__ \
({ \
xmlChar *load##mem; \
int index; \
int _ret = -1; \
load##mem = xmlNodeGetContent(cur); \
if (load##mem) { \
index = get_id_index(cur, max); \
if (index != -1) { \
type->mem[index] = (const char *)load##mem; \
type->mem##_len[index] = xmlStrlen(load##mem); \
_ret = 0; \
} \
} \
_ret; \
}) \
)
#define load_trigger(cur, event) \
(__extension__ \
({ \
int _ret = -1; \
xmlChar *content = xmlNodeGetContent(cur); \
if (content) { \
event->trigger = \
get_trigger_by_name((const char *)content); \
xmlFree(content); \
_ret = 0; \
} \
_ret; \
}) \
)
struct crash_t *get_crash_by_wd(int wd)
{
int id;
struct crash_t *crash;
for_each_crash(id, crash, conf) {
if (!crash)
continue;
if (crash->wd == wd)
return crash;
}
return NULL;
}
struct uptime_t *get_uptime_by_wd(int wd)
{
int id;
struct uptime_t *ut;
struct sender_t *sender;
for_each_sender(id, sender, conf) {
if (!sender)
continue;
ut = sender->uptime;
if (ut->wd == wd)
return ut;
}
return NULL;
}
enum event_type_t get_conf_by_wd(int wd, void **private)
{
void *conf;
conf = (void *)get_uptime_by_wd(wd);
if (conf) {
*private = conf;
return UPTIME;
}
conf = (void *)get_crash_by_wd(wd);
if (conf) {
*private = conf;
return CRASH;
}
return UNKNOWN;
}
struct sender_t *get_sender_by_name(const char *name)
{
int id;
struct sender_t *sender;
for_each_sender(id, sender, conf) {
if (!sender)
continue;
if (strcmp(name, sender->name) == 0)
return sender;
}
return NULL;
}
struct trigger_t *get_trigger_by_name(const char *name)
{
int id;
struct trigger_t *trigger;
for_each_trigger(id, trigger, conf) {
if (!trigger)
continue;
if (strcmp(name, trigger->name) == 0)
return trigger;
}
return NULL;
}
struct log_t *get_log_by_name(const char *name)
{
int id;
struct log_t *log;
for_each_log(id, log, conf) {
if (!log)
continue;
if (strcmp(name, log->name) == 0)
return log;
}
return NULL;
}
struct vm_t *get_vm_by_name(const char *name)
{
int id;
struct vm_t *vm;
for_each_vm(id, vm, conf) {
if (!vm)
continue;
if (strcmp(name, vm->name) == 0)
return vm;
}
return NULL;
}
int crash_depth(struct crash_t *tcrash)
{
int id;
int level = 0;
struct crash_t *crash;
for_each_crash(id, crash, conf) {
if (!crash)
continue;
if (crash->channel == tcrash->channel &&
crash->trigger == tcrash->trigger &&
crash->level > level)
level = crash->level;
}
level = level - tcrash->level;
return level;
}
int cfg_atoi(const char *a, size_t alen, int *i)
{
char *eptr;
int res;
if (!a || !alen || !i)
return -1;
res = (int)strtol(a, &eptr, 0);
if (a + alen != eptr) {
LOGE("Failed to convert (%s) to type int, check config file\n",
a);
return -1;
}
*i = res;
return 0;
}
static int is_enable(xmlNodePtr cur)
{
xmlChar *prop;
int ret = 0;
prop = xmlGetProp(cur, (const xmlChar *)"enable");
if (prop) {
ret = !xmlStrcmp((const xmlChar *)"true", prop);
xmlFree(prop);
}
return ret;
}
#define name_is(cur, key) \
(xmlStrcmp(cur->name, (const xmlChar *)key) == 0)
static int parse_info(xmlNodePtr cur, struct info_t *info)
{
int id;
int res = 0;
xmlChar *content;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, info, name);
else if (name_is(cur, "trigger"))
load_trigger(cur, info);
else if (name_is(cur, "channel"))
res = load_cur_content(cur, info, channel);
else if (name_is(cur, "log")) {
id = get_id_index(cur, LOG_MAX);
if (id == -1)
return -1;
content = xmlNodeGetContent(cur);
if (!content)
return -1;
info->log[id] = get_log_by_name((const char *)content);
xmlFree(content);
}
if (res)
return -1;
cur = cur->next;
}
return 0;
}
static int parse_log(xmlNodePtr cur, struct log_t *log)
{
int res = 0;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, log, name);
else if (name_is(cur, "type"))
res = load_cur_content(cur, log, type);
else if (name_is(cur, "deletesource"))
res = load_cur_content(cur, log, deletesource);
else if (name_is(cur, "path"))
res = load_cur_content(cur, log, path);
else if (name_is(cur, "lines"))
res = load_cur_content(cur, log, lines);
else if (name_is(cur, "sizelimit"))
res = load_cur_content(cur, log, sizelimit);
if (res)
return -1;
cur = cur->next;
}
return 0;
}
static int parse_crash(xmlNodePtr cur, struct crash_t *crash)
{
int id;
int expid;
int res = 0;
xmlChar *content;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, crash, name);
else if (name_is(cur, "trigger"))
load_trigger(cur, crash);
else if (name_is(cur, "channel"))
res = load_cur_content(cur, crash, channel);
else if (name_is(cur, "content"))
res = load_cur_content_with_id(cur, crash,
content, CONTENT_MAX);
else if (name_is(cur, "log")) {
id = get_id_index(cur, LOG_MAX);
if (id == -1)
return -1;
content = xmlNodeGetContent(cur);
if (!content)
return -1;
crash->log[id] = get_log_by_name((const char *)content);
xmlFree(content);
} else if (name_is(cur, "data"))
res = load_cur_content_with_id(cur, crash,
data, DATA_MAX);
else if (name_is(cur, "mightcontent")) {
id = get_id_index(cur, CONTENT_MAX);
expid = get_expid_index(cur, EXPRESSION_MAX);
if (id == -1 || expid == -1)
return -1;
content = xmlNodeGetContent(cur);
if (!content)
return -1;
crash->mightcontent[expid][id] = (const char *)content;
crash->mightcontent_len[expid][id] = xmlStrlen(content);
}
if (res)
return -1;
cur = cur->next;
}
return 0;
}
static int parse_crashes(xmlNodePtr crashes)
{
int id;
int res;
xmlNodePtr cur;
struct crash_t *crash;
cur = crashes->xmlChildrenNode;
while (cur) {
if (is_enable(cur)) {
crash = malloc(sizeof(*crash));
if (!crash)
return -1;
res = get_prop_int(cur, "inherit", CRASH_MAX);
if (res < 0) {
free(crash);
return -1;
}
id = res - 1;
if (id >= 0) {
memcpy(crash, conf.crash[id], sizeof(*crash));
crash->parents = conf.crash[id];
crash->level++;
TAILQ_INSERT_TAIL(&crash->parents->children,
crash, entries);
} else {
memset(crash, 0, sizeof(*crash));
}
id = get_id_index(cur, CRASH_MAX);
if (id == -1) {
free(crash);
return -1;
}
res = parse_crash(cur, crash);
if (res) {
free(crash);
return -1;
}
TAILQ_INIT(&crash->children);
conf.crash[id] = crash;
}
cur = cur->next;
}
return 0;
}
static int parse_vm(xmlNodePtr cur, struct vm_t *vm)
{
int res = 0;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, vm, name);
else if (name_is(cur, "channel"))
res = load_cur_content(cur, vm, channel);
else if (name_is(cur, "interval"))
res = load_cur_content(cur, vm, interval);
else if (name_is(cur, "syncevent"))
res = load_cur_content_with_id(cur, vm,
syncevent,
VM_EVENT_TYPE_MAX);
if (res)
return -1;
cur = cur->next;
}
return 0;
}
static int parse_uptime(xmlNodePtr cur, struct sender_t *sender)
{
struct uptime_t *uptime;
int res = 0;
uptime = malloc(sizeof(*uptime));
if (!uptime)
return -1;
memset(uptime, 0, sizeof(*uptime));
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, uptime, name);
else if (name_is(cur, "frequency"))
res = load_cur_content(cur, uptime, frequency);
else if (name_is(cur, "eventhours"))
res = load_cur_content(cur, uptime, eventhours);
if (res) {
free(uptime);
return -1;
}
cur = cur->next;
}
res = asprintf(&uptime->path, "%s/uptime", sender->outdir);
if (res < 0) {
LOGE("build string failed\n");
free(uptime);
return -1;
}
sender->uptime = uptime;
return 0;
}
static int parse_trigger(xmlNodePtr cur, struct trigger_t *trigger)
{
int res = 0;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, trigger, name);
else if (name_is(cur, "type"))
res = load_cur_content(cur, trigger, type);
else if (name_is(cur, "path"))
res = load_cur_content(cur, trigger, path);
if (res)
return -1;
cur = cur->next;
}
return 0;
}
static int parse_sender(xmlNodePtr cur, struct sender_t *sender)
{
int res = 0;
cur = cur->xmlChildrenNode;
while (cur) {
if (name_is(cur, "name"))
res = load_cur_content(cur, sender, name);
else if (name_is(cur, "outdir"))
res = load_cur_content(cur, sender, outdir);
else if (name_is(cur, "maxcrashdirs"))
res = load_cur_content(cur, sender, maxcrashdirs);
else if (name_is(cur, "maxlines"))
res = load_cur_content(cur, sender, maxlines);
else if (name_is(cur, "spacequota"))
res = load_cur_content(cur, sender, spacequota);
else if (name_is(cur, "foldersize"))
res = load_cur_content(cur, sender, foldersize);
else if (name_is(cur, "uptime"))
res = parse_uptime(cur, sender);
if (res)
return -1;
cur = cur->next;
}
return 0;
}
#define common_parse(node, mem, maxmem) \
(__extension__ \
({ \
int id; \
int _ret = 0; \
int res; \
struct mem##_t *mem; \
\
node = node->xmlChildrenNode; \
\
while (node) { \
if (is_enable(node)) { \
id = get_id_index(node, maxmem); \
if (id < 0) { \
_ret = -1; \
break; \
} \
\
mem = malloc(sizeof(*mem)); \
if (!mem) { \
_ret = -1; \
break; \
} \
memset(mem, 0, sizeof(*mem)); \
conf.mem[id] = mem; \
mem->id = id; \
res = parse_##mem(node, mem); \
if (res) { \
free(mem); \
_ret = -1; \
break; \
} \
} \
node = node->next; \
} \
_ret; \
}) \
)
int load_conf(const char *path)
{
int res = 0;
xmlDocPtr doc;
xmlNodePtr cur, node;
doc = xmlParseFile(path);
if (!doc) {
LOGI("Parsing conf (%s) fail\n", path);
goto error;
}
cur = xmlDocGetRootElement(doc);
if (!cur) {
LOGE("Get root (%s) fail\n", path);
goto free;
}
cur = cur->xmlChildrenNode;
while ((node = cur)) {
if (name_is(node, "senders"))
res = common_parse(node, sender, SENDER_MAX);
else if (name_is(node, "triggers"))
res = common_parse(node, trigger, TRIGGER_MAX);
else if (name_is(node, "vms"))
res = common_parse(node, vm, VM_MAX);
else if (name_is(node, "crashes"))
res = parse_crashes(node);
else if (name_is(node, "logs"))
res = common_parse(node, log, LOG_MAX);
else if (name_is(node, "infos"))
res = common_parse(node, info, INFO_MAX);
if (res)
goto free;
cur = cur->next;
}
print();
xmlFreeDoc(doc);
return 0;
free:
xmlFreeDoc(doc);
error:
return -1;
}

View File

@@ -0,0 +1,645 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <linux/loop.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <utime.h>
#include <string.h>
#include <blkid/blkid.h>
#include <ext2fs/ext2fs.h>
#include "fsutils.h"
#include "log_sys.h"
#define DEV_LOOP_CTL "/dev/loop-control"
struct walking_inode_data {
const char *current_out_native_dirpath;
int dumped_count;
};
static int get_par_startaddr_from_img(const char *img,
const char *target_parname,
unsigned long long *start)
{
blkid_probe pr;
blkid_partlist ls;
blkid_partition par;
int i;
int nparts;
const char *par_name;
unsigned int sector_size;
unsigned long long par_start;
if (!img || !target_parname || !start)
return -1;
pr = blkid_new_probe_from_filename(img);
if (!pr) {
LOGE("blkid new probe failed\n");
return -1;
}
ls = blkid_probe_get_partitions(pr);
if (!ls) {
LOGE("blkid get partitions failed\n");
goto err;
}
nparts = blkid_partlist_numof_partitions(ls);
if (nparts <= 0) {
LOGE("(%d) partitions in (%s)??\n", nparts, img);
goto err;
}
for (i = 0; i < nparts; i++) {
par = blkid_partlist_get_partition(ls, i);
par_name = blkid_partition_get_name(par);
if (!par_name) {
LOGW("A partition in (%s) don't have name??\n", img);
continue;
}
if (!strcmp(par_name, target_parname))
goto found;
}
LOGE("no partition of (%s) is named %s\n", img, target_parname);
err:
blkid_free_probe(pr);
return -1;
found:
sector_size = blkid_probe_get_sectorsize(pr);
par_start = (unsigned long long)blkid_partition_get_start(par);
*start = par_start * sector_size;
blkid_free_probe(pr);
return 0;
}
int loopdev_num_get_free(void)
{
int loopctlfd;
int devnr;
loopctlfd = open(DEV_LOOP_CTL, O_RDONLY);
if (loopctlfd == -1) {
LOGE("failed to open %s, error (%s)\n", DEV_LOOP_CTL,
strerror(errno));
return -errno;
}
devnr = ioctl(loopctlfd, LOOP_CTL_GET_FREE);
if (devnr == -1) {
LOGE("failed to get free loopdev, error (%s)\n",
strerror(errno));
close(loopctlfd);
return -errno;
}
close(loopctlfd);
return devnr;
}
static int loopdev_set_status(const char *loopdev,
const struct loop_info64 *info)
{
int loopfd;
int res;
if (!loopdev || !info)
return -EINVAL;
loopfd = open(loopdev, O_RDWR);
if (loopfd == -1) {
LOGE("failed to open (%s), error(%s)\n", loopdev,
strerror(errno));
return -errno;
}
res = ioctl(loopfd, LOOP_SET_STATUS64, info);
if (res == -1) {
LOGE("failed to set info to (%s), error(%s)\n", loopdev,
strerror(errno));
close(loopfd);
return -errno;
}
close(loopfd);
return 0;
}
static int loopdev_set_img(const char *loopdev, const char *img_path)
{
int loopfd;
int imgfd;
int res;
if (!loopdev || !img_path)
return -EINVAL;
loopfd = open(loopdev, O_WRONLY);
if (loopfd == -1) {
LOGE("failed to open %s, error (%s)\n", loopdev,
strerror(errno));
return -errno;
}
imgfd = open(img_path, O_RDONLY);
if (imgfd == -1) {
LOGE("failed to open %s, error (%s)\n", img_path,
strerror(errno));
close(loopfd);
return -errno;
}
res = ioctl(loopfd, LOOP_SET_FD, imgfd);
if (res == -1) {
LOGE("failed to set (%s) to (%s), error (%s)\n", img_path,
loopdev, strerror(errno));
close(loopfd);
close(imgfd);
return -errno;
}
close(loopfd);
close(imgfd);
return 0;
}
int loopdev_set_img_par(const char *loopdev, const char *img_path,
const char *parname)
{
struct loop_info64 info;
unsigned long long par_start;
int res;
if (!loopdev || !img_path || !parname)
return -1;
res = get_par_startaddr_from_img(img_path, parname, &par_start);
if (res == -1) {
LOGE("failed to get data par startaddr\n");
return -1;
}
res = loopdev_set_img(loopdev, img_path);
if (res) {
LOGE("failed to set img (%s) to (%s), error (%s)\n",
img_path, loopdev, strerror(-res));
return -1;
}
memset(&info, 0, sizeof(info));
info.lo_offset = par_start;
res = loopdev_set_status(loopdev, &info);
if (res < 0) {
LOGE("failed to set loopdev, error (%s)\n", strerror(-res));
return -1;
}
return 0;
}
int loopdev_check_parname(const char *loopdev, const char *parname)
{
struct ext2_super_block super;
int fd;
const int skiprate = 512;
loff_t sk = 0;
if (!loopdev || !parname)
return -ENOENT;
/* quickly find super block */
fd = open(loopdev, O_RDONLY);
if (fd == -1) {
LOGE("failed to open (%s), error(%s)\n", loopdev,
strerror(errno));
return -errno;
}
for (; lseek64(fd, sk, SEEK_SET) != -1 &&
read(fd, &super, 512) == 512; sk += skiprate) {
if (super.s_magic != EXT2_SUPER_MAGIC)
continue;
LOGD("find super block at +%ld\n", sk);
/* only look into the primary super block */
if (super.s_volume_name[0]) {
close(fd);
return !strcmp(super.s_volume_name, parname);
}
break;
}
close(fd);
return 0;
}
/**
* Align the file's perms, only print WARNING if errors occurred in this
* function.
*
* Note: Drop user and group.
*/
static void align_props(int fd, const char *name,
const struct ext2_inode *inode)
{
int res;
struct utimbuf ut;
if (!inode || !name)
return;
if (fd >= 0)
res = fchmod(fd, inode->i_mode);
else
res = chmod(name, inode->i_mode);
if (res == -1)
LOGW("failed to exec (xchmod), error (%s)\n", strerror(errno));
ut.actime = inode->i_atime;
ut.modtime = inode->i_mtime;
res = utime(name, &ut);
if (res == -1)
LOGW("failed to exec (utime), error (%s)\n", strerror(errno));
}
static int e2fs_get_inodenum_by_fpath(ext2_filsys fs, const char *fpath,
ext2_ino_t *out_ino)
{
ext2_ino_t root;
ext2_ino_t cwd;
errcode_t res;
if (!fs || !fpath || !out_ino)
return -1;
root = EXT2_ROOT_INO;
cwd = EXT2_ROOT_INO;
res = ext2fs_namei(fs, root, cwd, fpath, out_ino);
if (res) {
LOGE("ext2fs failed to get ino, path (%s), error (%s)\n",
fpath, error_message(res));
return -1;
}
return 0;
}
static int e2fs_read_inode_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
struct ext2_inode *inode)
{
errcode_t res;
if (!fs || !ino || !inode)
return -1;
res = ext2fs_read_inode(fs, ino, inode);
if (res) {
LOGE("ext2fs failed to get inode, ino (%d), error (%s)\n",
ino, error_message(res));
return -1;
}
return 0;
}
static int e2fs_dump_file_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
const char *out_fp)
{
errcode_t res;
int ret = 0;
int fd;
unsigned int got;
struct ext2_inode inode;
ext2_file_t e2_file;
char *buf;
ssize_t write_b;
if (!fs || !ino || !out_fp)
return -1;
res = e2fs_read_inode_by_inodenum(fs, ino, &inode);
if (res) {
LOGE("ext2fs failed to read inode, error (%s)\n",
error_message(res));
return -1;
}
fd = open(out_fp, O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE, 0666);
if (fd == -1) {
LOGE("open (%s) failed, error (%s)\n", out_fp, strerror(errno));
return -1;
}
/* open with read only */
res = ext2fs_file_open2(fs, ino, &inode, 0, &e2_file);
if (res) {
LOGE("ext2fs failed to open file, ino (%d), error (%s)\n",
ino, error_message(res));
close(fd);
return -1;
}
res = ext2fs_get_mem(fs->blocksize, &buf);
if (res) {
LOGE("ext2fs failed to get mem, error (%s)\n",
error_message(res));
close(fd);
ext2fs_file_close(e2_file);
return -1;
}
while (1) {
res = ext2fs_file_read(e2_file, buf, fs->blocksize, &got);
/* got equals zero in failed case */
if (res) {
LOGE("ext2fs failed to read (%u), error (%s)\n",
ino, error_message(res));
ret = -1;
}
if (!got)
break;
write_b = write(fd, buf, got);
if ((unsigned int)write_b != got) {
LOGE("failed to write file (%s), error (%s)\n",
out_fp, strerror(errno));
ret = -1;
break;
}
}
align_props(fd, out_fp, &inode);
if (buf)
ext2fs_free_mem(&buf);
/* ext2fs_file_close only failed in flush process */
ext2fs_file_close(e2_file);
close(fd);
return ret;
}
int e2fs_dump_file_by_fpath(ext2_filsys fs, const char *in_fp,
const char *out_fp)
{
int res;
ext2_ino_t ino;
if (!fs || !in_fp || !out_fp)
return -1;
res = e2fs_get_inodenum_by_fpath(fs, in_fp, &ino);
if (res)
return res;
return e2fs_dump_file_by_inodenum(fs, ino, out_fp);
}
static int e2fs_read_file_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
void **out_data, unsigned long *size)
{
errcode_t res;
unsigned int got;
struct ext2_inode inode;
ext2_file_t e2_file;
__u64 _size;
char *buf;
if (!fs || !ino || !out_data || !size)
return -1;
res = e2fs_read_inode_by_inodenum(fs, ino, &inode);
if (res) {
LOGE("ext2fs failed to read inode, error (%s)\n",
error_message(res));
return -1;
}
_size = EXT2_I_SIZE(&inode);
if (!_size) {
LOGW("try to read a empty file\n");
*size = 0;
*out_data = 0;
return 0;
}
/* open with read only */
res = ext2fs_file_open2(fs, ino, &inode, 0, &e2_file);
if (res) {
LOGE("ext2fs failed to open file, ino (%d), error (%s)\n",
ino, error_message(res));
return -1;
}
res = ext2fs_get_mem(_size + 1, &buf);
if (res) {
LOGE("ext2fs failed to get mem, error (%s)\n",
error_message(res));
ext2fs_file_close(e2_file);
return -1;
}
res = ext2fs_file_read(e2_file, buf, _size, &got);
/* got equals zero in failed case */
if (res) {
LOGE("ext2fs failed to read (%u), error (%s)\n",
ino, error_message(res));
goto err;
}
/* ext2fs_file_close only failed in flush process */
ext2fs_file_close(e2_file);
*size = _size;
buf[_size] = 0;
*out_data = buf;
return 0;
err:
free(buf);
ext2fs_file_close(e2_file);
return -1;
}
int e2fs_read_file_by_fpath(ext2_filsys fs, const char *in_fp,
void **out_data, unsigned long *size)
{
int res;
ext2_ino_t ino;
if (!fs || !in_fp || !out_data || !size)
return -1;
res = e2fs_get_inodenum_by_fpath(fs, in_fp, &ino);
if (res)
return res;
return e2fs_read_file_by_inodenum(fs, ino, out_data, size);
}
static int dump_inode_recursively_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
struct walking_inode_data *data,
const char *fname);
static int callback_for_subentries(struct ext2_dir_entry *dirent,
int offset EXT2FS_ATTR((unused)),
int blocksize EXT2FS_ATTR((unused)),
char *buf EXT2FS_ATTR((unused)),
void *private)
{
char fname[EXT2_NAME_LEN + 1];
struct walking_inode_data *data = private;
int len;
len = dirent->name_len & 0xFF; /* EXT2_NAME_LEN = 255 */
strncpy(fname, dirent->name, len);
fname[len] = 0;
return dump_inode_recursively_by_inodenum(NULL, dirent->inode,
data, fname);
}
static int dump_inode_recursively_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
struct walking_inode_data *data,
const char *fname)
{
int res;
char *out_fpath;
errcode_t err;
static ext2_filsys fs_for_dump;
struct ext2_inode inode;
if (!ino || !data || !data->current_out_native_dirpath || !fname)
goto abort;
/* caller is not callback_for_subentries */
if (fs)
fs_for_dump = fs;
if (!strcmp(fname, ".") || !strcmp(fname, ".."))
return 0;
res = asprintf(&out_fpath, "%s/%s", data->current_out_native_dirpath,
fname);
if (res == -1) {
LOGE("failed to construct target file name, ");
goto abort;
}
res = e2fs_read_inode_by_inodenum(fs_for_dump, ino, &inode);
if (res) {
LOGE("ext2fs failed to read inode, ");
goto abort_free;
}
if (LINUX_S_ISREG(inode.i_mode)) {
/* do dump for file */
res = e2fs_dump_file_by_inodenum(fs_for_dump, ino, out_fpath);
if (res) {
LOGE("ext2fs failed to dump file, ");
goto abort_free;
}
data->dumped_count++;
} else if (LINUX_S_ISDIR(inode.i_mode)) {
/* mkdir for directory and dump the subentry */
res = mkdir(out_fpath, 0700);
if (res == -1 && errno != EEXIST) {
LOGE("failed to mkdir (%s), error (%s), ", out_fpath,
strerror(errno));
goto abort_free;
}
data->dumped_count++;
data->current_out_native_dirpath = out_fpath;
err = ext2fs_dir_iterate(fs_for_dump, ino, 0, 0,
callback_for_subentries,
(void *)data);
if (err) {
LOGE("ext2fs failed to iterate dir, errno (%s), ",
error_message(err));
goto abort_free;
}
align_props(-1, out_fpath, &inode);
}
/* else ignore the rest types, such as link, socket, fifo, ... */
free(out_fpath);
return 0;
abort_free:
free(out_fpath);
abort:
LOGE("dump dir aborted...\n");
return DIRENT_ABORT;
}
int e2fs_dump_dir_by_dpath(ext2_filsys fs, const char *in_dp,
const char *out_dp, int *count)
{
ext2_ino_t ino;
struct walking_inode_data dump_needed;
const char *dname;
int res;
if (!fs || !in_dp || !count)
return -1;
*count = 0;
if (!directory_exists(out_dp)) {
LOGE("dir need dump into an existed dir\n");
return -1;
}
res = e2fs_get_inodenum_by_fpath(fs, in_dp, &ino);
if (res == -1)
return -1;
dname = strrchr(in_dp, '/');
if (dname)
dname++;
else
dname = in_dp;
dump_needed.dumped_count = 0;
dump_needed.current_out_native_dirpath = out_dp;
res = dump_inode_recursively_by_inodenum(fs, ino, &dump_needed, dname);
*count = dump_needed.dumped_count;
if (res) {
LOGE("ext2fs failed to dump dir\n");
return -1;
}
return 0;
}
int e2fs_open(const char *dev, ext2_filsys *outfs)
{
errcode_t res;
if (!dev || !outfs)
return -1;
add_error_table(&et_ext2_error_table);
res = ext2fs_open(dev, EXT2_FLAG_64BITS, 0, 0,
unix_io_manager, outfs);
if (res) {
LOGE("ext2fs fail to open (%s), error (%s)\n", dev,
error_message(res));
return -1;
}
return 0;
}
void e2fs_close(ext2_filsys fs)
{
if (fs)
ext2fs_close(fs);
remove_error_table(&et_ext2_error_table);
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>
#include <string.h>
#include <limits.h>
#include "load_conf.h"
#include "fsutils.h"
#include "crash_reclassify.h"
#include "sender.h"
#include "event_queue.h"
#include "event_handler.h"
#include "channels.h"
#include "log_sys.h"
#include "version.h"
#define CONFIG_INSTALL "/usr/share/defaults/telemetrics/acrnprobe.xml"
#define CONFIG_CUSTOMIZE "/etc/acrnprobe.xml"
void usage(void)
{
printf("[Usage]\n");
printf("\tacrnprobe -c [configuration file path] [-hV]\n");
printf("[Options]\n");
printf("\t-c, --config Configuration file\n");
printf("\t-h, --help print the help message\n");
printf("\t-V, --version Print the program version\n");
}
static void uptime(const struct sender_t *sender)
{
int fd;
int frequency;
const struct uptime_t *uptime;
uptime = sender->uptime;
if (!uptime)
return;
if (cfg_atoi(uptime->frequency, uptime->frequency_len,
&frequency) == -1) {
LOGE("Invalid frequency (%s) in config file, exiting...\n",
uptime->frequency);
exit(-1);
}
if (frequency > 0)
sleep(frequency);
fd = open(uptime->path, O_RDWR | O_CREAT, 0666);
if (fd < 0)
LOGE("open uptime_file with (%d, %s) failed, error (%s)\n",
frequency, uptime->path, strerror(errno));
else
close(fd);
}
int main(int argc, char *argv[])
{
int ret;
int id;
int op;
struct sender_t *sender;
char cfg[PATH_MAX];
const char * const config_path[2] = {
CONFIG_CUSTOMIZE,
CONFIG_INSTALL
};
const struct option opts[] = {
{ "config", required_argument, NULL, 'c' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
cfg[0] = 0;
while ((op = getopt_long(argc, argv, "c:hV", opts,
NULL)) != -1) {
switch (op) {
case 'c':
strncpy(cfg, optarg, PATH_MAX - 1);
break;
case 'h':
usage();
return 0;
case 'V':
printf("version is %d.%d-%s, build by %s@%s\n",
AP_MAJOR_VERSION, AP_MINOR_VERSION,
AP_BUILD_VERSION, AP_BUILD_USER,
AP_BUILD_TIME);
return 0;
case '?':
usage();
return -1;
}
}
if (!cfg[0]) {
if (file_exists(config_path[0]))
strncpy(cfg, config_path[0], PATH_MAX);
else
strncpy(cfg, config_path[1], PATH_MAX);
}
cfg[PATH_MAX - 1] = 0;
ret = load_conf(cfg);
if (ret)
return -1;
init_crash_reclassify();
ret = init_sender();
if (ret)
return -1;
init_event_queue();
ret = init_event_handler();
if (ret)
return -1;
ret = init_channels();
if (ret)
return -1;
while (1) {
for_each_sender(id, sender, conf) {
if (!sender)
continue;
uptime(sender);
}
}
return 0;
}

View File

@@ -0,0 +1,443 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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 <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <openssl/sha.h>
#include <time.h>
#include "property.h"
#include "fsutils.h"
#include "history.h"
#include "load_conf.h"
#include "log_sys.h"
#include "probeutils.h"
#include "strutils.h"
#define CRASH_CURRENT_LOG "currentcrashlog"
#define STATS_CURRENT_LOG "currentstatslog"
#define VM_CURRENT_LOG "currentvmlog"
#define BOOTID_NODE "/proc/sys/kernel/random/boot_id"
#define BOOTID_LOG "currentbootid"
unsigned long long get_uptime(void)
{
long long time_ns;
struct timespec ts;
int res;
res = clock_gettime(CLOCK_BOOTTIME, &ts);
if (res == -1)
return res;
time_ns = (long long)ts.tv_sec * 1000000000LL +
(long long)ts.tv_nsec;
return time_ns;
}
int get_uptime_string(char *newuptime, int *hours)
{
long long tm;
int seconds, minutes;
int len;
tm = get_uptime();
if (tm == -1)
return -1;
/* seconds */
*hours = (int)(tm / 1000000000LL);
seconds = *hours % 60;
/* minutes */
*hours /= 60;
minutes = *hours % 60;
/* hours */
*hours /= 60;
len = snprintf(newuptime, UPTIME_SIZE, "%04d:%02d:%02d", *hours,
minutes, seconds);
if (s_not_expect(len, UPTIME_SIZE))
return -1;
return 0;
}
int get_current_time_long(char *buf)
{
time_t t;
struct tm *time_val;
time(&t);
time_val = localtime((const time_t *)&t);
if (!time_val)
return -1;
return strftime(buf, LONG_TIME_SIZE, "%Y-%m-%d/%H:%M:%S ", time_val);
}
static int compute_key(char *key, size_t klen, const char *seed,
const size_t slen)
{
SHA256_CTX sha;
char buf[VERSION_SIZE];
int len;
long long time_ns;
char *tmp_key = key;
unsigned char results[SHA256_DIGEST_LENGTH];
size_t i;
if (!key || !seed || !slen)
return -1;
if (klen > SHA256_DIGEST_LENGTH * 2 || !klen)
return -1;
SHA256_Init(&sha);
time_ns = get_uptime();
len = snprintf(buf, VERSION_SIZE, "%s%s%lld",
gbuildversion, guuid, time_ns);
if (s_not_expect(len , VERSION_SIZE))
return -1;
SHA256_Update(&sha, (unsigned char *)buf, strnlen(buf, VERSION_SIZE));
SHA256_Update(&sha, (unsigned char *)seed, strnlen(seed, slen));
SHA256_Final(results, &sha);
for (i = 0; i < klen / 2; i++) {
len = snprintf(tmp_key, 3, "%02x", results[i]);
if (s_not_expect(len, 3))
return -1;
tmp_key += 2;
}
*tmp_key = 0;
return 0;
}
/**
* Generate an event id with specified type.
*
* @param seed1 Seed1.
* @param seed2 Seed2, this parameter will be ignored if the value is NULL.
* @param type The type of key. The length of generated id will be 20
* characters if type is KEY_SHORT; 32 characters if type is
* KEY_LONG.
*
* @return a pointer to result haskkey if successful, or NULL if not.
*/
char *generate_event_id(const char *seed1, size_t slen1, const char *seed2,
size_t slen2, enum key_type type)
{
int ret;
char *buf;
char *key;
size_t klen;
if (!seed1 || !slen1)
return NULL;
if (type == KEY_SHORT)
klen = SHORT_KEY_LENGTH;
else if (type == KEY_LONG)
klen = LONG_KEY_LENGTH;
else
return NULL;
key = (char *)malloc(klen + 1);
if (!key) {
LOGE("failed to generate event id, out of memory\n");
return NULL;
}
if (seed2) {
if (asprintf(&buf, "%s%s", seed1, seed2) == -1) {
LOGE("failed to generate event id, out of memory\n");
free(key);
return NULL;
}
ret = compute_key(key, klen, (const char *)buf, slen1 + slen2);
free(buf);
} else {
ret = compute_key(key, klen, seed1, slen1);
}
if (ret < 0) {
LOGE("compute_key error\n");
free(key);
key = NULL;
}
return key;
}
/**
* Reserve a dir for log storage.
*
* @param mode Mode for log storage.
* @param[out] dir Prefix of dir path reserved.
* @param[out] index of dir reserved.
*
* @return 0 if successful, or -1 if not.
*/
static int reserve_log_folder(enum e_dir_mode mode, char *dir,
unsigned int *current)
{
char path[PATH_MAX];
int res;
int plen;
int dlen;
struct sender_t *crashlog;
const char *outdir;
int maxdirs;
crashlog = get_sender_by_name("crashlog");
if (!crashlog)
return -1;
outdir = crashlog->outdir;
switch (mode) {
case MODE_CRASH:
plen = snprintf(path, PATH_MAX, "%s/%s", outdir,
CRASH_CURRENT_LOG);
dlen = snprintf(dir, PATH_MAX, "%s/%s", outdir, "crashlog");
break;
case MODE_STATS:
plen = snprintf(path, PATH_MAX, "%s/%s", outdir,
STATS_CURRENT_LOG);
dlen = snprintf(dir, PATH_MAX, "%s/%s", outdir, "stats");
break;
case MODE_VMEVENT:
plen = snprintf(path, PATH_MAX, "%s/%s", outdir,
VM_CURRENT_LOG);
dlen = snprintf(dir, PATH_MAX, "%s/%s", outdir, "vmevent");
break;
default:
LOGW("Invalid mode %d\n", mode);
return -1;
}
if (s_not_expect(plen, PATH_MAX) || s_not_expect(dlen, PATH_MAX)) {
LOGE("the length of path/dir is too long\n");
return -1;
}
/* Read current value in file */
res = file_read_int(path, current);
if (res < 0)
return res;
if (cfg_atoi(crashlog->maxcrashdirs, crashlog->maxcrashdirs_len,
&maxdirs) == -1)
return -1;
if (maxdirs <= 0) {
LOGE("failed to reserve dir, maxdirs must be greater than 0\n");
return -1;
}
/* Open file in read/write mode to update the new current */
res = file_update_int(path, *current, (unsigned int)maxdirs);
if (res < 0)
return res;
return 0;
}
static char *cf_line(char *dest, const char *k, size_t klen, const char *v,
size_t vlen)
{
char *t;
t = mempcpy(dest, k, klen);
t = mempcpy(t, v, vlen);
return mempcpy(t, "\n", 1);
}
/**
* Create a crashfile with given params.
*
* @param dir Where to generate crashfile.
* @param event Event name.
* @param hashkey Event id.
* @param type Subtype of this event.
* @param data* String obtained by get_data.
*/
void generate_crashfile(const char *dir,
const char *event, size_t elen,
const char *hashkey, size_t hlen,
const char *type, size_t tlen,
const char *data0, size_t d0len,
const char *data1, size_t d1len,
const char *data2, size_t d2len)
{
char *buf;
char *path;
char *tail;
char datetime[LONG_TIME_SIZE];
char uptime[UPTIME_SIZE];
int hours;
const int fmtsize = 128;
size_t ltlen;
int n;
int filesize;
if (!dir || !event || !elen || !hashkey || !hlen ||
!type || !tlen)
return;
if (d0len > 0 && !data0)
return;
if (d1len > 0 && !data1)
return;
if (d2len > 0 && !data2)
return;
ltlen = get_current_time_long(datetime);
if (!ltlen)
return;
n = get_uptime_string(uptime, &hours);
if (n < 0)
return;
filesize = fmtsize + ltlen + n + elen + hlen + tlen + d0len + d1len +
d2len + strnlen(guuid, UUID_SIZE) +
strnlen(gbuildversion, BUILD_VERSION_SIZE);
buf = malloc(filesize);
if (buf == NULL) {
LOGE("out of memory\n");
return;
}
tail = cf_line(buf, "EVENT=", 6, event, elen);
tail = cf_line(tail, "ID=", 3, hashkey, hlen);
tail = cf_line(tail, "DEVICEID=", 9, guuid, strnlen(guuid, UUID_SIZE));
tail = cf_line(tail, "DATE=", 5, datetime, ltlen);
tail = cf_line(tail, "UPTIME=", 7, uptime, n);
tail = cf_line(tail, "BUILD=", 6, gbuildversion,
strnlen(gbuildversion, BUILD_VERSION_SIZE));
tail = cf_line(tail, "TYPE=", 5, type, tlen);
if (d0len)
tail = cf_line(tail, "DATA0=", 6, data0, d0len);
if (d1len)
tail = cf_line(tail, "DATA1=", 6, data1, d1len);
if (d2len)
tail = cf_line(tail, "DATA2=", 6, data2, d2len);
tail = mempcpy(tail, "_END\n", 5);
*tail = '\0';
if (asprintf(&path, "%s/crashfile", dir) == -1) {
LOGE("out of memory\n");
free(buf);
return;
}
if (overwrite_file(path, buf) != 0)
LOGE("failed to new crashfile (%s), error (%s)\n", path,
strerror(errno));
free(buf);
free(path);
}
/**
* Create a dir for log storage.
*
* @param mode Mode for log storage.
* @param hashkey Event id.
* @param[out] dir_len Length of generated dir.
*
* @return a pointer to generated path if successful, or NULL if not.
*/
char *generate_log_dir(enum e_dir_mode mode, char *hashkey, size_t *dir_len)
{
char *path;
char dir[PATH_MAX];
unsigned int current;
int len;
if (reserve_log_folder(mode, dir, &current))
return NULL;
len = asprintf(&path, "%s%d_%s", dir, current, hashkey);
if (len == -1) {
LOGE("construct log path failed, out of memory\n");
hist_raise_infoerror("DIR CREATE", 10);
return NULL;
}
if (mkdir(path, 0777) == -1) {
LOGE("Cannot create dir %s\n", path);
hist_raise_infoerror("DIR CREATE", 10);
free(path);
return NULL;
}
if (dir_len)
*dir_len = (size_t)len;
return path;
}
int is_boot_id_changed(void)
{
void *boot_id;
void *logged_boot_id;
char logged_boot_id_path[PATH_MAX];
unsigned long size;
struct sender_t *crashlog;
int res;
int result = 1; /* returns changed by default */
crashlog = get_sender_by_name("crashlog");
if (!crashlog)
return result;
res = read_file(BOOTID_NODE, &size, &boot_id);
if (res == -1 || !size)
return result;
res = snprintf(logged_boot_id_path, sizeof(logged_boot_id_path),
"%s/%s", crashlog->outdir, BOOTID_LOG);
if (s_not_expect(res, sizeof(logged_boot_id_path)))
goto out;
if (file_exists(logged_boot_id_path)) {
res = read_file(logged_boot_id_path, &size, &logged_boot_id);
if (res == -1 || !size)
goto out;
if (!strcmp((char *)logged_boot_id, (char *)boot_id))
result = 0;
free(logged_boot_id);
}
if (result)
overwrite_file(logged_boot_id_path, boot_id);
out:
free(boot_id);
return result;
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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 <openssl/sha.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "property.h"
#include "log_sys.h"
#include "fsutils.h"
#define MACHINE_ID "/etc/machine-id"
#define OS_VERSION "/usr/lib/os-release"
#define OS_VERSION_KEY "VERSION_ID="
#define DEVICE_ID_UNKNOWN "UnknownId"
#define LOG_UUID "uuid.txt"
#define LOG_BUILDID "buildid.txt"
static void get_device_id(struct sender_t *sender)
{
int ret;
char *loguuid;
ret = asprintf(&loguuid, "%s/%s", sender->outdir, LOG_UUID);
if (ret < 0) {
LOGE("compute string failed, out of memory\n");
return;
}
ret = file_read_string(MACHINE_ID, guuid, BUILD_VERSION_SIZE);
if (ret <= 0)
LOGE("Could not get mmc id: %d (%s)\n",
ret, strerror(-ret));
else
goto write;
LOGE("Could not find DeviceId, set it to '%s'\n",
DEVICE_ID_UNKNOWN);
strncpy(guuid, DEVICE_ID_UNKNOWN, UUID_SIZE);
guuid[UUID_SIZE - 1] = '\0';
write:
overwrite_file(loguuid, guuid);
free(loguuid);
}
static int get_buildversion(struct sender_t *sender)
{
int ret;
char lastbuild[BUILD_VERSION_SIZE];
char *logbuildid;
char *currentbuild = gbuildversion;
ret = file_read_key_value(gbuildversion, sizeof(gbuildversion),
OS_VERSION, OS_VERSION_KEY,
strlen(OS_VERSION_KEY));
if (ret <= 0) {
LOGE("failed to get version from %s, error (%s)\n",
OS_VERSION, strerror(-ret));
return ret;
}
ret = asprintf(&logbuildid, "%s/%s", sender->outdir, LOG_BUILDID);
if (ret < 0) {
LOGE("compute string failed, out of memory\n");
return ret;
}
ret = file_read_string(logbuildid, lastbuild, BUILD_VERSION_SIZE);
if (ret == -ENOENT ||
!ret ||
(ret > 0 && strcmp(currentbuild, lastbuild))) {
/* build changed or file not found, overwrite it */
ret = overwrite_file(logbuildid, gbuildversion);
if (ret) {
LOGE("create (%s) failed, error (%s)\n", logbuildid,
strerror(-ret));
goto free;
}
sender->sw_updated = 1;
ret = 0;
} else if (ret < 0) {
LOGE("Cannot read %s, error (%s)\n",
logbuildid, strerror(errno));
} else {
/* buildid is the same */
sender->sw_updated = 0;
ret = 0;
}
free:
free(logbuildid);
return ret;
}
int swupdated(struct sender_t *sender)
{
return sender->sw_updated;
}
int init_properties(struct sender_t *sender)
{
int ret;
ret = get_buildversion(sender);
if (ret) {
LOGE("init properties failed\n");
return ret;
}
get_device_id(sender);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* 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 <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>
#include "fsutils.h"
#include "startupreason.h"
#include "log_sys.h"
#define CURRENT_KERNEL_CMDLINE "/proc/cmdline"
static int get_cmdline_bootreason(char *bootreason, const size_t limit)
{
int res;
unsigned long size;
char *start, *p1, *p2, *end;
char *cmdline;
const char *key = "ABL.reset=";
res = read_file(CURRENT_KERNEL_CMDLINE, &size, (void *)&cmdline);
if (res < 0) {
LOGE("failed to read file %s - %s\n",
CURRENT_KERNEL_CMDLINE, strerror(errno));
return -1;
}
if (!size) {
LOGW("empty file (%s)\n", CURRENT_KERNEL_CMDLINE);
return 0;
}
start = strstr(cmdline, key);
if (!start) {
LOGW("can't find reboot reason with key (%s) in cmdline\n",
key);
free(cmdline);
return 0;
}
/* if the string contains ' ' or '\n', break it by '\0' */
start += strlen(key);
p1 = strchr(start, ' ');
p2 = strchr(start, '\n');
if (p2 && p1)
end = MIN(p1, p2);
else
end = MAX(p1, p2);
if (!end)
end = cmdline + size;
const size_t len = MIN((size_t)(end - start), (size_t)(limit - 1));
if (len > 0) {
memcpy(bootreason, start, len);
*(bootreason + len) = 0;
}
free(cmdline);
return len;
}
static int get_default_bootreason(char *bootreason, const size_t limit)
{
int len;
int i;
len = get_cmdline_bootreason(bootreason, limit);
if (len <= 0)
return len;
for (i = 0; i < len; i++)
bootreason[i] = toupper(bootreason[i]);
return len;
}
void read_startupreason(char *startupreason, const size_t limit)
{
int res;
static char reboot_reason_cache[REBOOT_REASON_SIZE];
if (!startupreason || !limit)
return;
if (!reboot_reason_cache[0]) {
/* fill cache */
res = get_default_bootreason(reboot_reason_cache,
sizeof(reboot_reason_cache));
if (res <= 0)
strncpy(reboot_reason_cache, "UNKNOWN",
sizeof(reboot_reason_cache));
}
const size_t len = MIN(strnlen(reboot_reason_cache, REBOOT_REASON_SIZE),
limit - 1);
memcpy(startupreason, reboot_reason_cache, len);
*(startupreason + len) = 0;
return;
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2018 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "log_sys.h"
#include "fsutils.h"
#include "strutils.h"
#include "vmrecord.h"
#include <stdlib.h>
int vmrecord_last(struct vmrecord_t *vmrecord, const char *vm_name,
size_t nlen, char *vmkey, size_t ksize)
{
int res;
char *p;
char *key;
if (!vmrecord || !vm_name || !nlen || !vmkey || !ksize)
return -1;
key = malloc(nlen + 2);
if (!key)
return -1;
memcpy(key, vm_name, nlen);
key[nlen] = ' ';
key[nlen + 1] = '\0';
pthread_mutex_lock(&vmrecord->mtx);
res = file_read_key_value_r(vmkey, ksize, vmrecord->path, key,
nlen + 1);
pthread_mutex_unlock(&vmrecord->mtx);
free(key);
if (res < 0) {
LOGE("failed to search %s, %s\n", vmrecord->path,
strerror(errno));
return -1;
}
p = strchr(vmkey, ' ');
if (p)
*p = 0;
return 0;
}
/* This function must be called with holding vmrecord mutex */
int vmrecord_mark(struct vmrecord_t *vmrecord, const char *vmkey,
size_t klen, enum vmrecord_mark_t type)
{
size_t len;
char *line;
char *tag;
if (!vmrecord || !vmrecord->recos || !vmkey || !klen)
return -1;
line = get_line(vmkey, klen, vmrecord->recos->begin,
vmrecord->recos->size, vmrecord->recos->begin, &len);
if (!line)
return -1;
tag = line + len - VMRECORD_TAG_LEN;
if (type == SUCCESS)
memcpy(tag, VMRECORD_TAG_SUCCESS, VMRECORD_TAG_LEN);
else if (type == NOT_FOUND)
memcpy(tag, VMRECORD_TAG_NOT_FOUND, VMRECORD_TAG_LEN);
else if (type == MISS_LOG)
memcpy(tag, VMRECORD_TAG_MISS_LOG, VMRECORD_TAG_LEN);
else if (type == WAITING_SYNC)
memcpy(tag, VMRECORD_TAG_WAITING_SYNC, VMRECORD_TAG_LEN);
else if (type == ON_GOING)
memcpy(tag, VMRECORD_TAG_ON_GOING, VMRECORD_TAG_LEN);
else if (type == NO_RESRC)
memcpy(tag, VMRECORD_TAG_NO_RESOURCE, VMRECORD_TAG_LEN);
else
return -1;
return 0;
}
int vmrecord_open_mark(struct vmrecord_t *vmrecord, const char *vmkey,
size_t klen, enum vmrecord_mark_t type)
{
int ret;
if (!vmrecord || !vmkey || !klen)
return -1;
pthread_mutex_lock(&vmrecord->mtx);
vmrecord->recos = mmap_file(vmrecord->path);
if (!vmrecord->recos) {
LOGE("failed to mmap %s, %s\n", vmrecord->path,
strerror(errno));
ret = -1;
goto unlock;
}
if (!vmrecord->recos->size ||
mm_count_lines(vmrecord->recos) < VMRECORD_HEAD_LINES) {
LOGE("(%s) invalid\n", vmrecord->path);
ret = -1;
goto out;
}
ret = vmrecord_mark(vmrecord, vmkey, klen, type);
out:
unmap_file(vmrecord->recos);
unlock:
pthread_mutex_unlock(&vmrecord->mtx);
return ret;
}
int vmrecord_gen_ifnot_exists(struct vmrecord_t *vmrecord)
{
const char * const head =
"/* DONT EDIT!\n"
" * This file records VM id synced or about to be synched,\n"
" * the tag:\n"
" * \"<==\" indicates event waiting to sync.\n"
" * \"NOT_FOUND\" indicates event not found in UOS.\n"
" * \"MISS_LOGS\" indicates event miss logs in UOS.\n"
" * \"ON_GOING\" indicates event is under syncing.\n"
" * \"NO_RESORC\" indicates no enough resources in SOS.\n"
" */\n\n";
if (!vmrecord) {
LOGE("vmrecord was not initialized\n");
return -1;
}
pthread_mutex_lock(&vmrecord->mtx);
if (!file_exists(vmrecord->path)) {
if (overwrite_file(vmrecord->path, head) < 0) {
pthread_mutex_unlock(&vmrecord->mtx);
LOGE("failed to create file (%s), %s\n",
vmrecord->path, strerror(errno));
return -1;
}
}
pthread_mutex_unlock(&vmrecord->mtx);
return 0;
}
int vmrecord_new(struct vmrecord_t *vmrecord, const char *vm_name,
const char *key)
{
char log_new[64];
int nlen;
if (!vmrecord || !vm_name || !key)
return -1;
nlen = snprintf(log_new, sizeof(log_new), "%s %s %s\n",
vm_name, key, VMRECORD_TAG_WAITING_SYNC);
if (s_not_expect(nlen, sizeof(log_new))) {
LOGE("failed to construct record, key (%s)\n", key);
return -1;
}
pthread_mutex_lock(&vmrecord->mtx);
if (append_file(vmrecord->path, log_new, strnlen(log_new, 64)) < 0) {
pthread_mutex_unlock(&vmrecord->mtx);
LOGE("failed to append file (%s), %s\n", vmrecord->path,
strerror(errno));
return -1;
}
pthread_mutex_unlock(&vmrecord->mtx);
return 0;
}