mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-09-08 12:19:06 +00:00
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:
83
misc/tools/acrn-crashlog/acrnprobe/Makefile
Normal file
83
misc/tools/acrn-crashlog/acrnprobe/Makefile
Normal 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
|
193
misc/tools/acrn-crashlog/acrnprobe/README.rst
Normal file
193
misc/tools/acrn-crashlog/acrnprobe/README.rst
Normal 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
|
494
misc/tools/acrn-crashlog/acrnprobe/android_events.c
Normal file
494
misc/tools/acrn-crashlog/acrnprobe/android_events.c
Normal 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;
|
||||
}
|
582
misc/tools/acrn-crashlog/acrnprobe/channels.c
Normal file
582
misc/tools/acrn-crashlog/acrnprobe/channels.c
Normal 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;
|
||||
}
|
374
misc/tools/acrn-crashlog/acrnprobe/conf.rst
Normal file
374
misc/tools/acrn-crashlog/acrnprobe/conf.rst
Normal 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
|
371
misc/tools/acrn-crashlog/acrnprobe/crash_reclassify.c
Normal file
371
misc/tools/acrn-crashlog/acrnprobe/crash_reclassify.c
Normal 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;
|
||||
}
|
||||
}
|
212
misc/tools/acrn-crashlog/acrnprobe/event_handler.c
Normal file
212
misc/tools/acrn-crashlog/acrnprobe/event_handler.c
Normal 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;
|
||||
}
|
78
misc/tools/acrn-crashlog/acrnprobe/event_queue.c
Normal file
78
misc/tools/acrn-crashlog/acrnprobe/event_queue.c
Normal 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);
|
||||
}
|
450
misc/tools/acrn-crashlog/acrnprobe/history.c
Normal file
450
misc/tools/acrn-crashlog/acrnprobe/history.c
Normal 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;
|
||||
}
|
21
misc/tools/acrn-crashlog/acrnprobe/images/crash-config.dot
Normal file
21
misc/tools/acrn-crashlog/acrnprobe/images/crash-config.dot
Normal 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" ];
|
||||
}
|
26
misc/tools/acrn-crashlog/acrnprobe/images/crash-match.dot
Normal file
26
misc/tools/acrn-crashlog/acrnprobe/images/crash-match.dot
Normal 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
|
||||
}
|
||||
|
46
misc/tools/acrn-crashlog/acrnprobe/include/android_events.h
Normal file
46
misc/tools/acrn-crashlog/acrnprobe/include/android_events.h
Normal 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
|
24
misc/tools/acrn-crashlog/acrnprobe/include/channels.h
Normal file
24
misc/tools/acrn-crashlog/acrnprobe/include/channels.h
Normal 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
|
@@ -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);
|
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
extern int init_event_handler(void);
|
44
misc/tools/acrn-crashlog/acrnprobe/include/event_queue.h
Normal file
44
misc/tools/acrn-crashlog/acrnprobe/include/event_queue.h
Normal 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
|
35
misc/tools/acrn-crashlog/acrnprobe/include/history.h
Normal file
35
misc/tools/acrn-crashlog/acrnprobe/include/history.h
Normal 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
|
257
misc/tools/acrn-crashlog/acrnprobe/include/load_conf.h
Normal file
257
misc/tools/acrn-crashlog/acrnprobe/include/load_conf.h
Normal 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
|
19
misc/tools/acrn-crashlog/acrnprobe/include/loop.h
Normal file
19
misc/tools/acrn-crashlog/acrnprobe/include/loop.h
Normal 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);
|
54
misc/tools/acrn-crashlog/acrnprobe/include/probeutils.h
Normal file
54
misc/tools/acrn-crashlog/acrnprobe/include/probeutils.h
Normal 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
|
38
misc/tools/acrn-crashlog/acrnprobe/include/property.h
Normal file
38
misc/tools/acrn-crashlog/acrnprobe/include/property.h
Normal 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
|
6
misc/tools/acrn-crashlog/acrnprobe/include/sender.h
Normal file
6
misc/tools/acrn-crashlog/acrnprobe/include/sender.h
Normal file
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
extern int init_sender(void);
|
24
misc/tools/acrn-crashlog/acrnprobe/include/startupreason.h
Normal file
24
misc/tools/acrn-crashlog/acrnprobe/include/startupreason.h
Normal 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);
|
45
misc/tools/acrn-crashlog/acrnprobe/include/vmrecord.h
Normal file
45
misc/tools/acrn-crashlog/acrnprobe/include/vmrecord.h
Normal 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
|
773
misc/tools/acrn-crashlog/acrnprobe/load_conf.c
Normal file
773
misc/tools/acrn-crashlog/acrnprobe/load_conf.c
Normal 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;
|
||||
}
|
645
misc/tools/acrn-crashlog/acrnprobe/loop.c
Normal file
645
misc/tools/acrn-crashlog/acrnprobe/loop.c
Normal 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);
|
||||
}
|
139
misc/tools/acrn-crashlog/acrnprobe/main.c
Normal file
139
misc/tools/acrn-crashlog/acrnprobe/main.c
Normal 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;
|
||||
}
|
443
misc/tools/acrn-crashlog/acrnprobe/probeutils.c
Normal file
443
misc/tools/acrn-crashlog/acrnprobe/probeutils.c
Normal 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, ¤t))
|
||||
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;
|
||||
}
|
132
misc/tools/acrn-crashlog/acrnprobe/property.c
Normal file
132
misc/tools/acrn-crashlog/acrnprobe/property.c
Normal 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;
|
||||
}
|
1122
misc/tools/acrn-crashlog/acrnprobe/sender.c
Normal file
1122
misc/tools/acrn-crashlog/acrnprobe/sender.c
Normal file
File diff suppressed because it is too large
Load Diff
122
misc/tools/acrn-crashlog/acrnprobe/startupreason.c
Normal file
122
misc/tools/acrn-crashlog/acrnprobe/startupreason.c
Normal 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;
|
||||
}
|
171
misc/tools/acrn-crashlog/acrnprobe/vmrecord.c
Normal file
171
misc/tools/acrn-crashlog/acrnprobe/vmrecord.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user