/*
- Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2024-2024. All rights reserved.
- Description: Log storage manager implementation based on littlefs. */
#include "log_manager.h" #include "littlefs_adapt.h" #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <stdarg.h> #include <sys/stat.h>
// ==================== Static Variables ====================
static log_manager_t g_log_mgr = {0};
// ==================== Static Helper Functions ====================
/**
-
@brief Generate timestamp-based filename with format: YYYYMMDD_HHMMSS
-
Uses local time, caller should ensure time is set correctly. */ static void get_timestamp_string(char *buf, size_t len) { if (buf == NULL || len < 16) { return; }
time_t now = time(NULL); struct tm *timeinfo = localtime(&now);
snprintf(buf, len, "%04d%02d%02d_%02d%02d%02d", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); }
/**
- @brief Check if file should be rotated (exceeded size limit) */ static int should_rotate_file(unsigned int current_size) { return current_size >= LOG_MAX_FILE_SIZE; }
/**
-
@brief Generate unique filename with timestamp and sequence */ static void generate_unique_filename(char *filename, size_t max_len) { char timestamp[32]; get_timestamp_string(timestamp, sizeof(timestamp));
// Primary filename format: /logs/YYYYMMDD_HHMMSS.log snprintf(filename, max_len, "%s/%s.log", LOG_DIR_PATH, timestamp); }
/**
-
@brief Scan log directory and build file list */ static int scan_log_directory(void) { // Note: littlefs_adapt doesn't provide directory listing API // This is a placeholder for when directory API is added // For now, files are tracked in memory during runtime
g_log_mgr.file_count = 0; memset(g_log_mgr.file_list, 0, sizeof(g_log_mgr.file_list));
return 0; }
/**
-
@brief Update file metadata cache */ static int update_file_metadata(const char *filename) { if (!filename || strlen(filename) == 0) { return -1; }
unsigned int size = 0;
// Get file size if (fs_adapt_stat(filename, &size) != 0) { return -1; }
// Add to file list if not already present and within limit if (g_log_mgr.file_count < LOG_MAX_FILES) { strncpy(g_log_mgr.file_list[g_log_mgr.file_count].filename, filename, LOG_MAX_FILENAME_SIZE - 1); g_log_mgr.file_list[g_log_mgr.file_count].size = size; g_log_mgr.file_list[g_log_mgr.file_count].last_modified = time(NULL); g_log_mgr.file_count++; }
return 0; }
/**
-
@brief Rotate to new log file when current exceeds limit */ static int rotate_log_file(void) { // Close current file if open if (g_log_mgr.current_fd >= 0) { fs_adapt_sync(g_log_mgr.current_fd); fs_adapt_close(g_log_mgr.current_fd); g_log_mgr.current_fd = -1; }
// Generate new filename generate_unique_filename(g_log_mgr.current_filename, LOG_MAX_FILENAME_SIZE);
// Open new file g_log_mgr.current_fd = fs_adapt_open(g_log_mgr.current_filename, O_RDWR | O_CREAT | O_TRUNC); if (g_log_mgr.current_fd < 0) { return -1; }
g_log_mgr.current_file_size = 0;
// Update file metadata update_file_metadata(g_log_mgr.current_filename);
return 0; }
// ==================== Public API Implementation ====================
int log_manager_init(void) { // Mount littlefs fs_adapt_mount();
// Create log directory
int ret = fs_adapt_mkdir(LOG_DIR_PATH);
if (ret < 0 && ret != LFS_ERR_EXIST) {
return -1;
}
// Initialize manager structure
g_log_mgr.initialized = 1;
g_log_mgr.current_fd = -1;
g_log_mgr.current_file_size = 0;
g_log_mgr.file_count = 0;
// Scan existing files
scan_log_directory();
// Create initial log file
if (rotate_log_file() != 0) {
g_log_mgr.initialized = 0;
return -1;
}
return 0;
}
int log_manager_cleanup(void) { if (!g_log_mgr.initialized) { return -1; }
// Close current file
if (g_log_mgr.current_fd >= 0) {
fs_adapt_sync(g_log_mgr.current_fd);
fs_adapt_close(g_log_mgr.current_fd);
g_log_mgr.current_fd = -1;
}
// Unmount filesystem
fs_adapt_unmount();
g_log_mgr.initialized = 0;
return 0;
}
char* log_manager_generate_filename(char *filename, size_t max_len) { if (filename == NULL || max_len < 32) { return NULL; }
generate_unique_filename(filename, max_len);
return filename;
}
int log_manager_write(const char *log_message) { if (!g_log_mgr.initialized || !log_message) { return -1; }
unsigned int msg_len = strlen(log_message);
// Check if current file needs rotation
if (should_rotate_file(g_log_mgr.current_file_size + msg_len + 1)) {
if (rotate_log_file() != 0) {
return -1;
}
}
// Write the log message
int ret = fs_adapt_write(g_log_mgr.current_fd, log_message, msg_len);
if (ret <= 0) {
return -1;
}
// Write newline
ret = fs_adapt_write(g_log_mgr.current_fd, "\n", 1);
if (ret <= 0) {
return -1;
}
// Sync to storage
fs_adapt_sync(g_log_mgr.current_fd);
// Update file size
g_log_mgr.current_file_size += msg_len + 1;
return msg_len + 1;
}
int log_manager_printf(const char *format, ...) { if (!g_log_mgr.initialized || !format) { return -1; }
char buffer[512];
va_list args;
va_start(args, format);
int ret = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (ret < 0) {
return -1;
}
return log_manager_write(buffer);
}
int log_manager_write_level(const char *level, const char *message) { if (!level || !message) { return -1; }
char buffer[512];
time_t now = time(NULL);
struct tm *timeinfo = localtime(&now);
snprintf(buffer, sizeof(buffer),
"[%02d:%02d:%02d] [%-5s] %s",
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec,
level,
message);
return log_manager_write(buffer);
}
int log_manager_read(const char *filename, char *buffer, size_t max_len) { if (!filename || !buffer || max_len == 0) { return -1; }
char filepath[LOG_MAX_FILENAME_SIZE];
snprintf(filepath, sizeof(filepath), "%s/%s", LOG_DIR_PATH, filename);
// Open file in read-only mode
int fd = fs_adapt_open(filepath, O_RDONLY);
if (fd < 0) {
return 0; // File not found
}
// Read file content
int bytes_read = fs_adapt_read(fd, buffer, max_len - 1);
// Close file
fs_adapt_close(fd);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
return bytes_read;
}
return bytes_read;
}
int log_manager_read_offset(const char *filename, char *buffer, size_t max_len, unsigned int offset) { if (!filename || !buffer || max_len == 0) { return -1; }
char filepath[LOG_MAX_FILENAME_SIZE];
snprintf(filepath, sizeof(filepath), "%s/%s", LOG_DIR_PATH, filename);
// Open file
int fd = fs_adapt_open(filepath, O_RDONLY);
if (fd < 0) {
return 0;
}
// Seek to offset
int ret = fs_adapt_seek(fd, (int)offset, LFS_SEEK_SET);
if (ret < 0) {
fs_adapt_close(fd);
return -1;
}
// Read from offset
int bytes_read = fs_adapt_read(fd, buffer, max_len - 1);
// Close file
fs_adapt_close(fd);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
return bytes_read;
}
return bytes_read;
}
int log_manager_get_file_size(const char *filename, unsigned int *file_size) { if (!filename || !file_size) { return -1; }
char filepath[LOG_MAX_FILENAME_SIZE];
snprintf(filepath, sizeof(filepath), "%s/%s", LOG_DIR_PATH, filename);
return fs_adapt_stat(filepath, file_size);
}
int log_manager_delete_file(const char *filename) { if (!filename) { return -1; }
char filepath[LOG_MAX_FILENAME_SIZE];
snprintf(filepath, sizeof(filepath), "%s/%s", LOG_DIR_PATH, filename);
int ret = fs_adapt_delete(filepath);
if (ret == 0) {
// Remove from file list
for (int i = 0; i < g_log_mgr.file_count; i++) {
if (strcmp(g_log_mgr.file_list[i].filename, filename) == 0) {
// Shift remaining items
for (int j = i; j < g_log_mgr.file_count - 1; j++) {
memcpy(&g_log_mgr.file_list[j],
&g_log_mgr.file_list[j + 1],
sizeof(log_file_info_t));
}
g_log_mgr.file_count--;
break;
}
}
}
return ret == 0 ? 0 : -1;
}
int log_manager_list_files(log_file_info_t *files, int max_count, int *actual_count) { if (!files || max_count <= 0) { return -1; }
int count = g_log_mgr.file_count < max_count ? g_log_mgr.file_count : max_count;
memcpy(files, g_log_mgr.file_list, count * sizeof(log_file_info_t));
if (actual_count != NULL) {
*actual_count = count;
}
return 0;
}
int log_manager_cleanup_old_files(int max_files) { if (max_files <= 0 || g_log_mgr.file_count <= max_files) { return 0; }
int files_to_delete = g_log_mgr.file_count - max_files;
int deleted = 0;
// Simple approach: delete oldest files by index
for (int i = 0; i < files_to_delete && i < g_log_mgr.file_count; i++) {
if (log_manager_delete_file(g_log_mgr.file_list[i].filename) == 0) {
deleted++;
}
}
return deleted;
}
int log_manager_get_total_size(unsigned int *total_size) { if (!total_size) { return -1; }
*total_size = 0;
for (int i = 0; i < g_log_mgr.file_count; i++) {
*total_size += g_log_mgr.file_list[i].size;
}
return 0;
}
int log_manager_get_current_file_info(log_file_info_t *current_file) { if (!current_file) { return -1; }
if (strlen(g_log_mgr.current_filename) == 0) {
return -1;
}
strncpy(current_file->filename, g_log_mgr.current_filename, LOG_MAX_FILENAME_SIZE - 1);
current_file->size = g_log_mgr.current_file_size;
current_file->last_modified = time(NULL);
return 0;
}
int log_manager_clear_all(void) { // Delete all known log files int deleted = 0; int original_count = g_log_mgr.file_count;
for (int i = 0; i < original_count; i++) {
if (log_manager_delete_file(g_log_mgr.file_list[0].filename) == 0) {
deleted++;
}
}
// Rotate to create new empty log file
if (rotate_log_file() != 0) {
return -1;
}
return deleted;
}
int log_manager_get_flash_info(unsigned int *start_addr, unsigned int *total_size, unsigned int *free_size) { if (!start_addr || !total_size || !free_size) { return -1; }
// Access littlefs block information from littlefs_adapt module
// These are defined in littlefs_adapt.c
// extern lfs_block_info_t g_lfs_block_info
// where g_lfs_block_info.start_block and g_lfs_block_info.max_block are available
// Note: We need to access the global variables from littlefs_adapt.c
// The block size is 4KB (4096 bytes) as defined in littlefs_adapt.c
#define LFS_BLOCK_SIZE 4096
// This is a workaround - we retrieve block info by using partition API
// If partition info is available
partition_information_t part_info;
errcode_t ret = uapi_partition_get_info(CONFIG_LFS_PARTITION_ID, &part_info);
if (ret != ERRCODE_SUCC) {
return -1;
}
// Get partition address and size
*start_addr = part_info.part_info.addr_info.addr;
*total_size = part_info.part_info.addr_info.size;
// Calculate free space = total size - used log files size
unsigned int used_size = 0;
if (log_manager_get_total_size(&used_size) != 0) {
used_size = 0; // If we can't get it, assume 0
}
if (*total_size >= used_size) {
*free_size = *total_size - used_size;
} else {
*free_size = 0;
}
return 0;
}