Files
2026-08-24 10:03:24 +08:00

827 lines
18 KiB
C

/******************************************************************************
*
* Copyright (C) 2021-2021 amlogic 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 LOG_TAG "amlbt_fw"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <asm/ioctl.h>
#include <termios.h>
#include <time.h>
#include <zlib.h>
#include <pthread.h>
#include "amlbt_fwlog.h"
typedef struct {
uint32_t fsize; // Based on 1M multiples
uint32_t data_cnt;
uint32_t cur_data_cnt;
uint32_t files_max_cnt; // default gzip compress file number
uint32_t cur_files_cnt;
char fdir[FWLOG_DIR_MAX_SIZE];
char fpath[FWLOG_PATH_MAX_SIZE];
char fname_prefix[FWLOG_PREFIX_MAX_SIZE];
bool pcap_flag;
} fwlog_info;
// Structure to hold file-related information
typedef struct {
DIR* dir; // Directory pointer
char **files; // Array of file paths
int files_cnt; // Number of files
} files_info;
static fwlog_info fwlog_bt ={
.fsize = 100,
.data_cnt = 0,
.cur_data_cnt = 0,
.files_max_cnt = 10,
.cur_files_cnt = 1,
.fdir = DEFAULT_LOG_DIR,
.fpath = DEFAULT_FWLOG_PATH,
.fname_prefix = DEFAULT_LOG_FILE "_",
.pcap_flag = false,
};
static const int INVALID_FD = -1;
static int fwlog_fd = INVALID_FD;
static int32_t flag_gzip_start = 0;
// Add mutex definition
static pthread_mutex_t gzip_start_mutex = PTHREAD_MUTEX_INITIALIZER;
// Add Condition Variable Definition
static pthread_cond_t gzip_start_cond = PTHREAD_COND_INITIALIZER;
pthread_t gzip_thread;
static void get_time_stamp(char *buffer, size_t size)
{
time_t rawTime;
struct tm timeInfo;
// Get the current time
time(&rawTime);
// Convert to local time in a thread-safe way
if (localtime_r(&rawTime, &timeInfo) == NULL) {
// Clear the buffer if conversion fails
if (size > 0) {
buffer[0] = '\0';
}
return;
}
// Format the time as "YYYYMMDDHHMMSS"
strftime(buffer, size, "%Y%m%d%H%M%S", &timeInfo);
}
static int mkdir_p(const char *path)
{
char tmp[256];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp), "%s", path);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = 0;
if (mkdir(tmp, 0755) != 0) {
if (errno != EEXIST) {
ALOGE("mkdir:%s fail1 %s\n", tmp, strerror(errno));
return -1;
}
}
*p = '/';
}
}
if (mkdir(tmp, 0755) != 0) {
if (errno != EEXIST) {
ALOGE("mkdir:%s fail2 %s\n", tmp, strerror(errno));
return -1;
}
}
return 0;
}
static int fwlog_get_file_path(const char *file_path, char *fdir, size_t fdir_len,
char *fname, size_t fname_len)
{
if (!file_path || (!*file_path)) {
ALOGE("Invalid fwlog path");
return -1;
}
int ret = snprintf(fdir, fdir_len, "%s", file_path);
if (ret < 0 || (size_t)ret >= fdir_len) {
ALOGE("Out buffer too small");
return -1;
}
char *last_slash = strrchr(fdir, '/');
if (!last_slash) {
ALOGE("Invalid path, no slash");
fdir[0] = '\0';
return -1;
}
if (*(last_slash + 1) != '\0') {
snprintf(fname, fname_len, "%s", last_slash + 1);
} else {
snprintf(fname, fname_len, "%s", DEFAULT_LOG_FILE);
}
*(last_slash + 1) = '\0';
return 0;
}
int fwlog_set_path(const char *new_path)
{
int ret;
char tmp_dir[FWLOG_DIR_MAX_SIZE];
char tmp_file[FWLOG_NAME_MAX_SIZE];
if (!new_path) {
ALOGE("Invalid parameter");
return -1;
}
ret = fwlog_get_file_path(new_path, tmp_dir, sizeof(tmp_dir), tmp_file, sizeof(tmp_file));
if (ret) {
ALOGE("Invalid path");
return -1;
}
strcpy(fwlog_bt.fdir,tmp_dir);
ret = snprintf(fwlog_bt.fpath, sizeof(fwlog_bt.fpath), "%s%s", fwlog_bt.fdir, tmp_file);
if (ret < 0 || (size_t)ret >= sizeof(fwlog_bt.fpath)) {
ALOGE("fpath too long");
return -1;
}
ret = snprintf(fwlog_bt.fname_prefix, sizeof(fwlog_bt.fname_prefix), "%s_", tmp_file);
if (ret < 0 || (size_t)ret >= sizeof(fwlog_bt.fname_prefix)) {
ALOGE("fname_prefix too long");
return -1;
}
ALOGI("fdir:%s fpath:%s fname_prefix:%s",fwlog_bt.fdir, fwlog_bt.fpath, \
fwlog_bt.fname_prefix);
fwlog_pcap_flag_set(true);
return 0;
}
static void fwlog_write_hex_data(int fd, const uint8_t *data,
size_t data_len, size_t start_idx)
{
if (fd < 0 || data == NULL || start_idx >= data_len)
return;
struct timeval tv;
struct tm tm;
char header[64];
char line[512];
size_t pos = 0;
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm);
strftime(header, sizeof(header), "%Y-%m-%d %H:%M:%S", &tm);
pos += snprintf(line + pos, sizeof(line) - pos, "%s.%06ld ",
header, (long)tv.tv_usec);
for (size_t i = start_idx; i < data_len && pos + 3 < sizeof(line); i++)
pos += snprintf(line + pos, sizeof(line) - pos, "%02x ", data[i]);
dprintf(fd, "%s", line);
}
static int fwlog_compare_file_num(const char* file1, const char* file2)
{
char number1[FWLOG_NAME_MAX_SIZE] = {0};
char number2[FWLOG_NAME_MAX_SIZE] = {0};
sscanf(file1 + strlen(fwlog_bt.fname_prefix), "%s", number1);
sscanf(file2 + strlen(fwlog_bt.fname_prefix), "%s", number2);
long long num1 = atoll(number1);
long long num2 = atoll(number2);
return (num1 < num2) ? -1 : (num1 > num2) ? 1 : 0;
}
static int fwlog_qsort_compare(const void *a, const void *b) {
return fwlog_compare_file_num(*(const char **)a, *(const char **)b);
}
static void fwlog_relog_file(void)
{
DIR* dir = NULL;
struct dirent *entry;
int maxNum = -1;
char rename_fpath[FWLOG_PATH_MAX_SIZE];
dir = opendir(fwlog_bt.fdir);
if (!dir) {
ALOGE("opendir:%s fail %s\n", fwlog_bt.fdir, strerror(errno));
return;
}
while ((entry = readdir(dir)) != NULL) {
const char *filename = entry->d_name;
if (strncmp(filename, fwlog_bt.fname_prefix, strlen(fwlog_bt.fname_prefix)) != 0) {
continue;
}
const char *numPart = filename + strlen(fwlog_bt.fname_prefix);
size_t len = strlen(numPart);
const size_t gz_len = strlen(".gz");
if (len <= gz_len) {
continue;
}
if (strcmp(numPart + len - gz_len, ".gz") != 0) {
continue;
}
if (len - gz_len >= FWLOG_NAME_MAX_SIZE) {
ALOGE("Filename too long: %s", filename);
continue;
}
char numStr[FWLOG_NAME_MAX_SIZE];
strncpy(numStr, numPart, len - gz_len);
numStr[len - gz_len] = '\0';
int num = 0;
if (sscanf(numStr, "%d", &num) == 1) {
if (num > maxNum) {
ALOGD("maxNum: %d", maxNum);
maxNum = num;
}
}
}
closedir(dir);
if (maxNum > 0) {
fwlog_bt.cur_files_cnt = maxNum + 1;
} else {
fwlog_bt.cur_files_cnt = 1;
}
if (fwlog_fd != INVALID_FD) {
close(fwlog_fd);
fwlog_fd = INVALID_FD;
}
snprintf(rename_fpath, sizeof(rename_fpath), "%s_%u", fwlog_bt.fpath, fwlog_bt.cur_files_cnt);
ALOGI("cur_files_cnt:%u rename_fpath: %s", fwlog_bt.cur_files_cnt, rename_fpath);
if (rename(fwlog_bt.fpath, rename_fpath) != 0) {
if (errno != ENOENT) {
ALOGE("rename to %s fail, errno: %s", rename_fpath, strerror(errno));
}
}
mode_t prev_mask = umask(0);
fwlog_fd = open(fwlog_bt.fpath, O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
umask(prev_mask);
if (fwlog_fd == INVALID_FD) {
ALOGE("open :%s fail", fwlog_bt.fpath);
}
}
static void fwlog_update_file(void)
{
files_info res = {NULL, NULL, 0};
fwlog_relog_file();
res.dir = opendir(fwlog_bt.fdir);
if (!res.dir) {
ALOGE("opendir:%s fail %s\n", fwlog_bt.fdir, strerror(errno));
return;
}
struct dirent* entry;
while ((entry = readdir(res.dir)) != NULL) {
if (strncmp(entry->d_name, fwlog_bt.fname_prefix,
strlen(fwlog_bt.fname_prefix)) == 0) {
char **tmp = realloc(res.files, (res.files_cnt + 1) * sizeof(char *));
if (!tmp) {
ALOGE("realloc failed");
// Free already allocated memory
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]);
}
free(res.files);
res.files = NULL;
break;
}
res.files = tmp;
res.files[res.files_cnt] = strdup(entry->d_name);
if (!res.files[res.files_cnt]) {
ALOGE("strdup failed");
// Free already allocated memory
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]);
}
free(res.files);
res.files = NULL;
res.files_cnt = 0;
break;
}
res.files_cnt++;
}
}
closedir(res.dir);
res.dir = NULL;
ALOGI("files_max_cnt:%u files_cnt:%d", fwlog_bt.files_max_cnt, res.files_cnt);
if (res.files_cnt > fwlog_bt.files_max_cnt) {
int deleteCnt = res.files_cnt - fwlog_bt.files_max_cnt;
qsort(res.files, res.files_cnt, sizeof(char *), fwlog_qsort_compare);
for (int i = 0; i < deleteCnt; i++) {
char oldestFile[FWLOG_PATH_MAX_SIZE];
snprintf(oldestFile, sizeof(oldestFile), "%s/%s", fwlog_bt.fdir, res.files[i]);
remove(oldestFile);
ALOGI("Removing %s", oldestFile);
free(res.files[i]); // Released
}
// Use memmove to move the back forward
memmove(res.files, res.files + deleteCnt, (res.files_cnt - deleteCnt) * sizeof(char *));
res.files_cnt -= deleteCnt;
}
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]); // The remaining release
}
free(res.files);
pthread_mutex_lock(&gzip_start_mutex);
flag_gzip_start = 1;
pthread_cond_signal(&gzip_start_cond);
pthread_mutex_unlock(&gzip_start_mutex);
}
static int fwlog_gzip_rename(void)
{
files_info res = {NULL, NULL, 0};
int ret = -1;
char tmp_path[FWLOG_PATH_MAX_SIZE];
res.dir = opendir(fwlog_bt.fdir);
if (!res.dir) {
ALOGE("opendir:%s fail %s\n", fwlog_bt.fdir, strerror(errno));
return ret;
}
struct dirent *entry;
while ((entry = readdir(res.dir)) != NULL) {
if (strncmp(entry->d_name, fwlog_bt.fname_prefix, strlen(fwlog_bt.fname_prefix)) == 0) {
char **tmp = realloc(res.files, (res.files_cnt + 1) * sizeof(char *));
if (!tmp) {
ALOGE("realloc failed");
goto cleanup;
}
res.files = tmp;
res.files[res.files_cnt] = strdup(entry->d_name);
if (!res.files[res.files_cnt]) {
ALOGE("strdup failed");
goto cleanup;
}
res.files_cnt++;
}
}
closedir(res.dir);
res.dir = NULL;
ALOGI("files_cnt: %d", res.files_cnt);
if (res.files_cnt == 0) {
ret = 0;
goto cleanup;
}
qsort(res.files, res.files_cnt, sizeof(char *), fwlog_qsort_compare);
// Step 1: rename old files to tmp
for (int i = 0; i < res.files_cnt; i++) {
char old_path[FWLOG_PATH_MAX_SIZE];
snprintf(old_path, sizeof(old_path), "%s%s", fwlog_bt.fdir, res.files[i]);
snprintf(tmp_path, sizeof(tmp_path), "%s%s.tmp", fwlog_bt.fdir, res.files[i]);
if (rename(old_path, tmp_path) != 0) {
ALOGE("Rename %s to %s fail, errno: %s", old_path, tmp_path, strerror(errno));
goto cleanup;
}
char *new_name = strdup(tmp_path);
if (!new_name) {
ALOGE("strdup failed for tmp_path");
goto cleanup;
}
free(res.files[i]);
res.files[i] = new_name;
}
// Step 2: rename tmp to new name with index
for (int i = 0; i < res.files_cnt; i++) {
snprintf(tmp_path, sizeof(tmp_path), "%s%s%d.gz",
fwlog_bt.fdir, fwlog_bt.fname_prefix, i + 1);
if (rename(res.files[i], tmp_path) != 0) {
ALOGE("Rename %s to %s fail, errno: %s", res.files[i], tmp_path, strerror(errno));
goto cleanup;
}
ALOGI("Renamed to %s", tmp_path);
}
ret = 0;
cleanup:
if (res.dir)
closedir(res.dir);
if (res.files) {
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]);
}
free(res.files);
}
return ret;
}
static int fwlog_gzip_compress(const char* input_path, const char* output_path)
{
FILE* source = NULL;
gzFile dest = NULL;
unsigned char buffer[4096] = {0};
int num_read = 0, num_write = 0;
mode_t prev_mask;
int ret = 0;
prev_mask = umask(002);
ALOGI("start compress: %s -> %s", input_path, output_path);
source = fopen(input_path, "rb");
if (!source) {
ALOGE("fopen: %s fail: %s", input_path, strerror(errno));
umask(prev_mask);
return -1;
}
dest = gzopen(output_path, "wb");
if (!dest) {
ALOGE("gzopen: %s fail: %s", output_path, strerror(errno));
fclose(source);
umask(prev_mask);
return -1;
}
umask(prev_mask);
while ((num_read = fread(buffer, 1, sizeof(buffer), source)) > 0) {
num_write = gzwrite(dest, buffer, num_read);
if (num_write == 0) {
int errnum = 0;
const char* msg = gzerror(dest, &errnum);
ALOGE("gzwrite failed: %s", msg);
ret = -1;
break;
}
}
if (ferror(source)) {
ALOGE("fread error: %s", strerror(errno));
ret = -1;
}
fclose(source);
gzclose(dest);
ALOGI("finish compress: %s", ret == 0 ? "OK" : "FAIL");
return ret;
}
// Thread cleanup function, used to release resources when the thread is canceled
static void fwlog_thread_cleanup(void *arg)
{
files_info *res = (files_info *)arg;
// Close directory
if (res->dir) {
closedir(res->dir);
res->dir = NULL;
}
// Free log files
if (res->files) {
for (int i = 0; i < res->files_cnt; i++) {
free(res->files[i]);
}
free(res->files);
res->files = NULL;
res->files_cnt = 0;
}
// Release mutex to prevent deadlock
if (pthread_mutex_trylock(&gzip_start_mutex) == 0) {
pthread_mutex_unlock(&gzip_start_mutex);
}
ALOGI("thread cleanup done");
}
static void *fwlog_gzip_compress_thread(void *arg)
{
(void)arg;
struct dirent *entry;
files_info res = {NULL, NULL, 0};
// Set thread cleanup function
pthread_cleanup_push(fwlog_thread_cleanup, (void*)&res);
ALOGI("start");
while (1) {
pthread_mutex_lock(&gzip_start_mutex);
while (!flag_gzip_start) {
pthread_cond_wait(&gzip_start_cond, &gzip_start_mutex);
}
pthread_mutex_unlock(&gzip_start_mutex);
ALOGI("gzip compress files start");
// Free previous log files
if (res.files) {
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]);
}
free(res.files);
res.files = NULL;
res.files_cnt = 0;
}
res.dir = opendir(fwlog_bt.fdir);
if (!res.dir) {
ALOGE("opendir failed");
goto compress_exit;
}
while ((entry = readdir(res.dir)) != NULL) {
size_t len = strlen(entry->d_name);
if (strncmp(entry->d_name, fwlog_bt.fname_prefix,
strlen(fwlog_bt.fname_prefix)) == 0 &&
!(len >= 3 && strcmp(entry->d_name + len - 3, ".gz") == 0)) {
char **tmp = realloc(res.files, (res.files_cnt + 1) * sizeof(char *));
if (!tmp) {
ALOGE("realloc failed");
goto compress_exit;
}
res.files = tmp;
res.files[res.files_cnt] = strdup(entry->d_name);
if (!res.files[res.files_cnt]) {
ALOGE("strdup failed");
goto compress_exit;
}
res.files_cnt++;
}
}
closedir(res.dir);
res.dir = NULL;
for (int i = 0; i < res.files_cnt; ) {
char Filename[FWLOG_PATH_MAX_SIZE];
char gFilename[FWLOG_PATH_MAX_SIZE];
snprintf(Filename, sizeof(Filename), "%s%s%s", fwlog_bt.fdir,
(fwlog_bt.fdir[strlen(fwlog_bt.fdir) - 1] == '/') ? "" : "/", res.files[i]);
snprintf(gFilename, sizeof(gFilename), "%s.gz", Filename);
ALOGI("Compress in: %s, out: %s", Filename, gFilename);
if (!fwlog_gzip_compress(Filename, gFilename)) {
if (remove(Filename) == 0) {
free(res.files[i]);
memmove(res.files + i, res.files + i + 1, (res.files_cnt - i - 1) * sizeof(char *));
res.files_cnt--;
} else {
ALOGE("Remove fail: %s", Filename);
i++;
}
} else {
ALOGE("Compress fail: %s", Filename);
remove(gFilename);
i++;
}
}
fwlog_gzip_rename();
compress_exit:
if (res.dir) {
closedir(res.dir);
res.dir = NULL;
}
if (res.files) {
for (int i = 0; i < res.files_cnt; i++) {
free(res.files[i]);
}
free(res.files);
res.files = NULL;
res.files_cnt = 0;
}
pthread_mutex_lock(&gzip_start_mutex);
flag_gzip_start = 0;
pthread_mutex_unlock(&gzip_start_mutex);
sleep(1);
}
ALOGI("finish");
// Pop-up cleanup function
pthread_cleanup_pop(1);
return NULL;
}
void fwlog_set_size(uint16_t val)
{
fwlog_bt.fsize = val;
}
void fwlog_set_cnt(uint16_t val)
{
fwlog_bt.files_max_cnt = val;
}
void fwlog_pcap_flag_set(bool val)
{
fwlog_bt.pcap_flag = val;
}
bool fwlog_pcap_flag_get(void)
{
return fwlog_bt.pcap_flag;
}
void fwlog_init(void)
{
bool pcap_flag = fwlog_pcap_flag_get();
if (!pcap_flag) {
ALOGI("flag=%d, exit", pcap_flag);
return;
}
if (mkdir_p(fwlog_bt.fdir)) {
ALOGE("mkdir_p fail");
return;
}
ALOGI("fsize:%u data_cnt:%u files_max_cnt:%u cur_data_cnt:%u", fwlog_bt.fsize, \
fwlog_bt.data_cnt, fwlog_bt.files_max_cnt, fwlog_bt.cur_data_cnt);
if (pthread_create(&gzip_thread, NULL, fwlog_gzip_compress_thread, NULL) != 0) {
ALOGE("gzip_thread pthread_create fail");
} else {
//pthread_setname_np(gzip_thread, "bt_fwlog_gzip");
prctl(PR_SET_NAME, "bt_fwlog_gzip", 0,0,0);
}
fwlog_update_file();
}
void fwlog_close (void)
{
bool pcap_flag = fwlog_pcap_flag_get();
if (!pcap_flag) {
ALOGI("flag=%d, exit", pcap_flag);
return;
} else {
ALOGI();
}
if (fwlog_fd != INVALID_FD) {
close(fwlog_fd);
fwlog_fd = INVALID_FD;
}
// Cancel gzip thread
if (pthread_cancel(gzip_thread) != 0) {
ALOGE("pthread_cancel failed");
} else {
// Wait for thread to finish
if (pthread_join(gzip_thread, NULL) != 0) {
ALOGE("pthread_join failed");
} else {
ALOGI("gzip_thread exit successful");
}
}
// Destroy mutex and condition variable
pthread_mutex_destroy(&gzip_start_mutex);
pthread_cond_destroy(&gzip_start_cond);
}
void fwlog_handle_signal(int sig)
{
ALOGD();
fwlog_close();
exit(0);
}
void fwlog_record(const uint8_t* data, uint32_t data_len)
{
if (!fwlog_pcap_flag_get()) {
return;
}
if (fwlog_fd == INVALID_FD) {
ALOGE("invalid fwlog_fd");
return;
}
uint32_t one_pkg_size = 0;
// Need to remove three bytes, event code + command code + data len
fwlog_write_hex_data(fwlog_fd, data, (data_len - 2), 2);
fwlog_bt.cur_data_cnt++;
ALOGD("fsize:%u", fwlog_bt.fsize);
one_pkg_size = (data_len - 2) * 3 + 27; // 27:time tag len
fwlog_bt.data_cnt = (fwlog_bt.fsize * 1024 * 1024) / one_pkg_size;
ALOGD("one_pkg_size:%u data_cnt:%u cur_data_cnt:%u", one_pkg_size, \
fwlog_bt.data_cnt, fwlog_bt.cur_data_cnt);
if (fwlog_bt.cur_data_cnt >= fwlog_bt.data_cnt) {
fwlog_update_file();
fwlog_bt.cur_data_cnt = 0;
}
}