/****************************************************************************** * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } }