[阶段0] 从零重写全部模块 — odai-governed 自主实现
道:读懂功能需求(SPEC.md) → 独立编码 → 不复制原工程代码 术:构建系统 → myBase.h → liblist → liblog → libfunc,逐模块验证 法:每个函数源自需求理解 + 自主设计,编译→测试→提交闭环 交付: - myBase.h: 颜色/文件名/大小端/类型枚举(LOCAL/S_STR/DATA_TYPE_*) - liblist: 11个API, C/C++双兼容, 纯头文件 - liblog: 消息池预分配 + pthread_once + 批量消费 + 文件轮转(纯C, 0 malloc运行时) - libfunc: 65个函数7大分类, 全部自主研发实现(校验/字符串/时间/文件/进程/环境/IPC) - 构建: 4层递归makefile, x86/arm切换, gcc/g++混合编译
This commit is contained in:
parent
a353f83393
commit
4ee054e583
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
echo "=== RTU_ALL_AI Build ==="
|
||||
if [ "$1" = "arm" ]; then
|
||||
echo "ARM cross-compile..."
|
||||
make CROSS=arm clean && make CROSS=arm
|
||||
else
|
||||
echo "x86 local build..."
|
||||
make clean && make
|
||||
fi
|
||||
echo "=== Done ==="
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* @file list.h
|
||||
* @brief 内核风格侵入式双向循环链表(纯头文件,C/C++ 兼容)
|
||||
*
|
||||
* 用法:将 struct list_head 嵌入你的结构体,通过 list_entry 取回宿主指针。
|
||||
* 所有操作 O(1),遍历 O(n)。C++ 兼容:不用 new/class/typeof 等关键字。
|
||||
*/
|
||||
|
||||
#ifndef _LIST_H_
|
||||
#define _LIST_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 节点 ---- */
|
||||
struct list_head { struct list_head *next, *prev; };
|
||||
|
||||
/* ---- 初始化 ---- */
|
||||
#define LIST_HEAD_INIT(n) { &(n), &(n) }
|
||||
#define LIST_HEAD(n) struct list_head n = LIST_HEAD_INIT(n)
|
||||
|
||||
static inline void INIT_LIST_HEAD(struct list_head *h)
|
||||
{ h->next = h; h->prev = h; }
|
||||
|
||||
/* ---- 内部:在 prev 和 next 之间插入 node ---- */
|
||||
static inline void __list_add(struct list_head *node,
|
||||
struct list_head *prev,
|
||||
struct list_head *next)
|
||||
{ next->prev = node; node->next = next; node->prev = prev; prev->next = node; }
|
||||
|
||||
/* ---- 内部:删除 prev 和 next 之间的节点 ---- */
|
||||
static inline void __list_del(struct list_head *prev, struct list_head *next)
|
||||
{ next->prev = prev; prev->next = next; }
|
||||
|
||||
/* ---- 添加 ---- */
|
||||
static inline void list_add(struct list_head *node, struct list_head *head)
|
||||
{ __list_add(node, head, head->next); }
|
||||
static inline void list_add_tail(struct list_head *node, struct list_head *head)
|
||||
{ __list_add(node, head->prev, head); }
|
||||
|
||||
/* ---- 删除 ---- */
|
||||
static inline void list_del(struct list_head *e)
|
||||
{ __list_del(e->prev, e->next); }
|
||||
static inline void list_del_init(struct list_head *e)
|
||||
{ __list_del(e->prev, e->next); INIT_LIST_HEAD(e); }
|
||||
|
||||
/* ---- 移动 ---- */
|
||||
static inline void list_move(struct list_head *l, struct list_head *h)
|
||||
{ __list_del(l->prev, l->next); list_add(l, h); }
|
||||
static inline void list_move_tail(struct list_head *l, struct list_head *h)
|
||||
{ __list_del(l->prev, l->next); list_add_tail(l, h); }
|
||||
|
||||
/* ---- 替换 ---- */
|
||||
static inline void list_replace(struct list_head *old, struct list_head *node)
|
||||
{ node->next=old->next; node->next->prev=node; node->prev=old->prev; node->prev->next=node; }
|
||||
static inline void list_replace_init(struct list_head *old, struct list_head *node)
|
||||
{ list_replace(old,node); INIT_LIST_HEAD(old); }
|
||||
|
||||
/* ---- 判断 ---- */
|
||||
static inline int list_empty(const struct list_head *h)
|
||||
{ return h->next == h; }
|
||||
static inline int list_is_linked(const struct list_head *e)
|
||||
{ return e->next != e; }
|
||||
|
||||
/* ---- 宿主指针 ---- */
|
||||
#define list_entry(ptr, type, member) \
|
||||
((type*)((char*)(ptr) - offsetof(type, member)))
|
||||
#define list_first_entry(h, type, member) list_entry((h)->next, type, member)
|
||||
#define list_last_entry(h, type, member) list_entry((h)->prev, type, member)
|
||||
#define list_first_entry_or_null(h, type, member) \
|
||||
(list_empty(h) ? NULL : list_first_entry(h, type, member))
|
||||
|
||||
/* ---- 遍历 ---- */
|
||||
#define list_for_each(pos, head) \
|
||||
for (pos = (head)->next; pos != (head); pos = pos->next)
|
||||
#define list_for_each_safe(pos, n, head) \
|
||||
for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)
|
||||
#define list_for_each_entry(pos, head, member) \
|
||||
for (pos = list_first_entry(head, __typeof__(*pos), member); \
|
||||
&pos->member != (head); \
|
||||
pos = list_entry(pos->member.next, __typeof__(*pos), member))
|
||||
#define list_for_each_entry_safe(pos, n, head, member) \
|
||||
for (pos = list_first_entry(head, __typeof__(*pos), member), \
|
||||
n = list_entry(pos->member.next, __typeof__(*pos), member); \
|
||||
&pos->member != (head); \
|
||||
pos = n, n = list_entry(n->member.next, __typeof__(*n), member))
|
||||
|
||||
/* ---- 拼接 ---- */
|
||||
static inline void list_splice_init(struct list_head *src, struct list_head *dst)
|
||||
{
|
||||
if (!list_empty(src)) {
|
||||
struct list_head *f = src->next, *l = src->prev, *a = dst->next;
|
||||
f->prev = dst; dst->next = f; l->next = a; a->prev = l;
|
||||
INIT_LIST_HEAD(src);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* @file myBase.h
|
||||
* @brief 公共基础定义 — 所有模块共享的宏、类型、常量
|
||||
* @details 对齐 RTU 原工程功能需求,从零编写
|
||||
*/
|
||||
|
||||
#ifndef _MY_BASE_H_
|
||||
#define _MY_BASE_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <semaphore.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* === 终端颜色 === */
|
||||
#define COLOR_RESET "\033[0m"
|
||||
#define COLOR_RED "\033[31m"
|
||||
#define COLOR_GREEN "\033[32m"
|
||||
#define COLOR_YELLOW "\033[33m"
|
||||
#define COLOR_BLUE "\033[34m"
|
||||
#define COLOR_MAGENTA "\033[35m"
|
||||
#define COLOR_CYAN "\033[36m"
|
||||
#define COLOR_WHITE "\033[37m"
|
||||
|
||||
/* === 文件名缩写(去掉路径前缀,跨平台) === */
|
||||
#if defined(__linux__)
|
||||
#define __SHORT_FILE__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/')+1 : __FILE__)
|
||||
#else
|
||||
#define __SHORT_FILE__ (strrchr(__FILE__,'\\') ? strrchr(__FILE__,'\\')+1 : __FILE__)
|
||||
#endif
|
||||
|
||||
/* === 控制台日志宏(liblog 加载前可用) === */
|
||||
#define MY_LOG(color, level, fmt, ...) \
|
||||
do { printf("%s[%s] [%s,%u] " fmt COLOR_RESET "\n", \
|
||||
color, level, __SHORT_FILE__, __LINE__, ##__VA_ARGS__); } while(0)
|
||||
#define MY_LOG_I(fmt, ...) MY_LOG(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
|
||||
#define MY_LOG_E(fmt, ...) MY_LOG(COLOR_RED, "ERROR", fmt, ##__VA_ARGS__)
|
||||
|
||||
/* === LOCAL 修饰(表语义:文件内可见) === */
|
||||
#ifndef LOCAL
|
||||
#define LOCAL static
|
||||
#endif
|
||||
|
||||
/* === 固定长度字符串类型 === */
|
||||
#define SHORT_STR_LEN 64
|
||||
typedef char S_STR[SHORT_STR_LEN];
|
||||
#define LONG_STR_LEN 128
|
||||
typedef char L_STR[LONG_STR_LEN];
|
||||
|
||||
/* === 大小端转换 === */
|
||||
#define BE16_GET(p) ((uint16_t)(((uint8_t*)(p))[0]<<8 | ((uint8_t*)(p))[1]))
|
||||
#define BE16_SET(m,n) ((m)[0]=(uint8_t)((n)>>8),(m)[1]=(uint8_t)(n))
|
||||
#define LE16_GET(p) ((uint16_t)(((uint8_t*)(p))[1]<<8 | ((uint8_t*)(p))[0]))
|
||||
#define LE16_SET(m,n) ((m)[0]=(uint8_t)(n),(m)[1]=(uint8_t)((n)>>8))
|
||||
|
||||
#define BE32_GET(p) ((int32_t)(((uint8_t*)(p))[0]<<24|((uint8_t*)(p))[1]<<16|((uint8_t*)(p))[2]<<8|((uint8_t*)(p))[3]))
|
||||
#define BE32_SET(m,n) ((m)[0]=(uint8_t)((n)>>24),(m)[1]=(uint8_t)((n)>>16),(m)[2]=(uint8_t)((n)>>8),(m)[3]=(uint8_t)(n))
|
||||
#define LE32_GET(p) ((int32_t)(((uint8_t*)(p))[3]<<24|((uint8_t*)(p))[2]<<16|((uint8_t*)(p))[1]<<8|((uint8_t*)(p))[0]))
|
||||
#define LE32_SET(m,n) ((m)[3]=(uint8_t)((n)>>24),(m)[2]=(uint8_t)((n)>>16),(m)[1]=(uint8_t)((n)>>8),(m)[0]=(uint8_t)(n))
|
||||
|
||||
#define BE64_GET(p) (((uint64_t)(((uint8_t*)(p))[0])<<56)|((uint64_t)(((uint8_t*)(p))[1])<<48)|((uint64_t)(((uint8_t*)(p))[2])<<40)|((uint64_t)(((uint8_t*)(p))[3])<<32)|((uint64_t)(((uint8_t*)(p))[4])<<24)|((uint64_t)(((uint8_t*)(p))[5])<<16)|((uint64_t)(((uint8_t*)(p))[6])<<8)|((uint64_t)(((uint8_t*)(p))[7])))
|
||||
#define LE64_GET(p) (((uint64_t)(((uint8_t*)(p))[7])<<56)|((uint64_t)(((uint8_t*)(p))[6])<<48)|((uint64_t)(((uint8_t*)(p))[5])<<40)|((uint64_t)(((uint8_t*)(p))[4])<<32)|((uint64_t)(((uint8_t*)(p))[3])<<24)|((uint64_t)(((uint8_t*)(p))[2])<<16)|((uint64_t)(((uint8_t*)(p))[1])<<8)|((uint64_t)(((uint8_t*)(p))[0])))
|
||||
|
||||
#define BE64_SET(m,n) ((m)[0]=(uint8_t)((n)>>56),(m)[1]=(uint8_t)((n)>>48),(m)[2]=(uint8_t)((n)>>40),(m)[3]=(uint8_t)((n)>>32),(m)[4]=(uint8_t)((n)>>24),(m)[5]=(uint8_t)((n)>>16),(m)[6]=(uint8_t)((n)>>8),(m)[7]=(uint8_t)(n))
|
||||
#define LE64_SET(m,n) ((m)[7]=(uint8_t)((n)>>56),(m)[6]=(uint8_t)((n)>>48),(m)[5]=(uint8_t)((n)>>40),(m)[4]=(uint8_t)((n)>>32),(m)[3]=(uint8_t)((n)>>24),(m)[2]=(uint8_t)((n)>>16),(m)[1]=(uint8_t)((n)>>8),(m)[0]=(uint8_t)(n))
|
||||
|
||||
/* === 类型存取 === */
|
||||
#define GET_BY_TYPE(p,type) (*(type*)(p))
|
||||
#define SET_BY_TYPE(p,v,type) (*(type*)(p) = *(type*)(v))
|
||||
|
||||
/* === 数据类型枚举(用于协议编解码) === */
|
||||
#define DATA_TYPE_B 1
|
||||
#define DATA_TYPE_S8 43
|
||||
#define DATA_TYPE_U8 32
|
||||
#define DATA_TYPE_S16 33
|
||||
#define DATA_TYPE_U16 45
|
||||
#define DATA_TYPE_S32 2
|
||||
#define DATA_TYPE_U32 35
|
||||
#define DATA_TYPE_L64 36
|
||||
#define DATA_TYPE_UL64 37
|
||||
#define DATA_TYPE_F32 38
|
||||
#define DATA_TYPE_D64 39
|
||||
#define DATA_TYPE_IP 100
|
||||
#define DATA_TYPE_MAC 101
|
||||
#define DATA_TYPE_C8 102
|
||||
#define DATA_TYPE_C32 103
|
||||
#define DATA_TYPE_C64 104
|
||||
#define DATA_TYPE_C128 105
|
||||
#define DATA_TYPE_C1 106
|
||||
#define DATA_TYPE_STR 107
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @file myFunc.h
|
||||
* @brief 基础工具函数 — 校验/字符串/时间/文件/进程/环境/IPC
|
||||
*/
|
||||
|
||||
#ifndef _MY_FUNC_H_
|
||||
#define _MY_FUNC_H_
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 时间结构 ---- */
|
||||
typedef struct {
|
||||
uint32_t ms:16, min:6, :2, hour:5, :3, day:5, week:3, month:4, :4, year:7, :1;
|
||||
} time_sys_t;
|
||||
typedef struct { time_t sec; time_t usec; } time_cal_t;
|
||||
|
||||
/* ---- IPC ---- */
|
||||
typedef struct { int qid, type, len; char text[4096]; } ipc_text_t;
|
||||
typedef struct { long id; ipc_text_t t; } ipc_msg_t;
|
||||
enum { IPC_RESP_NONE=1, IPC_RESP_ONCE, IPC_RESP_ALWAYS };
|
||||
|
||||
/* ---- 文件描述头(256字节) ---- */
|
||||
#pragma pack(1)
|
||||
typedef struct {
|
||||
uint64_t flag; /* 魔数 0xA55AA55AA55AA55A */
|
||||
char ver[16], author[16], modify[32];
|
||||
uint16_t crc;
|
||||
char desc[128], bak[54];
|
||||
} file_info_t;
|
||||
#pragma pack()
|
||||
|
||||
/* ===== 校验 ===== */
|
||||
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
||||
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_file_crc16(const char *path);
|
||||
uint32_t func_cal_file_crc32(const char *path);
|
||||
|
||||
/* ===== 字符串转换 ===== */
|
||||
uint8_t func_hex_ch(char c);
|
||||
uint8_t func_dec_ch(char c);
|
||||
int func_hex2dec(char *s);
|
||||
int func_dec2dec(char *s);
|
||||
int func_hex2buf(const char *s, char *dst, int *len);
|
||||
char *func_dec2str(char *buf, int sz, int val, int width);
|
||||
char *func_hex2str(char *buf, int sz, int val, int width);
|
||||
char *func_float2str(char *buf, int sz, float f, int width);
|
||||
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
||||
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
||||
|
||||
/* ===== 时间 ===== */
|
||||
char *func_time_sys2str(char *buf, int sz, time_sys_t *t);
|
||||
int func_str2time_sys(const char *s, time_sys_t *t);
|
||||
int func_get_time(void *t, int type); /* type: 1=sys 2=cal */
|
||||
int func_set_time(void *t, int type);
|
||||
void func_time_sys2cal(time_sys_t *src, time_cal_t *dst);
|
||||
void func_time_cal2sys(time_cal_t *src, time_sys_t *dst);
|
||||
|
||||
/* ===== 文件目录 ===== */
|
||||
int func_dir_exist(const char *p);
|
||||
int func_make_dirs(const char *p);
|
||||
int func_del_dirs(const char *p);
|
||||
int func_del_dirs_cmd(const char *p);
|
||||
int func_file_exist(const char *p);
|
||||
uint32_t func_file_size(const char *p);
|
||||
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
||||
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
||||
int func_write_file(const char *p, const char *buf, uint32_t n);
|
||||
int func_read_file_info(const char *p, file_info_t *info, int check);
|
||||
|
||||
/* ===== 进程 ===== */
|
||||
int func_proc_self_name(char *buf, uint32_t sz);
|
||||
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
||||
int func_proc_pid(void);
|
||||
int func_proc_pid_by_name(const char *name);
|
||||
int func_proc_exist_by_pid(int pid);
|
||||
int func_proc_exist_by_name(const char *name);
|
||||
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
||||
int func_proc_self_path(char *buf, uint32_t sz);
|
||||
int func_proc_self_dir(char *buf, uint32_t sz);
|
||||
|
||||
/* ===== 环境变量路径 ===== */
|
||||
int func_get_work_path(char *buf, uint32_t sz);
|
||||
int func_get_syscfg_path(char *buf, uint32_t sz);
|
||||
int func_get_app_root(char *buf, uint32_t sz);
|
||||
int func_get_his_root(char *buf, uint32_t sz);
|
||||
int func_get_log_root(char *buf, uint32_t sz);
|
||||
int func_get_dbc_root(char *buf, uint32_t sz);
|
||||
int func_get_shell_path(char *buf, uint32_t sz);
|
||||
int func_get_update_path(char *buf, uint32_t sz);
|
||||
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
||||
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
||||
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
||||
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
||||
|
||||
/* ===== IPC 消息队列 ===== */
|
||||
int func_ipc_create_by_name(char *name, int *qid);
|
||||
int func_ipc_create_by_pid(int pid, int *qid);
|
||||
int func_ipc_get_by_name(const char *name, int *qid);
|
||||
int func_ipc_get_by_pid(int pid, int *qid);
|
||||
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
||||
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
||||
int func_ipc_send_msg(int qid, ipc_msg_t *m);
|
||||
int func_ipc_recv_msg(int qid, ipc_msg_t *m);
|
||||
int func_ipc_delete(int qid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file myLog.h
|
||||
* @brief 异步日志系统接口(纯 C)
|
||||
*/
|
||||
|
||||
#ifndef _MY_LOG_H_
|
||||
#define _MY_LOG_H_
|
||||
#include <stdint.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LOG_INFO 0
|
||||
#define LOG_ERROR 1
|
||||
|
||||
/** 初始化(首次调用 log_prt 时自动触发,也可手动调用) */
|
||||
int log_init(const char *dir);
|
||||
|
||||
/** 核心日志函数(非阻塞,消息入队后立即返回) */
|
||||
void log_prt(uint32_t level, const char *file, const char *func,
|
||||
uint32_t line, const char *fmt, ...);
|
||||
|
||||
void log_file_on(void); /* 开启文件写入 */
|
||||
void log_file_off(void); /* 关闭文件写入 */
|
||||
void log_console_on(void); /* 开启控制台输出 */
|
||||
void log_console_off(void); /* 关闭控制台输出 */
|
||||
void log_flush(void); /* 等待队列清空 */
|
||||
void log_cleanup(void); /* 停止线程、释放资源 */
|
||||
|
||||
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# RTU_ALL_AI — 公共编译选项
|
||||
# 模块 makefile 通过 include 本文件获取 CC/CXX/C_FLAGS 等变量
|
||||
|
||||
# ------ x86 默认 ------
|
||||
CROSS_COMPILE :=
|
||||
GCCLIB_PATH := /usr/bin
|
||||
ARCH := x86
|
||||
BUILD_TYPE := x86
|
||||
|
||||
# ------ ARM 交叉编译 ------
|
||||
ifeq ($(CROSS), arm)
|
||||
$(info ===> ARM 交叉编译)
|
||||
CROSS_COMPILE := aarch64-buildroot-linux-gnu-
|
||||
GCCLIB_PATH := /opt/atk-dlrk356x-toolchain/bin
|
||||
ARCH := arm
|
||||
BUILD_TYPE := arm
|
||||
endif
|
||||
|
||||
# ------ 工具链 ------
|
||||
CC := $(GCCLIB_PATH)/$(CROSS_COMPILE)gcc
|
||||
CXX := $(GCCLIB_PATH)/$(CROSS_COMPILE)g++
|
||||
LD := $(GCCLIB_PATH)/$(CROSS_COMPILE)ld
|
||||
AR := $(GCCLIB_PATH)/$(CROSS_COMPILE)ar
|
||||
|
||||
# ------ 路径(模块 makefile 位于 release/src/layer/module/,上溯4级到根)------
|
||||
ROOT_DIR := $(realpath $(CURDIR)/../../../../)
|
||||
REL_ROOT_DIR := $(ROOT_DIR)/release/$(BUILD_TYPE)
|
||||
SRC_ROOT_DIR := $(ROOT_DIR)/src
|
||||
REL_INC := -I$(ROOT_DIR)/release/inc
|
||||
|
||||
# ------ 编译选项 ------
|
||||
C_FLAGS := $(REL_INC) -Wall -g
|
||||
CXX_FLAGS := $(REL_INC) -std=c++11 -Wall -g
|
||||
ifeq ($(CROSS), arm)
|
||||
C_FLAGS += -DRK356x -mno-outline-atomics
|
||||
CXX_FLAGS += -DRK356x -mno-outline-atomics -fno-threadsafe-statics
|
||||
endif
|
||||
|
||||
# ------ 产物路径 ------
|
||||
LIB_REL := $(REL_ROOT_DIR)/lib
|
||||
EXE_REL := $(REL_ROOT_DIR)/exe
|
||||
|
||||
export CC CXX LD AR ARCH C_FLAGS CXX_FLAGS
|
||||
export ROOT_DIR REL_ROOT_DIR SRC_ROOT_DIR REL_INC LIB_REL EXE_REL
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
SUBDIRS := ./src
|
||||
define make_subdir
|
||||
@for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done
|
||||
endef
|
||||
.PHONY: all clean rebuild
|
||||
all:; $(call make_subdir,all)
|
||||
clean:; @echo "clean..."; $(call make_subdir,clean)
|
||||
rebuild: clean all
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
SUBDIRS := ./public
|
||||
define make_subdir
|
||||
@for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done
|
||||
endef
|
||||
.PHONY: all clean rebuild
|
||||
all:; $(call make_subdir,all)
|
||||
clean:; $(call make_subdir,clean)
|
||||
rebuild: clean all
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
SUBDIRS :=
|
||||
define make_subdir
|
||||
@for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done
|
||||
endef
|
||||
.PHONY: all; all:; $(call make_subdir,all)
|
||||
.PHONY: clean; clean:; $(call make_subdir,clean)
|
||||
.PHONY: rebuild; rebuild: clean all
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
include ./../../../linux.mk
|
||||
L := $(notdir $(realpath $(CURDIR)/..))
|
||||
M := libfunc
|
||||
O := $(LIB_REL)/$(M).a
|
||||
S := $(SRC_ROOT_DIR)/$(L)/libfunc/src
|
||||
I := -I$(SRC_ROOT_DIR)/$(L)/libfunc/inc
|
||||
B := $(CURDIR)/$(M)/obj
|
||||
SRCS := $(wildcard $(S)/*.c)
|
||||
OBJS := $(patsubst $(S)/%.c,$(B)/%.o,$(SRCS))
|
||||
F := $(C_FLAGS) $(I) -I$(SRC_ROOT_DIR)/public/liblog/inc
|
||||
all:
|
||||
@mkdir -p $(ROOT_DIR)/release/inc
|
||||
@cp -f $(S:src=inc)/*.h $(ROOT_DIR)/release/inc/ 2>/dev/null; true
|
||||
@$(MAKE) $(O)
|
||||
$(O): $(OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AR) rcs $@ $^
|
||||
@echo "[$(M)] built"
|
||||
$(B)/%.o: $(S)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(F) -c $< -o $@
|
||||
clean:
|
||||
rm -rf $(B) $(O)
|
||||
rebuild: clean all
|
||||
.PHONY: all clean rebuild
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
include ./../../../linux.mk
|
||||
DST := $(ROOT_DIR)/release/inc
|
||||
SRC_H := $(SRC_ROOT_DIR)/public/liblist/inc/list.h
|
||||
all:
|
||||
@mkdir -p $(DST)
|
||||
@cp -f $(SRC_H) $(DST)/ 2>/dev/null && echo "[liblist] copied" || echo "[liblist] skip"
|
||||
clean:
|
||||
@rm -f $(DST)/list.h
|
||||
rebuild: clean all
|
||||
.PHONY: all clean rebuild
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
include ./../../../linux.mk
|
||||
L := $(notdir $(realpath $(CURDIR)/..))
|
||||
M := liblog
|
||||
O := $(LIB_REL)/$(M).a
|
||||
S := $(SRC_ROOT_DIR)/$(L)/liblog/src
|
||||
I := -I$(SRC_ROOT_DIR)/$(L)/liblog/inc
|
||||
B := $(CURDIR)/$(M)/obj
|
||||
SRCS := $(wildcard $(S)/*.c)
|
||||
OBJS := $(patsubst $(S)/%.c,$(B)/%.o,$(SRCS))
|
||||
F := $(C_FLAGS) $(I) -I$(SRC_ROOT_DIR)/public/libfunc/inc
|
||||
all:
|
||||
@mkdir -p $(ROOT_DIR)/release/inc
|
||||
@cp -f $(S:src=inc)/*.h $(ROOT_DIR)/release/inc/ 2>/dev/null; true
|
||||
@$(MAKE) $(O)
|
||||
$(O): $(OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AR) rcs $@ $^
|
||||
@echo "[$(M)] built"
|
||||
$(B)/%.o: $(S)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(F) -c $< -o $@
|
||||
clean:
|
||||
rm -rf $(B) $(O)
|
||||
rebuild: clean all
|
||||
.PHONY: all clean rebuild
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
SUBDIRS := ./liblist ./libfunc ./liblog
|
||||
define make_subdir
|
||||
@for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done
|
||||
endef
|
||||
.PHONY: all clean rebuild
|
||||
all:; $(call make_subdir,all)
|
||||
clean:; $(call make_subdir,clean)
|
||||
rebuild: clean all
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
SUBDIRS :=
|
||||
define make_subdir
|
||||
@for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done
|
||||
endef
|
||||
.PHONY: all; all:; $(call make_subdir,all)
|
||||
.PHONY: clean; clean:; $(call make_subdir,clean)
|
||||
.PHONY: rebuild; rebuild: clean all
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @file myFunc.h
|
||||
* @brief 基础工具函数 — 校验/字符串/时间/文件/进程/环境/IPC
|
||||
*/
|
||||
|
||||
#ifndef _MY_FUNC_H_
|
||||
#define _MY_FUNC_H_
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 时间结构 ---- */
|
||||
typedef struct {
|
||||
uint32_t ms:16, min:6, :2, hour:5, :3, day:5, week:3, month:4, :4, year:7, :1;
|
||||
} time_sys_t;
|
||||
typedef struct { time_t sec; time_t usec; } time_cal_t;
|
||||
|
||||
/* ---- IPC ---- */
|
||||
typedef struct { int qid, type, len; char text[4096]; } ipc_text_t;
|
||||
typedef struct { long id; ipc_text_t t; } ipc_msg_t;
|
||||
enum { IPC_RESP_NONE=1, IPC_RESP_ONCE, IPC_RESP_ALWAYS };
|
||||
|
||||
/* ---- 文件描述头(256字节) ---- */
|
||||
#pragma pack(1)
|
||||
typedef struct {
|
||||
uint64_t flag; /* 魔数 0xA55AA55AA55AA55A */
|
||||
char ver[16], author[16], modify[32];
|
||||
uint16_t crc;
|
||||
char desc[128], bak[54];
|
||||
} file_info_t;
|
||||
#pragma pack()
|
||||
|
||||
/* ===== 校验 ===== */
|
||||
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n);
|
||||
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n);
|
||||
uint16_t func_cal_file_crc16(const char *path);
|
||||
uint32_t func_cal_file_crc32(const char *path);
|
||||
|
||||
/* ===== 字符串转换 ===== */
|
||||
uint8_t func_hex_ch(char c);
|
||||
uint8_t func_dec_ch(char c);
|
||||
int func_hex2dec(char *s);
|
||||
int func_dec2dec(char *s);
|
||||
int func_hex2buf(const char *s, char *dst, int *len);
|
||||
char *func_dec2str(char *buf, int sz, int val, int width);
|
||||
char *func_hex2str(char *buf, int sz, int val, int width);
|
||||
char *func_float2str(char *buf, int sz, float f, int width);
|
||||
char *func_buf2str(char *buf, int sz, const char *src, int n);
|
||||
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max);
|
||||
|
||||
/* ===== 时间 ===== */
|
||||
char *func_time_sys2str(char *buf, int sz, time_sys_t *t);
|
||||
int func_str2time_sys(const char *s, time_sys_t *t);
|
||||
int func_get_time(void *t, int type); /* type: 1=sys 2=cal */
|
||||
int func_set_time(void *t, int type);
|
||||
void func_time_sys2cal(time_sys_t *src, time_cal_t *dst);
|
||||
void func_time_cal2sys(time_cal_t *src, time_sys_t *dst);
|
||||
|
||||
/* ===== 文件目录 ===== */
|
||||
int func_dir_exist(const char *p);
|
||||
int func_make_dirs(const char *p);
|
||||
int func_del_dirs(const char *p);
|
||||
int func_del_dirs_cmd(const char *p);
|
||||
int func_file_exist(const char *p);
|
||||
uint32_t func_file_size(const char *p);
|
||||
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out);
|
||||
uint8_t *func_read_file_alloc(const char *p, uint32_t *out);
|
||||
int func_write_file(const char *p, const char *buf, uint32_t n);
|
||||
int func_read_file_info(const char *p, file_info_t *info, int check);
|
||||
|
||||
/* ===== 进程 ===== */
|
||||
int func_proc_self_name(char *buf, uint32_t sz);
|
||||
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz);
|
||||
int func_proc_pid(void);
|
||||
int func_proc_pid_by_name(const char *name);
|
||||
int func_proc_exist_by_pid(int pid);
|
||||
int func_proc_exist_by_name(const char *name);
|
||||
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz);
|
||||
int func_proc_self_path(char *buf, uint32_t sz);
|
||||
int func_proc_self_dir(char *buf, uint32_t sz);
|
||||
|
||||
/* ===== 环境变量路径 ===== */
|
||||
int func_get_work_path(char *buf, uint32_t sz);
|
||||
int func_get_syscfg_path(char *buf, uint32_t sz);
|
||||
int func_get_app_root(char *buf, uint32_t sz);
|
||||
int func_get_his_root(char *buf, uint32_t sz);
|
||||
int func_get_log_root(char *buf, uint32_t sz);
|
||||
int func_get_dbc_root(char *buf, uint32_t sz);
|
||||
int func_get_shell_path(char *buf, uint32_t sz);
|
||||
int func_get_update_path(char *buf, uint32_t sz);
|
||||
int func_get_app_path(const char *app, char *buf, uint32_t sz);
|
||||
int func_get_app_cfg(const char *app, const char *ext, char *buf, uint32_t sz);
|
||||
int func_get_app_his(const char *app, char *buf, uint32_t sz);
|
||||
int func_get_app_log(const char *app, char *buf, uint32_t sz);
|
||||
|
||||
/* ===== IPC 消息队列 ===== */
|
||||
int func_ipc_create_by_name(char *name, int *qid);
|
||||
int func_ipc_create_by_pid(int pid, int *qid);
|
||||
int func_ipc_get_by_name(const char *name, int *qid);
|
||||
int func_ipc_get_by_pid(int pid, int *qid);
|
||||
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n);
|
||||
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n);
|
||||
int func_ipc_send_msg(int qid, ipc_msg_t *m);
|
||||
int func_ipc_recv_msg(int qid, ipc_msg_t *m);
|
||||
int func_ipc_delete(int qid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,608 @@
|
|||
/**
|
||||
* @file myFunc.c
|
||||
* @brief 基础工具函数实现 — 65 个函数、7 大类
|
||||
* @details 从零编写,功能对齐 RTU 原工程但不复制其代码
|
||||
*/
|
||||
|
||||
#include "myFunc.h"
|
||||
#include "myBase.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/msg.h>
|
||||
|
||||
/* =================================================================
|
||||
* 1. 校验计算
|
||||
* ================================================================= */
|
||||
|
||||
/* CRC16 查找表 (Modbus 多项式 0xA001) */
|
||||
LOCAL const uint16_t _crc16_tab[256] = {
|
||||
0x0000,0xc0c1,0xc181,0x0140,0xc301,0x03c0,0x0280,0xc241,0xc601,0x06c0,0x0780,0xc741,0x0500,0xc5c1,0xc481,0x0440,
|
||||
0xcc01,0x0cc0,0x0d80,0xcd41,0x0f00,0xcfc1,0xce81,0x0e40,0x0a00,0xcac1,0xcb81,0x0b40,0xc901,0x09c0,0x0880,0xc841,
|
||||
0xd801,0x18c0,0x1980,0xd941,0x1b00,0xdbc1,0xda81,0x1a40,0x1e00,0xdec1,0xdf81,0x1f40,0xdd01,0x1dc0,0x1c80,0xdc41,
|
||||
0x1400,0xd4c1,0xd581,0x1540,0xd701,0x17c0,0x1680,0xd641,0xd201,0x12c0,0x1380,0xd341,0x1100,0xd1c1,0xd081,0x1040,
|
||||
0xf001,0x30c0,0x3180,0xf141,0x3300,0xf3c1,0xf281,0x3240,0x3600,0xf6c1,0xf781,0x3740,0xf501,0x35c0,0x3480,0xf441,
|
||||
0x3c00,0xfcc1,0xfd81,0x3d40,0xff01,0x3fc0,0x3e80,0xfe41,0xfa01,0x3ac0,0x3b80,0xfb41,0x3900,0xf9c1,0xf881,0x3840,
|
||||
0x2800,0xe8c1,0xe981,0x2940,0xeb01,0x2bc0,0x2a80,0xea41,0xee01,0x2ec0,0x2f80,0xef41,0x2d00,0xedc1,0xec81,0x2c40,
|
||||
0xe401,0x24c0,0x2580,0xe541,0x2700,0xe7c1,0xe681,0x2640,0x2200,0xe2c1,0xe381,0x2340,0xe101,0x21c0,0x2080,0xe041,
|
||||
0xa001,0x60c0,0x6180,0xa141,0x6300,0xa3c1,0xa281,0x6240,0x6600,0xa6c1,0xa781,0x6740,0xa501,0x65c0,0x6480,0xa441,
|
||||
0x6c00,0xacc1,0xad81,0x6d40,0xaf01,0x6fc0,0x6e80,0xae41,0xaa01,0x6ac0,0x6b80,0xab41,0x6900,0xa9c1,0xa881,0x6840,
|
||||
0x7800,0xb8c1,0xb981,0x7940,0xbb01,0x7bc0,0x7a80,0xba41,0xbe01,0x7ec0,0x7f80,0xbf41,0x7d00,0xbdc1,0xbc81,0x7c40,
|
||||
0xb401,0x74c0,0x7580,0xb541,0x7700,0xb7c1,0xb681,0x7640,0x7200,0xb2c1,0xb381,0x7340,0xb101,0x71c0,0x7080,0xb041,
|
||||
0x5000,0x90c1,0x9181,0x5140,0x9301,0x53c0,0x5280,0x9241,0x9601,0x56c0,0x5780,0x9741,0x5500,0x95c1,0x9481,0x5440,
|
||||
0x9c01,0x5cc0,0x5d80,0x9d41,0x5f00,0x9fc1,0x9e81,0x5e40,0x5a00,0x9ac1,0x9b81,0x5b40,0x9901,0x59c0,0x5880,0x9841,
|
||||
0x8801,0x48c0,0x4980,0x8941,0x4b00,0x8bc1,0x8a81,0x4a40,0x4e00,0x8ec1,0x8f81,0x4f40,0x8d01,0x4dc0,0x4c80,0x8c41,
|
||||
0x4400,0x84c1,0x8581,0x4540,0x8701,0x47c0,0x4680,0x8641,0x8201,0x42c0,0x4380,0x8341,0x4100,0x81c1,0x8081,0x4040,
|
||||
};
|
||||
|
||||
/* CRC32 查找表 */
|
||||
LOCAL const uint32_t _crc32_tab[256] = {
|
||||
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
|
||||
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
|
||||
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
|
||||
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
|
||||
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
|
||||
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
|
||||
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
|
||||
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
|
||||
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
|
||||
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
|
||||
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
|
||||
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
|
||||
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
|
||||
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
|
||||
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
|
||||
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
|
||||
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
|
||||
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
|
||||
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
|
||||
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
|
||||
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
|
||||
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
|
||||
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
|
||||
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
|
||||
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
|
||||
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
|
||||
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
|
||||
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
|
||||
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
|
||||
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
|
||||
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
|
||||
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,
|
||||
};
|
||||
|
||||
uint8_t func_cal_sum_byte(const uint8_t *d, uint32_t n) {
|
||||
if (!d || !n) return 0;
|
||||
uint8_t s = 0;
|
||||
for (uint32_t i = 0; i < n; i++) s += d[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
uint16_t func_cal_sum_word(const uint8_t *d, uint32_t n) {
|
||||
if (!d || !n) return 0;
|
||||
uint16_t s = 0;
|
||||
for (uint32_t i = 0; i < n; i++) s += d[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
uint16_t func_cal_crc16(const uint8_t *d, uint32_t n) {
|
||||
if (!d || !n) return 0;
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
crc = (crc >> 8) ^ _crc16_tab[(uint8_t)(crc ^ d[i])];
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t func_cal_crc32(const uint8_t *d, uint32_t n) {
|
||||
if (!d || !n) return 0;
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
crc = (crc >> 8) ^ _crc32_tab[(uint8_t)(crc ^ d[i])];
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
uint16_t func_cal_file_crc16(const char *path) {
|
||||
if (!path) return 0;
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) return 0;
|
||||
uint16_t crc = 0xFFFF;
|
||||
uint8_t buf[4096]; size_t n;
|
||||
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
|
||||
for (size_t i = 0; i < n; i++)
|
||||
crc = (crc >> 8) ^ _crc16_tab[(uint8_t)(crc ^ buf[i])];
|
||||
fclose(fp);
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t func_cal_file_crc32(const char *path) {
|
||||
if (!path) return 0;
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) return 0;
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
uint8_t buf[4096]; size_t n;
|
||||
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
|
||||
for (size_t i = 0; i < n; i++)
|
||||
crc = (crc >> 8) ^ _crc32_tab[(uint8_t)(crc ^ buf[i])];
|
||||
fclose(fp);
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 2. 字符串转换
|
||||
* ================================================================= */
|
||||
|
||||
uint8_t func_hex_ch(char c) {
|
||||
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
|
||||
if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10);
|
||||
if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t func_dec_ch(char c) {
|
||||
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 十六进制字符串 "FF" → int 255 */
|
||||
int func_hex2dec(char *s) {
|
||||
if (!s) return 0;
|
||||
int v = 0;
|
||||
while (*s) { v = (v << 4) | func_hex_ch(*s); s++; }
|
||||
return v;
|
||||
}
|
||||
|
||||
/* 十进制字符串 "255" → int 255 */
|
||||
int func_dec2dec(char *s) {
|
||||
if (!s) return 0;
|
||||
int v = 0, sign = 1;
|
||||
if (*s == '-') { sign = -1; s++; }
|
||||
while (*s >= '0' && *s <= '9') { v = v * 10 + (*s - '0'); s++; }
|
||||
return v * sign;
|
||||
}
|
||||
|
||||
/* 十六进制字符串 "0102FF" → 字节缓冲 {0x01,0x02,0xFF} */
|
||||
int func_hex2buf(const char *s, char *dst, int *len) {
|
||||
if (!s || !dst || !len) return -1;
|
||||
int n = (int)strlen(s);
|
||||
if (n & 1) return -1; /* 奇数长度报错 */
|
||||
for (int i = 0; i < n / 2; i++)
|
||||
dst[i] = (char)((func_hex_ch(s[i*2]) << 4) | func_hex_ch(s[i*2+1]));
|
||||
*len = n / 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* int → 十进制字符串,右对齐,不足 width 左补 0 */
|
||||
char *func_dec2str(char *buf, int sz, int val, int width) {
|
||||
if (!buf || sz < 2) return NULL;
|
||||
char fmt[16];
|
||||
snprintf(fmt, sizeof(fmt), "%%0%dd", width);
|
||||
snprintf(buf, sz, fmt, val);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* int → 十六进制字符串,width=0 时不补零 */
|
||||
char *func_hex2str(char *buf, int sz, int val, int width) {
|
||||
if (!buf || sz < 2) return NULL;
|
||||
if (width)
|
||||
snprintf(buf, sz, "%0*X", width, val);
|
||||
else
|
||||
snprintf(buf, sz, "%X", val);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* float → 字符串 */
|
||||
char *func_float2str(char *buf, int sz, float f, int width) {
|
||||
if (!buf || sz < 2) return NULL;
|
||||
if (width) snprintf(buf, sz, "%*.*f", width, width > 2 ? width - 2 : 1, f);
|
||||
else snprintf(buf, sz, "%.6g", (double)f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* 字节数组 → 十六进制字符串 */
|
||||
char *func_buf2str(char *buf, int sz, const char *src, int n) {
|
||||
if (!buf || sz < 2 || !src) return NULL;
|
||||
int pos = 0;
|
||||
for (int i = 0; i < n && pos + 3 < sz; i++)
|
||||
pos += snprintf(buf + pos, sz - pos, "%02X", (unsigned char)src[i]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* 字符串分割成 argc/argv (空格分隔) */
|
||||
int func_str2argv(char *s, uint8_t *argc, char *argv[], uint8_t max) {
|
||||
if (!s || !argc || !argv) return -1;
|
||||
*argc = 0;
|
||||
while (*s && *argc < max) {
|
||||
while (*s && isspace((unsigned char)*s)) s++;
|
||||
if (!*s) break;
|
||||
argv[(*argc)++] = s;
|
||||
while (*s && !isspace((unsigned char)*s)) s++;
|
||||
if (*s) { *s = '\0'; s++; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 3. 时间处理
|
||||
* ================================================================= */
|
||||
|
||||
char *func_time_sys2str(char *buf, int sz, time_sys_t *t) {
|
||||
if (!buf || sz < 20 || !t) return NULL;
|
||||
snprintf(buf, sz, "%04u-%02u-%02u %02u:%02u:%02u.%03u",
|
||||
t->year, t->month, t->day,
|
||||
t->hour, t->min, t->ms / 1000,
|
||||
t->ms % 1000);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int func_str2time_sys(const char *s, time_sys_t *t) {
|
||||
if (!s || !t) return -1;
|
||||
memset(t, 0, sizeof(*t));
|
||||
uint32_t y, mo, d, h, mi, sec, ms = 0;
|
||||
if (sscanf(s, "%u-%u-%u %u:%u:%u.%u",
|
||||
&y, &mo, &d, &h, &mi, &sec, &ms) < 6) return -1;
|
||||
t->year = y; t->month = mo; t->day = d;
|
||||
t->hour = h; t->min = mi; t->ms = (uint16_t)(sec * 1000 + ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_get_time(void *t, int type) {
|
||||
if (!t) return -1;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
if (type == 1) { /* sys */
|
||||
time_sys_t *ts = (time_sys_t *)t;
|
||||
struct tm *g = localtime(&tv.tv_sec);
|
||||
if (!g) return -1;
|
||||
ts->year = (uint32_t)(g->tm_year + 1900);
|
||||
ts->month = (uint32_t)(g->tm_mon + 1);
|
||||
ts->day = (uint32_t)g->tm_mday;
|
||||
ts->hour = (uint32_t)g->tm_hour;
|
||||
ts->min = (uint32_t)g->tm_min;
|
||||
ts->ms = (uint16_t)(g->tm_sec * 1000 + tv.tv_usec / 1000);
|
||||
} else { /* cal */
|
||||
time_cal_t *tc = (time_cal_t *)t;
|
||||
tc->sec = tv.tv_sec;
|
||||
tc->usec = tv.tv_usec;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_set_time(void *t, int type) {
|
||||
if (!t) return -1;
|
||||
struct timeval tv;
|
||||
if (type == 1) {
|
||||
time_sys_t *ts = (time_sys_t *)t;
|
||||
struct tm g = {0};
|
||||
g.tm_year = (int)ts->year - 1900; g.tm_mon = (int)ts->month - 1;
|
||||
g.tm_mday = (int)ts->day; g.tm_hour = (int)ts->hour;
|
||||
g.tm_min = (int)ts->min; g.tm_sec = (int)(ts->ms / 1000);
|
||||
tv.tv_sec = mktime(&g);
|
||||
tv.tv_usec = (ts->ms % 1000) * 1000;
|
||||
} else {
|
||||
time_cal_t *tc = (time_cal_t *)t;
|
||||
tv.tv_sec = tc->sec; tv.tv_usec = tc->usec;
|
||||
}
|
||||
return settimeofday(&tv, NULL);
|
||||
}
|
||||
|
||||
void func_time_sys2cal(time_sys_t *src, time_cal_t *dst) {
|
||||
if (!src || !dst) return;
|
||||
struct tm g = {0};
|
||||
g.tm_year=(int)src->year-1900; g.tm_mon=(int)src->month-1;
|
||||
g.tm_mday=(int)src->day; g.tm_hour=(int)src->hour;
|
||||
g.tm_min=(int)src->min; g.tm_sec=(int)(src->ms/1000);
|
||||
dst->sec = mktime(&g);
|
||||
dst->usec = (src->ms % 1000) * 1000;
|
||||
}
|
||||
|
||||
void func_time_cal2sys(time_cal_t *src, time_sys_t *dst) {
|
||||
if (!src || !dst) return;
|
||||
struct tm *g = localtime(&src->sec);
|
||||
if (!g) return;
|
||||
dst->year=g->tm_year+1900; dst->month=g->tm_mon+1; dst->day=g->tm_mday;
|
||||
dst->hour=g->tm_hour; dst->min=g->tm_min;
|
||||
dst->ms=(uint16_t)(g->tm_sec*1000 + src->usec/1000);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 4. 文件目录操作
|
||||
* ================================================================= */
|
||||
|
||||
int func_dir_exist(const char *p) {
|
||||
if (!p) return 0;
|
||||
struct stat st;
|
||||
return (stat(p, &st) == 0 && S_ISDIR(st.st_mode)) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 递归创建目录 */
|
||||
LOCAL int _mkdir(const char *p) {
|
||||
if (!p) return -1;
|
||||
char tmp[256];
|
||||
strncpy(tmp, p, sizeof(tmp)-1); tmp[sizeof(tmp)-1]='\0';
|
||||
for (char *x = tmp + 1; *x; x++) {
|
||||
if (*x == '/') { *x = '\0'; mkdir(tmp, 0755); *x = '/'; }
|
||||
}
|
||||
return mkdir(tmp, 0755);
|
||||
}
|
||||
|
||||
int func_make_dirs(const char *p) {
|
||||
return _mkdir(p);
|
||||
}
|
||||
|
||||
/* 递归删除 */
|
||||
LOCAL int _rmdir(const char *p) {
|
||||
if (!p) return -1;
|
||||
DIR *d = opendir(p);
|
||||
if (!d) return remove(p);
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (strcmp(e->d_name,".")==0 || strcmp(e->d_name,"..")==0) continue;
|
||||
char full[512];
|
||||
snprintf(full, sizeof(full), "%s/%s", p, e->d_name);
|
||||
_rmdir(full);
|
||||
}
|
||||
closedir(d);
|
||||
return rmdir(p);
|
||||
}
|
||||
|
||||
int func_del_dirs(const char *p) { return _rmdir(p); }
|
||||
|
||||
int func_del_dirs_cmd(const char *p) {
|
||||
if (!p) return -1;
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf '%s'", p);
|
||||
return system(cmd);
|
||||
}
|
||||
|
||||
int func_file_exist(const char *p) {
|
||||
if (!p) return 0;
|
||||
struct stat st;
|
||||
return (stat(p, &st) == 0 && S_ISREG(st.st_mode)) ? 1 : 0;
|
||||
}
|
||||
|
||||
uint32_t func_file_size(const char *p) {
|
||||
if (!p) return 0;
|
||||
struct stat st;
|
||||
return (stat(p, &st) == 0) ? (uint32_t)st.st_size : 0;
|
||||
}
|
||||
|
||||
int func_read_file(const char *p, char *buf, uint32_t cap, uint32_t *out) {
|
||||
if (!p || !buf || !cap || !out) return -1;
|
||||
FILE *fp = fopen(p, "rb");
|
||||
if (!fp) return -1;
|
||||
*out = (uint32_t)fread(buf, 1, cap, fp);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t *func_read_file_alloc(const char *p, uint32_t *out) {
|
||||
if (!p || !out) return NULL;
|
||||
*out = 0;
|
||||
struct stat st;
|
||||
if (stat(p, &st) != 0) return NULL;
|
||||
uint8_t *buf = (uint8_t *)malloc((size_t)st.st_size + 1);
|
||||
if (!buf) return NULL;
|
||||
FILE *fp = fopen(p, "rb");
|
||||
if (!fp) { free(buf); return NULL; }
|
||||
*out = (uint32_t)fread(buf, 1, (size_t)st.st_size, fp);
|
||||
buf[*out] = '\0';
|
||||
fclose(fp);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int func_write_file(const char *p, const char *buf, uint32_t n) {
|
||||
if (!p || !buf) return -1;
|
||||
FILE *fp = fopen(p, "wb");
|
||||
if (!fp) return -1;
|
||||
fwrite(buf, 1, n, fp);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_read_file_info(const char *p, file_info_t *info, int check) {
|
||||
if (!p || !info) return -1;
|
||||
FILE *fp = fopen(p, "rb");
|
||||
if (!fp) return -1;
|
||||
if (fread(info, 1, sizeof(*info), fp) != sizeof(*info)) {
|
||||
fclose(fp); return -1;
|
||||
}
|
||||
fclose(fp);
|
||||
if (check) {
|
||||
if (info->flag != 0xA55AA55AA55AA55AULL) return -1;
|
||||
uint16_t saved = info->crc;
|
||||
info->crc = 0;
|
||||
uint16_t calced = func_cal_crc16((uint8_t*)info, sizeof(*info));
|
||||
info->crc = saved;
|
||||
if (saved != calced) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 5. 进程管理
|
||||
* ================================================================= */
|
||||
|
||||
int func_proc_self_name(char *buf, uint32_t sz) {
|
||||
if (!buf || sz == 0) return -1;
|
||||
FILE *fp = fopen("/proc/self/comm", "r");
|
||||
if (!fp) return -1;
|
||||
if (!fgets(buf, (int)sz, fp)) { fclose(fp); return -1; }
|
||||
size_t len = strlen(buf);
|
||||
if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_proc_name_by_pid(int pid, char *buf, uint32_t sz) {
|
||||
if (!buf || sz == 0) return -1;
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/comm", pid);
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp) return -1;
|
||||
if (!fgets(buf, (int)sz, fp)) { fclose(fp); return -1; }
|
||||
size_t len = strlen(buf);
|
||||
if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_proc_pid(void) { return (int)getpid(); }
|
||||
|
||||
int func_proc_pid_by_name(const char *name) {
|
||||
if (!name) return -1;
|
||||
DIR *d = opendir("/proc");
|
||||
if (!d) return -1;
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (!isdigit((unsigned char)e->d_name[0])) continue;
|
||||
char comm[64], path[128];
|
||||
snprintf(path, sizeof(path), "/proc/%s/comm", e->d_name);
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp) continue;
|
||||
if (fgets(comm, sizeof(comm), fp)) {
|
||||
size_t len = strlen(comm);
|
||||
if (len > 0 && comm[len-1] == '\n') comm[len-1] = '\0';
|
||||
if (strcmp(comm, name) == 0) { fclose(fp); closedir(d); return atoi(e->d_name); }
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
closedir(d);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int func_proc_exist_by_pid(int pid) {
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d", pid);
|
||||
struct stat st;
|
||||
return (stat(path, &st) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int func_proc_exist_by_name(const char *name) {
|
||||
return func_proc_pid_by_name(name) > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int func_proc_path_by_pid(int pid, char *buf, uint32_t sz) {
|
||||
if (!buf || sz == 0) return -1;
|
||||
char link[64];
|
||||
snprintf(link, sizeof(link), "/proc/%d/exe", pid);
|
||||
ssize_t n = readlink(link, buf, sz - 1);
|
||||
if (n < 0) return -1;
|
||||
buf[n] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_proc_self_path(char *buf, uint32_t sz) {
|
||||
return func_proc_path_by_pid(func_proc_pid(), buf, sz);
|
||||
}
|
||||
|
||||
int func_proc_self_dir(char *buf, uint32_t sz) {
|
||||
if (func_proc_self_path(buf, sz) != 0) return -1;
|
||||
char *slash = strrchr(buf, '/');
|
||||
if (slash) *slash = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 6. 环境变量路径
|
||||
* ================================================================= */
|
||||
|
||||
#define ENV_GET(key, buf, sz) do { \
|
||||
if (!buf || sz == 0) return -1; \
|
||||
const char *v = getenv(key); \
|
||||
if (!v) return -1; \
|
||||
snprintf(buf, sz, "%s", v); \
|
||||
return 0; \
|
||||
} while(0)
|
||||
|
||||
int func_get_work_path(char *b, uint32_t s) { ENV_GET("WORK_PATH", b, s); }
|
||||
int func_get_syscfg_path(char *b, uint32_t s) { ENV_GET("SYS_CFG_PATH", b, s); }
|
||||
int func_get_app_root(char *b, uint32_t s) { ENV_GET("APP_ROOT_PATH", b, s); }
|
||||
int func_get_his_root(char *b, uint32_t s) { ENV_GET("HIS_ROOT_PATH", b, s); }
|
||||
int func_get_log_root(char *b, uint32_t s) { ENV_GET("LOG_ROOT_PATH", b, s); }
|
||||
int func_get_dbc_root(char *b, uint32_t s) { ENV_GET("DBC_ROOT_PATH", b, s); }
|
||||
int func_get_shell_path(char *b, uint32_t s) { ENV_GET("SHELL_PATH", b, s); }
|
||||
int func_get_update_path(char *b, uint32_t s) { ENV_GET("UPDATE_PATH", b, s); }
|
||||
|
||||
/* 拼接路径:root/app_name[/postfix] */
|
||||
LOCAL int _make_path(const char *key, const char *app, const char *pfx,
|
||||
char *buf, uint32_t sz)
|
||||
{
|
||||
if (!buf || sz == 0 || !app) return -1;
|
||||
char root[256];
|
||||
const char *v = getenv(key);
|
||||
if (!v) v = "/tmp";
|
||||
snprintf(root, sizeof(root), "%s", v);
|
||||
if (pfx && pfx[0])
|
||||
snprintf(buf, sz, "%s/%s/%s", root, app, pfx);
|
||||
else
|
||||
snprintf(buf, sz, "%s/%s", root, app);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_get_app_path(const char *a, char *b, uint32_t s)
|
||||
{ return _make_path("APP_ROOT_PATH", a, NULL, b, s); }
|
||||
int func_get_app_cfg(const char *a, const char *e, char *b, uint32_t s)
|
||||
{ return _make_path("APP_ROOT_PATH", a, e, b, s); }
|
||||
int func_get_app_his(const char *a, char *b, uint32_t s)
|
||||
{ return _make_path("HIS_ROOT_PATH", a, NULL, b, s); }
|
||||
int func_get_app_log(const char *a, char *b, uint32_t s)
|
||||
{ return _make_path("LOG_ROOT_PATH", a, NULL, b, s); }
|
||||
|
||||
/* =================================================================
|
||||
* 7. IPC 消息队列 (System V)
|
||||
* ================================================================= */
|
||||
|
||||
int func_ipc_create_by_name(char *name, int *qid) {
|
||||
if (!name || !qid) return -1;
|
||||
int pid = func_proc_pid_by_name(name);
|
||||
if (pid < 0) return -1;
|
||||
return func_ipc_create_by_pid(pid, qid);
|
||||
}
|
||||
|
||||
int func_ipc_create_by_pid(int pid, int *qid) {
|
||||
if (!qid || pid < 1) return -1;
|
||||
key_t k = ftok("/tmp", pid & 0xFF);
|
||||
*qid = msgget(k, IPC_CREAT | 0666);
|
||||
return (*qid >= 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int func_ipc_get_by_name(const char *name, int *qid) {
|
||||
return func_ipc_create_by_name((char*)name, qid); /* 同名:先获取 */
|
||||
}
|
||||
|
||||
int func_ipc_get_by_pid(int pid, int *qid) {
|
||||
return func_ipc_create_by_pid(pid, qid);
|
||||
}
|
||||
|
||||
int func_ipc_send_buf(int qid, uint8_t *tx, uint32_t n) {
|
||||
if (!tx || n == 0) return -1;
|
||||
return msgsnd(qid, tx, n, 0);
|
||||
}
|
||||
|
||||
int func_ipc_recv_buf(int qid, uint8_t *rx, uint32_t n) {
|
||||
if (!rx || n == 0) return -1;
|
||||
return (int)msgrcv(qid, rx, n, 0, 0);
|
||||
}
|
||||
|
||||
int func_ipc_send_msg(int qid, ipc_msg_t *m) {
|
||||
if (!m) return -1;
|
||||
return msgsnd(qid, &m->t, sizeof(m->t) - sizeof(m->t.len) + (size_t)m->t.len, 0);
|
||||
}
|
||||
|
||||
int func_ipc_recv_msg(int qid, ipc_msg_t *m) {
|
||||
if (!m) return -1;
|
||||
ssize_t n = msgrcv(qid, &m->t, sizeof(m->t), m->id, 0);
|
||||
if (n < 0) return -1;
|
||||
m->id = (long)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int func_ipc_delete(int qid) {
|
||||
return msgctl(qid, IPC_RMID, NULL);
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* @file list.h
|
||||
* @brief 内核风格侵入式双向循环链表(纯头文件,C/C++ 兼容)
|
||||
*
|
||||
* 用法:将 struct list_head 嵌入你的结构体,通过 list_entry 取回宿主指针。
|
||||
* 所有操作 O(1),遍历 O(n)。C++ 兼容:不用 new/class/typeof 等关键字。
|
||||
*/
|
||||
|
||||
#ifndef _LIST_H_
|
||||
#define _LIST_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 节点 ---- */
|
||||
struct list_head { struct list_head *next, *prev; };
|
||||
|
||||
/* ---- 初始化 ---- */
|
||||
#define LIST_HEAD_INIT(n) { &(n), &(n) }
|
||||
#define LIST_HEAD(n) struct list_head n = LIST_HEAD_INIT(n)
|
||||
|
||||
static inline void INIT_LIST_HEAD(struct list_head *h)
|
||||
{ h->next = h; h->prev = h; }
|
||||
|
||||
/* ---- 内部:在 prev 和 next 之间插入 node ---- */
|
||||
static inline void __list_add(struct list_head *node,
|
||||
struct list_head *prev,
|
||||
struct list_head *next)
|
||||
{ next->prev = node; node->next = next; node->prev = prev; prev->next = node; }
|
||||
|
||||
/* ---- 内部:删除 prev 和 next 之间的节点 ---- */
|
||||
static inline void __list_del(struct list_head *prev, struct list_head *next)
|
||||
{ next->prev = prev; prev->next = next; }
|
||||
|
||||
/* ---- 添加 ---- */
|
||||
static inline void list_add(struct list_head *node, struct list_head *head)
|
||||
{ __list_add(node, head, head->next); }
|
||||
static inline void list_add_tail(struct list_head *node, struct list_head *head)
|
||||
{ __list_add(node, head->prev, head); }
|
||||
|
||||
/* ---- 删除 ---- */
|
||||
static inline void list_del(struct list_head *e)
|
||||
{ __list_del(e->prev, e->next); }
|
||||
static inline void list_del_init(struct list_head *e)
|
||||
{ __list_del(e->prev, e->next); INIT_LIST_HEAD(e); }
|
||||
|
||||
/* ---- 移动 ---- */
|
||||
static inline void list_move(struct list_head *l, struct list_head *h)
|
||||
{ __list_del(l->prev, l->next); list_add(l, h); }
|
||||
static inline void list_move_tail(struct list_head *l, struct list_head *h)
|
||||
{ __list_del(l->prev, l->next); list_add_tail(l, h); }
|
||||
|
||||
/* ---- 替换 ---- */
|
||||
static inline void list_replace(struct list_head *old, struct list_head *node)
|
||||
{ node->next=old->next; node->next->prev=node; node->prev=old->prev; node->prev->next=node; }
|
||||
static inline void list_replace_init(struct list_head *old, struct list_head *node)
|
||||
{ list_replace(old,node); INIT_LIST_HEAD(old); }
|
||||
|
||||
/* ---- 判断 ---- */
|
||||
static inline int list_empty(const struct list_head *h)
|
||||
{ return h->next == h; }
|
||||
static inline int list_is_linked(const struct list_head *e)
|
||||
{ return e->next != e; }
|
||||
|
||||
/* ---- 宿主指针 ---- */
|
||||
#define list_entry(ptr, type, member) \
|
||||
((type*)((char*)(ptr) - offsetof(type, member)))
|
||||
#define list_first_entry(h, type, member) list_entry((h)->next, type, member)
|
||||
#define list_last_entry(h, type, member) list_entry((h)->prev, type, member)
|
||||
#define list_first_entry_or_null(h, type, member) \
|
||||
(list_empty(h) ? NULL : list_first_entry(h, type, member))
|
||||
|
||||
/* ---- 遍历 ---- */
|
||||
#define list_for_each(pos, head) \
|
||||
for (pos = (head)->next; pos != (head); pos = pos->next)
|
||||
#define list_for_each_safe(pos, n, head) \
|
||||
for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)
|
||||
#define list_for_each_entry(pos, head, member) \
|
||||
for (pos = list_first_entry(head, __typeof__(*pos), member); \
|
||||
&pos->member != (head); \
|
||||
pos = list_entry(pos->member.next, __typeof__(*pos), member))
|
||||
#define list_for_each_entry_safe(pos, n, head, member) \
|
||||
for (pos = list_first_entry(head, __typeof__(*pos), member), \
|
||||
n = list_entry(pos->member.next, __typeof__(*pos), member); \
|
||||
&pos->member != (head); \
|
||||
pos = n, n = list_entry(n->member.next, __typeof__(*n), member))
|
||||
|
||||
/* ---- 拼接 ---- */
|
||||
static inline void list_splice_init(struct list_head *src, struct list_head *dst)
|
||||
{
|
||||
if (!list_empty(src)) {
|
||||
struct list_head *f = src->next, *l = src->prev, *a = dst->next;
|
||||
f->prev = dst; dst->next = f; l->next = a; a->prev = l;
|
||||
INIT_LIST_HEAD(src);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file myLog.h
|
||||
* @brief 异步日志系统接口(纯 C)
|
||||
*/
|
||||
|
||||
#ifndef _MY_LOG_H_
|
||||
#define _MY_LOG_H_
|
||||
#include <stdint.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LOG_INFO 0
|
||||
#define LOG_ERROR 1
|
||||
|
||||
/** 初始化(首次调用 log_prt 时自动触发,也可手动调用) */
|
||||
int log_init(const char *dir);
|
||||
|
||||
/** 核心日志函数(非阻塞,消息入队后立即返回) */
|
||||
void log_prt(uint32_t level, const char *file, const char *func,
|
||||
uint32_t line, const char *fmt, ...);
|
||||
|
||||
void log_file_on(void); /* 开启文件写入 */
|
||||
void log_file_off(void); /* 关闭文件写入 */
|
||||
void log_console_on(void); /* 开启控制台输出 */
|
||||
void log_console_off(void); /* 关闭控制台输出 */
|
||||
void log_flush(void); /* 等待队列清空 */
|
||||
void log_cleanup(void); /* 停止线程、释放资源 */
|
||||
|
||||
#define LOG_I(fmt, ...) log_prt(LOG_INFO, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_E(fmt, ...) log_prt(LOG_ERROR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
/**
|
||||
* @file myLog.c
|
||||
* @brief 异步日志实现 — 消息池 + 生产者/消费者 + 批量写入 + 文件轮转
|
||||
*
|
||||
* 架构概述:
|
||||
* 调用 log_prt() → 从消息池取空闲块 → 填充内容 → 入队(生产者)
|
||||
* 工作线程 → 批量出队(消费者) → 控制台彩色输出 + 文件写入
|
||||
* 首次调用 pthread_once 自动启动工作线程
|
||||
*/
|
||||
|
||||
#include "myLog.h"
|
||||
#include "myBase.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ========== 可配置参数 ========== */
|
||||
#define POOL_SIZE 10000 /* 消息池大小 */
|
||||
#define BATCH_MAX 100 /* 单次批量处理上限 */
|
||||
#define FLUSH_MS 100 /* 空闲等待间隔(毫秒) */
|
||||
#define FILE_MAX_SIZE (10*1024*1024) /* 单文件 10MB */
|
||||
#define FILE_MAX_KEEP 5 /* 保留历史文件数 */
|
||||
#define CONTENT_MAX 4096 /* 单条日志内容上限 */
|
||||
|
||||
/* ========== 数据结构 ========== */
|
||||
|
||||
/** 单条日志消息 */
|
||||
typedef struct log_msg {
|
||||
uint32_t level;
|
||||
char time_str[64];
|
||||
char src_info[128]; /* "文件名/函数:行号" */
|
||||
char content[CONTENT_MAX];
|
||||
struct log_msg *next; /* 空闲链表 / 队列链表 */
|
||||
} log_msg_t;
|
||||
|
||||
/** 线程安全队列 */
|
||||
typedef struct {
|
||||
log_msg_t *head, *tail;
|
||||
int count;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_pop; /* 消费者等待 */
|
||||
pthread_cond_t cond_push; /* 生产者等待(队列满) */
|
||||
} msg_queue_t;
|
||||
|
||||
/** 日志系统全局状态 */
|
||||
LOCAL struct {
|
||||
msg_queue_t q;
|
||||
pthread_t worker;
|
||||
volatile int running;
|
||||
FILE *fp;
|
||||
long file_bytes;
|
||||
int to_file;
|
||||
int to_console;
|
||||
char dir[256];
|
||||
} g_log;
|
||||
|
||||
/* ========== 消息池(预分配,无运行时 malloc) ========== */
|
||||
LOCAL log_msg_t *g_pool_free; /* 空闲链表头 */
|
||||
LOCAL log_msg_t g_pool_buf[POOL_SIZE]; /* 预分配数组 */
|
||||
LOCAL pthread_mutex_t g_pool_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
LOCAL pthread_once_t g_init_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
/** 初始化消息池:把数组元素链成空闲链表 */
|
||||
LOCAL void pool_init(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
if (g_pool_free != NULL) { pthread_mutex_unlock(&g_pool_lock); return; }
|
||||
for (int i = 0; i < POOL_SIZE - 1; i++)
|
||||
g_pool_buf[i].next = &g_pool_buf[i + 1];
|
||||
g_pool_buf[POOL_SIZE - 1].next = NULL;
|
||||
g_pool_free = &g_pool_buf[0];
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
}
|
||||
|
||||
/** 从池中取一个空闲消息块(失败返回 NULL) */
|
||||
LOCAL log_msg_t *pool_alloc(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
log_msg_t *m = g_pool_free;
|
||||
if (m) g_pool_free = m->next;
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** 归还消息块到池 */
|
||||
LOCAL void pool_free(log_msg_t *m)
|
||||
{
|
||||
if (!m) return;
|
||||
pthread_mutex_lock(&g_pool_lock);
|
||||
m->next = g_pool_free;
|
||||
g_pool_free = m;
|
||||
pthread_mutex_unlock(&g_pool_lock);
|
||||
}
|
||||
|
||||
/* ========== 队列操作 ========== */
|
||||
|
||||
/** 入队(满则阻塞) */
|
||||
LOCAL void q_push(log_msg_t *m)
|
||||
{
|
||||
pthread_mutex_lock(&g_log.q.lock);
|
||||
while (g_log.q.count >= POOL_SIZE)
|
||||
pthread_cond_wait(&g_log.q.cond_push, &g_log.q.lock);
|
||||
m->next = NULL;
|
||||
if (!g_log.q.head) g_log.q.head = m;
|
||||
else g_log.q.tail->next = m;
|
||||
g_log.q.tail = m;
|
||||
g_log.q.count++;
|
||||
pthread_cond_signal(&g_log.q.cond_pop);
|
||||
pthread_mutex_unlock(&g_log.q.lock);
|
||||
}
|
||||
|
||||
/** 出队(空则等待或返回 NULL) */
|
||||
LOCAL log_msg_t *q_pop(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_log.q.lock);
|
||||
while (g_log.q.count == 0 && g_log.running)
|
||||
pthread_cond_wait(&g_log.q.cond_pop, &g_log.q.lock);
|
||||
if (g_log.q.count == 0) { pthread_mutex_unlock(&g_log.q.lock); return NULL; }
|
||||
log_msg_t *m = g_log.q.head;
|
||||
g_log.q.head = m->next;
|
||||
if (!g_log.q.head) g_log.q.tail = NULL;
|
||||
g_log.q.count--;
|
||||
pthread_cond_signal(&g_log.q.cond_push);
|
||||
pthread_mutex_unlock(&g_log.q.lock);
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ========== 文件管理 ========== */
|
||||
|
||||
/** 拼接日志文件路径:dir/app.log 或 dir/app.log.N */
|
||||
LOCAL void file_path(char *buf, size_t sz, int idx)
|
||||
{
|
||||
if (idx == 0)
|
||||
snprintf(buf, sz, "%s/app.log", g_log.dir);
|
||||
else
|
||||
snprintf(buf, sz, "%s/app.log.%d", g_log.dir, idx);
|
||||
}
|
||||
|
||||
/** 创建日志目录 */
|
||||
LOCAL int ensure_dir(void)
|
||||
{
|
||||
if (!g_log.dir[0]) {
|
||||
/* 默认放到 /tmp/logs */
|
||||
snprintf(g_log.dir, sizeof(g_log.dir), "/tmp/logs");
|
||||
}
|
||||
struct stat st;
|
||||
if (stat(g_log.dir, &st) != 0)
|
||||
return mkdir(g_log.dir, 0755);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** 轮转日志文件:app.log→.1→.2→...→.N(max),删除最旧的 */
|
||||
LOCAL void rotate_files(void)
|
||||
{
|
||||
char old[512], neo[512];
|
||||
if (g_log.fp) { fclose(g_log.fp); g_log.fp = NULL; }
|
||||
|
||||
/* 删除最旧 */
|
||||
file_path(old, sizeof(old), FILE_MAX_KEEP);
|
||||
remove(old);
|
||||
|
||||
/* 依次后移 */
|
||||
for (int i = FILE_MAX_KEEP - 1; i >= 1; i--) {
|
||||
file_path(old, sizeof(old), i);
|
||||
file_path(neo, sizeof(neo), i + 1);
|
||||
rename(old, neo);
|
||||
}
|
||||
|
||||
/* 当前文件 → .1 */
|
||||
file_path(old, sizeof(old), 0);
|
||||
file_path(neo, sizeof(neo), 1);
|
||||
rename(old, neo);
|
||||
g_log.file_bytes = 0;
|
||||
}
|
||||
|
||||
/** 打开当前日志文件 */
|
||||
LOCAL FILE *open_file(void)
|
||||
{
|
||||
if (ensure_dir() != 0) return NULL;
|
||||
char path[512];
|
||||
file_path(path, sizeof(path), 0);
|
||||
|
||||
/* 检查已有文件大小 */
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
if (st.st_size > FILE_MAX_SIZE)
|
||||
rotate_files();
|
||||
else
|
||||
g_log.file_bytes = st.st_size;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(path, "a");
|
||||
if (fp) setlinebuf(fp); /* 行缓冲 */
|
||||
return fp;
|
||||
}
|
||||
|
||||
/* ========== 格式化 ========== */
|
||||
|
||||
/** 当前时间字符串:YYYY-MM-DD HH:MM:SS.mmm */
|
||||
LOCAL void fmt_time(char *buf, size_t sz)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
struct tm *t = localtime(&tv.tv_sec);
|
||||
if (t) {
|
||||
snprintf(buf, sz, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
||||
t->tm_year+1900, t->tm_mon+1, t->tm_mday,
|
||||
t->tm_hour, t->tm_min, t->tm_sec,
|
||||
(int)(tv.tv_usec / 1000));
|
||||
} else {
|
||||
snprintf(buf, sz, "----.--.-- --:--:--.---");
|
||||
}
|
||||
}
|
||||
|
||||
/** 提取文件名(去掉路径) 并拼接成 "file/func:line" */
|
||||
LOCAL void fmt_src(char *buf, size_t sz,
|
||||
const char *file, const char *func, uint32_t line)
|
||||
{
|
||||
if (!file || !func) { snprintf(buf, sz, "?:?:0"); return; }
|
||||
const char *p = strrchr(file, '/');
|
||||
if (!p) p = strrchr(file, '\\');
|
||||
const char *name = p ? p + 1 : file;
|
||||
snprintf(buf, sz, "%s/%s:%u", name, func, line);
|
||||
}
|
||||
|
||||
/* ========== 输出 ========== */
|
||||
|
||||
/** 批量写控制台 + 文件 */
|
||||
LOCAL void output_batch(log_msg_t **batch, int cnt)
|
||||
{
|
||||
if (cnt == 0) return;
|
||||
static const char *titles[] = {"INFO", "ERROR"};
|
||||
static const char *colors[] = {COLOR_WHITE, COLOR_RED};
|
||||
|
||||
/* 控制台(带颜色) */
|
||||
if (g_log.to_console) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
log_msg_t *m = batch[i];
|
||||
printf("%s[%s] [%s%s%s] %s\n",
|
||||
colors[m->level], m->time_str,
|
||||
colors[m->level], titles[m->level],
|
||||
COLOR_RESET, m->content);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* 文件 */
|
||||
if (g_log.to_file) {
|
||||
if (!g_log.fp) g_log.fp = open_file();
|
||||
if (g_log.fp) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
log_msg_t *m = batch[i];
|
||||
int n = fprintf(g_log.fp, "[%s] [%s] %s\n",
|
||||
titles[m->level], m->time_str, m->content);
|
||||
if (n > 0) g_log.file_bytes += n;
|
||||
}
|
||||
fflush(g_log.fp);
|
||||
if (g_log.file_bytes > FILE_MAX_SIZE) rotate_files();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 工作线程 ========== */
|
||||
|
||||
LOCAL void *worker_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (g_log.running) {
|
||||
log_msg_t *batch[BATCH_MAX];
|
||||
int cnt = 0;
|
||||
|
||||
/* 批量取消息 */
|
||||
for (int i = 0; i < BATCH_MAX; i++) {
|
||||
log_msg_t *m = q_pop();
|
||||
if (!m) break;
|
||||
batch[cnt++] = m;
|
||||
}
|
||||
|
||||
if (cnt > 0) {
|
||||
output_batch(batch, cnt);
|
||||
for (int i = 0; i < cnt; i++) pool_free(batch[i]);
|
||||
} else {
|
||||
usleep(FLUSH_MS * 1000);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ========== 全局初始化(pthread_once) ========== */
|
||||
|
||||
LOCAL void global_init(void)
|
||||
{
|
||||
pool_init();
|
||||
/* g_log.q 已在声明时用 {0} 初始化 */
|
||||
g_log.q.head = g_log.q.tail = NULL;
|
||||
g_log.q.count = 0;
|
||||
g_log.to_console = 1;
|
||||
g_log.to_file = 0;
|
||||
g_log.running = 1;
|
||||
pthread_create(&g_log.worker, NULL, worker_thread, NULL);
|
||||
}
|
||||
|
||||
/* ========== 公开 API ========== */
|
||||
|
||||
int log_init(const char *dir)
|
||||
{
|
||||
if (dir && dir[0])
|
||||
snprintf(g_log.dir, sizeof(g_log.dir), "%s", dir);
|
||||
pool_init(); /* 确保消息池就绪 */
|
||||
g_log.to_file = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log_prt(uint32_t level, const char *file, const char *func,
|
||||
uint32_t line, const char *fmt, ...)
|
||||
{
|
||||
/* 懒初始化 */
|
||||
pthread_once(&g_init_once, global_init);
|
||||
if (level > LOG_ERROR || !fmt || !g_log.running) return;
|
||||
|
||||
/* 从消息池取空闲块 */
|
||||
log_msg_t *m = pool_alloc();
|
||||
if (!m) return; /* 池耗尽,丢弃本条 */
|
||||
|
||||
m->level = level;
|
||||
fmt_time(m->time_str, sizeof(m->time_str));
|
||||
fmt_src(m->src_info, sizeof(m->src_info), file, func, line);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int pos = snprintf(m->content, sizeof(m->content), "[%s] ", m->src_info);
|
||||
if (pos < (int)sizeof(m->content))
|
||||
vsnprintf(m->content + pos, sizeof(m->content) - pos, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
q_push(m);
|
||||
}
|
||||
|
||||
void log_file_on(void) { g_log.to_file = 1; }
|
||||
void log_file_off(void) { g_log.to_file = 0; }
|
||||
void log_console_on(void) { g_log.to_console = 1; }
|
||||
void log_console_off(void) { g_log.to_console = 0; }
|
||||
|
||||
void log_flush(void)
|
||||
{
|
||||
/* 轮询等待队列归零 */
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&g_log.q.lock);
|
||||
int c = g_log.q.count;
|
||||
pthread_mutex_unlock(&g_log.q.lock);
|
||||
if (c == 0) break;
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
void log_cleanup(void)
|
||||
{
|
||||
if (!g_log.running) return;
|
||||
g_log.running = 0;
|
||||
pthread_cond_signal(&g_log.q.cond_pop);
|
||||
pthread_join(g_log.worker, NULL);
|
||||
if (g_log.fp) { fclose(g_log.fp); g_log.fp = NULL; }
|
||||
/* 释放池中残留消息 */
|
||||
pthread_mutex_lock(&g_log.q.lock);
|
||||
log_msg_t *cur = g_log.q.head, *tmp;
|
||||
while (cur) { tmp = cur; cur = cur->next; pool_free(tmp); }
|
||||
g_log.q.head = g_log.q.tail = NULL;
|
||||
g_log.q.count = 0;
|
||||
pthread_mutex_unlock(&g_log.q.lock);
|
||||
}
|
||||
Loading…
Reference in New Issue