acrn-hypervisor/tools/acrn-crashlog/acrnprobe/android_events.c
Liu, Xinwu 3ffa9686ca tools: acrn-crashlog: fix potential issues
Changes include:
1. check the parameter of snprintf
2. remove atoi
3. remove sscanf
4. fix one memleak

Tracked-On: #1024
Signed-off-by: Liu, Xinwu <xinwu.liu@intel.com>
Reviewed-by: Huang, Yonghua <yonghua.huang@intel.com>
Acked-by: Chen, Gang <gang.c.chen@intel.com>
2018-11-02 11:52:01 +08:00

566 lines
13 KiB
C

/*
* 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"
#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;
}
#define VMRECORD_HEAD_LINES 7
#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_SUCCESS " "
static int generate_log_vmrecord(const char *path)
{
const char * const head =
"/* DONT EDIT!\n"
" * This file records VM id synced or about to be synched,\n"
" * the tag \"<==\" indicates event waiting to sync.\n"
" * the tag \"NOT_FOUND\" indicates event not found in UOS.\n"
" * the tag \"MISS_LOGS\" indicates event miss logs in UOS.\n"
" */\n\n";
LOGD("Generate (%s)\n", path);
return overwrite_file(path, head);
}
enum stage1_refresh_type_t {
MM_ONLY,
MM_FILE
};
/**
* There are 2 stages in vm events sync.
* Stage1: record events to log_vmrecordid file.
* Stage2: call sender's callback for each recorded events.
*
* The design reason is to give UOS some time to log to storage.
*/
static int refresh_key_synced_stage1(const struct sender_t *sender,
struct vm_t *vm, const char *key,
size_t klen,
enum stage1_refresh_type_t type)
{
char log_new[64];
char *log_vmrecordid;
int sid;
int nlen;
sid = sender_id(sender);
if (sid == -1)
return -1;
log_vmrecordid = sender->log_vmrecordid;
/* the length of key must be 20, and its value can not be
* 00000000000000000000.
*/
if ((klen == ANDROID_EVT_KEY_LEN) &&
strcmp(key, "00000000000000000000")) {
memcpy(vm->last_synced_line_key[sid], key, klen);
vm->last_synced_line_key[sid][klen] = '\0';
if (type == MM_ONLY)
return 0;
/* create a log file, so we can locate
* the right place in case of reboot
*/
if (!file_exists(log_vmrecordid))
generate_log_vmrecord(log_vmrecordid);
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;
}
if (append_file(log_vmrecordid, log_new,
strnlen(log_new, 64)) < 0) {
LOGE("failed to add new record (%s) to (%s)\n",
log_new, log_vmrecordid);
return -1;
}
return 0;
}
LOGE("try to record an invalid key (%s) for (%s)\n",
key, vm->name);
return -1;
}
enum stage2_refresh_type_t {
SUCCESS,
NOT_FOUND,
MISS_LOG
};
static int refresh_key_synced_stage2(char *line, size_t len,
enum stage2_refresh_type_t type)
{
/* re-mark symbol "<==" for synced key */
char *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
return -1;
return 0;
}
static int get_vms_history(const struct sender_t *sender)
{
struct vm_t *vm;
unsigned long size;
int ret;
int sid;
int id;
sid = sender_id(sender);
if (sid == -1)
return -1;
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;
}
if (!size) {
LOGE("empty vm_history from (%s).\n", vm->name);
vm->history_data = NULL;
e2fs_close(vm->datafs);
vm->datafs = NULL;
continue;
}
/* warning large history file once */
if (size == vm->history_size[sid])
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[sid] = size;
}
return 0;
}
static void sync_lines_stage1(const struct sender_t *sender)
{
int id, sid;
struct vm_t *vm;
sid = sender_id(sender);
if (sid == -1)
return;
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[sid];
last_key = &vm->last_synced_line_key[sid][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;
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);
start = strchr(line_to_sync, '\n');
continue;
}
LOGD("stage1 %s\n", vmkey);
refresh_key_synced_stage1(sender, vm, vmkey,
strnlen(vmkey, sizeof(vmkey)),
MM_FILE);
start = strchr(line_to_sync, '\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 sync_lines_stage2(const struct sender_t *sender,
int (*fn)(const char*, size_t, const struct vm_t *))
{
struct mm_file_t *recos;
char *record;
size_t recolen;
int sid;
sid = sender_id(sender);
if (sid == -1)
return;
recos = mmap_file(sender->log_vmrecordid);
if (!recos) {
LOGE("mmap %s failed, strerror(%s)\n", sender->log_vmrecordid,
strerror(errno));
return;
}
if (!recos->size ||
mm_count_lines(recos) < VMRECORD_HEAD_LINES) {
LOGE("(%s) invalid\n", sender->log_vmrecordid);
goto out;
}
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[sid],
vm->history_data, &len);
if (!hist_line) {
LOGW("mark vmevent(%s) as not-found\n", vmkey);
refresh_key_synced_stage2(record, recolen, NOT_FOUND);
continue;
}
res = fn(hist_line, len + 1, vm);
if (res == VMEVT_HANDLED)
refresh_key_synced_stage2(record, recolen, SUCCESS);
else if (res == VMEVT_MISSLOG)
refresh_key_synced_stage2(record, recolen, MISS_LOG);
}
out:
unmap_file(recos);
}
/* This function only for initialization */
static void get_last_line_synced(const struct sender_t *sender)
{
int id;
int sid;
struct vm_t *vm;
sid = sender_id(sender);
if (sid == -1)
return;
for_each_vm(id, vm, conf) {
int ret;
char *p;
char vmkey[ANDROID_WORD_LEN];
char vm_name[32];
if (!vm)
continue;
/* generally only exec for each vm once */
if (vm->last_synced_line_key[sid][0])
continue;
ret = snprintf(vm_name, sizeof(vm_name), "%s ", vm->name);
if (s_not_expect(ret, sizeof(vm_name)))
continue;
ret = file_read_key_value_r(vmkey, sizeof(vmkey),
sender->log_vmrecordid,
vm_name, strnlen(vm_name, 32));
if (ret == -ENOENT) {
LOGD("(%s) does not exist, will generate it\n",
sender->log_vmrecordid);
generate_log_vmrecord(sender->log_vmrecordid);
continue;
} else if (ret == -ENOMSG) {
LOGD("couldn't find any records with (%s)\n", vm->name);
continue;
} else if (ret < 0) {
LOGE("failed to search records in (%s), error (%s)\n",
sender->log_vmrecordid, strerror(errno));
continue;
}
p = strchr(vmkey, ' ');
if (p)
*p = 0;
ret = refresh_key_synced_stage1(sender, vm, vmkey,
strnlen(vmkey, sizeof(vmkey)),
MM_ONLY);
if (ret < 0) {
LOGE("invalid vm event (%s) in (%s)\n",
vmkey, sender->log_vmrecordid);
continue;
}
}
}
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();
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(const 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");
}
get_last_line_synced(sender);
get_vms_history(sender);
sync_lines_stage2(sender, fn);
sync_lines_stage1(sender);
for_each_vm(id, vm, conf) {
if (!vm)
continue;
if (vm->history_data) {
free(vm->history_data);
vm->history_data = NULL;
}
if (vm->datafs) {
e2fs_close(vm->datafs);
vm->datafs = NULL;
}
}
}