feat(platform): 新增跨平台抽象层 platform.h + platform.c
- platform.h: 定义跨平台类型 cocoon_socket_t / cocoon_file_t / cocoon_dir_iter_t - POSIX 路径: fcntl O_NONBLOCK, sendfile 零拷贝, opendir/readdir, sysconf - Windows 路径: ioctlsocket(FIONBIO), WSAStartup/WSACleanup, FindFirstFile - 错误码统一: WSAEWOULDBLOCK→EAGAIN, WSAEINTR→EINTR, WSAECONNRESET→ECONNRESET - 全部中文注释,Doxygen 风格
This commit is contained in:
parent
9041d8dfc4
commit
c9693612bd
562
platform.c
Normal file
562
platform.c
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
/**
|
||||||
|
* platform.c - 跨平台抽象层实现
|
||||||
|
*
|
||||||
|
* 高性能实现原则:
|
||||||
|
* - sendfile 替代方案使用 64KB 大块缓冲区,最大化单次 I/O 吞吐量
|
||||||
|
* - Windows 目录遍历用 FindFirstFile 一次性获取,减少系统调用次数
|
||||||
|
* - 错误码统一映射,避免跨平台条件分支污染业务代码
|
||||||
|
* - socket 关闭统一用 cocoon_socket_close,防止 Windows 下误用 close()
|
||||||
|
*
|
||||||
|
* @author xfy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ========== POSIX 实现 ========== */
|
||||||
|
#ifdef COCOON_PLATFORM_POSIX
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX socket 子系统无需显式初始化。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_init(void) { return 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX socket 子系统无需显式清理。
|
||||||
|
*/
|
||||||
|
void cocoon_socket_cleanup(void) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下使用 fcntl 设置 O_NONBLOCK 标志。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_nonblock(cocoon_socket_t fd) {
|
||||||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
if (flags < 0) return -1;
|
||||||
|
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下 socket 与文件描述符统一用 close()。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_close(cocoon_socket_t fd) {
|
||||||
|
return close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下使用 shutdown(SHUT_RDWR) 双向断开连接。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_shutdown(cocoon_socket_t fd) {
|
||||||
|
return shutdown(fd, SHUT_RDWR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下 socket 发送直接用 write(),
|
||||||
|
* 内核态零拷贝路径,无需额外封装。
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_socket_send(cocoon_socket_t fd, const char *buf, size_t len) {
|
||||||
|
return write(fd, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t cocoon_socket_recv(cocoon_socket_t fd, void *buf, size_t len) {
|
||||||
|
return read(fd, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下文件操作直接映射标准 API。
|
||||||
|
*/
|
||||||
|
cocoon_file_t cocoon_file_open(const char *path) {
|
||||||
|
return open(path, O_RDONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t cocoon_file_read(cocoon_file_t fd, void *buf, size_t count) {
|
||||||
|
return read(fd, buf, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cocoon_file_seek(cocoon_file_t fd, int64_t offset, int whence) {
|
||||||
|
return lseek(fd, (off_t)offset, whence);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_file_close(cocoon_file_t fd) {
|
||||||
|
return close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_file_stat(const char *path, cocoon_stat_t *st) {
|
||||||
|
return stat(path, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_file_fstat(cocoon_file_t fd, cocoon_stat_t *st) {
|
||||||
|
return fstat(fd, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cocoon_stat_isreg(const cocoon_stat_t *st) {
|
||||||
|
return S_ISREG(st->st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cocoon_stat_isdir(const cocoon_stat_t *st) {
|
||||||
|
return S_ISDIR(st->st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cocoon_stat_size(const cocoon_stat_t *st) {
|
||||||
|
return (int64_t)st->st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t cocoon_stat_mtime(const cocoon_stat_t *st) {
|
||||||
|
return st->st_mtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linux 下优先使用 sendfile 零拷贝。
|
||||||
|
* 失败时回退到 read+write 循环,64KB 缓冲区。
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_file_send(cocoon_socket_t sock, cocoon_file_t file,
|
||||||
|
int64_t offset, size_t count) {
|
||||||
|
off_t off = (off_t)offset;
|
||||||
|
ssize_t sent = sendfile(sock, file, &off, count);
|
||||||
|
if (sent >= 0 || (errno != EINVAL && errno != ENOSYS)) {
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sendfile 不支持此场景,回退到 read+write */
|
||||||
|
if (lseek(file, (off_t)offset, SEEK_SET) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[65536]; /* 64KB 缓冲区,最大化单次 I/O */
|
||||||
|
size_t total = 0;
|
||||||
|
while (total < count) {
|
||||||
|
size_t to_read = count - total;
|
||||||
|
if (to_read > sizeof(buf)) to_read = sizeof(buf);
|
||||||
|
ssize_t n = read(file, buf, to_read);
|
||||||
|
if (n <= 0) {
|
||||||
|
if (n < 0 && errno == EINTR) continue;
|
||||||
|
return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buf_sent = 0;
|
||||||
|
while (buf_sent < (size_t)n) {
|
||||||
|
ssize_t w = write(sock, buf + buf_sent, (size_t)n - buf_sent);
|
||||||
|
if (w < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue;
|
||||||
|
return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
}
|
||||||
|
if (w == 0) return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
buf_sent += (size_t)w;
|
||||||
|
}
|
||||||
|
total += (size_t)n;
|
||||||
|
}
|
||||||
|
return (ssize_t)total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下目录遍历直接用 opendir/readdir/closedir。
|
||||||
|
*/
|
||||||
|
int cocoon_dir_open(cocoon_dir_iter_t *iter, const char *path) {
|
||||||
|
if (!iter || !path) return -1;
|
||||||
|
iter->dir = opendir(path);
|
||||||
|
return (iter->dir != NULL) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_dir_next(cocoon_dir_iter_t *iter) {
|
||||||
|
if (!iter || !iter->dir) return -1;
|
||||||
|
struct dirent *entry = readdir(iter->dir);
|
||||||
|
if (!entry) return 1; /* 遍历结束 */
|
||||||
|
|
||||||
|
strncpy(iter->name, entry->d_name, sizeof(iter->name) - 1);
|
||||||
|
iter->name[sizeof(iter->name) - 1] = '\0';
|
||||||
|
|
||||||
|
/* 用 d_type 快速判断(如果支持),否则 stat */
|
||||||
|
#ifdef _DIRENT_HAVE_D_TYPE
|
||||||
|
iter->is_dir = (entry->d_type == DT_DIR);
|
||||||
|
#else
|
||||||
|
char full_path[4096];
|
||||||
|
/* 注意:这里 path 未存储在 iter 中,调用方需自行判断 */
|
||||||
|
iter->is_dir = false; /* 保守默认值,static.c 会 stat 验证 */
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cocoon_dir_close(cocoon_dir_iter_t *iter) {
|
||||||
|
if (iter && iter->dir) {
|
||||||
|
closedir(iter->dir);
|
||||||
|
iter->dir = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 信号处理:SIGINT + SIGTERM。
|
||||||
|
*/
|
||||||
|
static void (*g_signal_handler)(int) = NULL;
|
||||||
|
|
||||||
|
static void posix_signal_handler(int sig) {
|
||||||
|
if (g_signal_handler) g_signal_handler(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cocoon_signal_setup(void (*handler)(int)) {
|
||||||
|
g_signal_handler = handler;
|
||||||
|
signal(SIGINT, posix_signal_handler);
|
||||||
|
signal(SIGTERM, posix_signal_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下获取 CPU 核心数。
|
||||||
|
*/
|
||||||
|
uint32_t cocoon_cpu_count(void) {
|
||||||
|
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
return (n > 0) ? (uint32_t)n : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下路径规范化用 realpath。
|
||||||
|
*/
|
||||||
|
bool cocoon_realpath(const char *path, char *resolved, size_t resolved_size) {
|
||||||
|
if (!path || !resolved || resolved_size == 0) return false;
|
||||||
|
char *r = realpath(path, NULL);
|
||||||
|
if (r) {
|
||||||
|
strncpy(resolved, r, resolved_size - 1);
|
||||||
|
resolved[resolved_size - 1] = '\0';
|
||||||
|
free(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下创建目录。
|
||||||
|
*/
|
||||||
|
int cocoon_mkdir(const char *path) {
|
||||||
|
return mkdir(path, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POSIX 下直接返回 errno。
|
||||||
|
*/
|
||||||
|
int cocoon_get_last_error(void) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cocoon_strerror(int err) {
|
||||||
|
return strerror(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* COCOON_PLATFORM_POSIX */
|
||||||
|
|
||||||
|
|
||||||
|
/* ========== Windows 实现 ========== */
|
||||||
|
#ifdef COCOON_PLATFORM_WINDOWS
|
||||||
|
|
||||||
|
#include <fcntl.h> /* _O_RDONLY, _O_BINARY */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下 Winsock 需要显式初始化和清理。
|
||||||
|
* 使用静态引用计数,确保多线程安全(此处单线程 accept 足够)。
|
||||||
|
*/
|
||||||
|
static int g_wsa_ref = 0;
|
||||||
|
|
||||||
|
int cocoon_socket_init(void) {
|
||||||
|
if (g_wsa_ref == 0) {
|
||||||
|
WSADATA wsaData;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_wsa_ref++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cocoon_socket_cleanup(void) {
|
||||||
|
if (g_wsa_ref > 0) {
|
||||||
|
g_wsa_ref--;
|
||||||
|
if (g_wsa_ref == 0) {
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下用 ioctlsocket(FIONBIO) 设置非阻塞。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_nonblock(cocoon_socket_t fd) {
|
||||||
|
u_long mode = 1; /* 1 = 非阻塞 */
|
||||||
|
return ioctlsocket(fd, FIONBIO, &mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下 socket 必须用 closesocket(),不能用 close()。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_close(cocoon_socket_t fd) {
|
||||||
|
return closesocket(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下 shutdown 行为与 POSIX 一致。
|
||||||
|
*/
|
||||||
|
int cocoon_socket_shutdown(cocoon_socket_t fd) {
|
||||||
|
return shutdown(fd, SD_BOTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下 socket 发送用 send(),不能用 write()。
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_socket_send(cocoon_socket_t fd, const char *buf, size_t len) {
|
||||||
|
/* send() 的 len 参数是 int,大于 INT_MAX 时分批发送 */
|
||||||
|
int chunk = (len > INT_MAX) ? INT_MAX : (int)len;
|
||||||
|
int r = send(fd, buf, chunk, 0);
|
||||||
|
if (r == SOCKET_ERROR) {
|
||||||
|
WSASetLastError(WSAGetLastError()); /* 保持错误码 */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (ssize_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t cocoon_socket_recv(cocoon_socket_t fd, void *buf, size_t len) {
|
||||||
|
int chunk = (len > INT_MAX) ? INT_MAX : (int)len;
|
||||||
|
int r = recv(fd, (char *)buf, chunk, 0);
|
||||||
|
if (r == SOCKET_ERROR) {
|
||||||
|
WSASetLastError(WSAGetLastError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (ssize_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下文件操作用 _open/_read/_lseek/_close(二进制模式)。
|
||||||
|
*/
|
||||||
|
cocoon_file_t cocoon_file_open(const char *path) {
|
||||||
|
return _open(path, _O_RDONLY | _O_BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t cocoon_file_read(cocoon_file_t fd, void *buf, size_t count) {
|
||||||
|
int chunk = (count > INT_MAX) ? INT_MAX : (int)count;
|
||||||
|
int r = _read(fd, buf, chunk);
|
||||||
|
return (r < 0) ? -1 : (ssize_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cocoon_file_seek(cocoon_file_t fd, int64_t offset, int whence) {
|
||||||
|
return _lseeki64(fd, offset, whence);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_file_close(cocoon_file_t fd) {
|
||||||
|
return _close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下用 _stat64 获取文件信息,支持大于 2GB 的文件。
|
||||||
|
*/
|
||||||
|
int cocoon_file_stat(const char *path, cocoon_stat_t *st) {
|
||||||
|
return _stat64(path, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_file_fstat(cocoon_file_t fd, cocoon_stat_t *st) {
|
||||||
|
return _fstat64(fd, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cocoon_stat_isreg(const cocoon_stat_t *st) {
|
||||||
|
return S_ISREG(st->st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cocoon_stat_isdir(const cocoon_stat_t *st) {
|
||||||
|
return S_ISDIR(st->st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cocoon_stat_size(const cocoon_stat_t *st) {
|
||||||
|
return (int64_t)st->st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t cocoon_stat_mtime(const cocoon_stat_t *st) {
|
||||||
|
return st->st_mtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下无原生 sendfile,使用 read+send 循环。
|
||||||
|
* 64KB 缓冲区,最大化单次 I/O 吞吐量,减少用户态/内核态切换开销。
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_file_send(cocoon_socket_t sock, cocoon_file_t file,
|
||||||
|
int64_t offset, size_t count) {
|
||||||
|
if (_lseeki64(file, offset, SEEK_SET) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[65536]; /* 64KB:现代页表和磁盘 I/O 的最佳平衡点 */
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
while (total < count) {
|
||||||
|
size_t to_read = count - total;
|
||||||
|
if (to_read > sizeof(buf)) to_read = sizeof(buf);
|
||||||
|
|
||||||
|
int chunk = (to_read > INT_MAX) ? INT_MAX : (int)to_read;
|
||||||
|
int n = _read(file, buf, chunk);
|
||||||
|
if (n <= 0) {
|
||||||
|
if (n < 0 && errno == EINTR) continue;
|
||||||
|
return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buf_sent = 0;
|
||||||
|
while (buf_sent < (size_t)n) {
|
||||||
|
int to_send = (int)((size_t)n - buf_sent);
|
||||||
|
int w = send(sock, buf + buf_sent, to_send, 0);
|
||||||
|
if (w == SOCKET_ERROR) {
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
if (err == WSAEWOULDBLOCK || err == WSAEINTR) continue;
|
||||||
|
errno = EIO; /* 映射为通用 I/O 错误 */
|
||||||
|
return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
}
|
||||||
|
if (w == 0) return (total > 0) ? (ssize_t)total : -1;
|
||||||
|
buf_sent += (size_t)w;
|
||||||
|
}
|
||||||
|
total += (size_t)n;
|
||||||
|
}
|
||||||
|
return (ssize_t)total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下目录遍历用 FindFirstFile/FindNextFile。
|
||||||
|
* 一次性获取所有信息,减少系统调用次数。
|
||||||
|
*/
|
||||||
|
int cocoon_dir_open(cocoon_dir_iter_t *iter, const char *path) {
|
||||||
|
if (!iter || !path) return -1;
|
||||||
|
|
||||||
|
/* 构建搜索路径:path + \\* */
|
||||||
|
size_t len = strlen(path);
|
||||||
|
if (len >= sizeof(iter->path_buf) - 3) return -1;
|
||||||
|
|
||||||
|
memcpy(iter->path_buf, path, len);
|
||||||
|
iter->path_buf[len] = '\\';
|
||||||
|
iter->path_buf[len + 1] = '*';
|
||||||
|
iter->path_buf[len + 2] = '\0';
|
||||||
|
|
||||||
|
iter->handle = FindFirstFileA(iter->path_buf, &iter->data);
|
||||||
|
if (iter->handle == INVALID_HANDLE_VALUE) return -1;
|
||||||
|
|
||||||
|
iter->first = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cocoon_dir_next(cocoon_dir_iter_t *iter) {
|
||||||
|
if (!iter || iter->handle == INVALID_HANDLE_VALUE) return -1;
|
||||||
|
|
||||||
|
if (!iter->first) {
|
||||||
|
if (!FindNextFileA(iter->handle, &iter->data)) {
|
||||||
|
if (GetLastError() == ERROR_NO_MORE_FILES) return 1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iter->first = false;
|
||||||
|
|
||||||
|
/* 跳过 . 和 .. */
|
||||||
|
while (iter->data.cFileName[0] == '.') {
|
||||||
|
if (iter->data.cFileName[1] == '\0') goto skip;
|
||||||
|
if (iter->data.cFileName[1] == '.' && iter->data.cFileName[2] == '\0') goto skip;
|
||||||
|
break;
|
||||||
|
skip:
|
||||||
|
if (!FindNextFileA(iter->handle, &iter->data)) {
|
||||||
|
if (GetLastError() == ERROR_NO_MORE_FILES) return 1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(iter->name, iter->data.cFileName, sizeof(iter->name) - 1);
|
||||||
|
iter->name[sizeof(iter->name) - 1] = '\0';
|
||||||
|
iter->is_dir = (iter->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cocoon_dir_close(cocoon_dir_iter_t *iter) {
|
||||||
|
if (iter && iter->handle != INVALID_HANDLE_VALUE) {
|
||||||
|
FindClose(iter->handle);
|
||||||
|
iter->handle = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 控制台事件处理(替代 POSIX 信号)。
|
||||||
|
*/
|
||||||
|
static void (*g_win_handler)(int) = NULL;
|
||||||
|
|
||||||
|
static BOOL WINAPI win_ctrl_handler(DWORD ctrl_type) {
|
||||||
|
(void)ctrl_type;
|
||||||
|
if (g_win_handler) g_win_handler(2); /* SIGINT = 2 */
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cocoon_signal_setup(void (*handler)(int)) {
|
||||||
|
g_win_handler = handler;
|
||||||
|
SetConsoleCtrlHandler(win_ctrl_handler, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下用 GetSystemInfo 获取 CPU 核心数。
|
||||||
|
*/
|
||||||
|
uint32_t cocoon_cpu_count(void) {
|
||||||
|
SYSTEM_INFO si;
|
||||||
|
GetSystemInfo(&si);
|
||||||
|
return (si.dwNumberOfProcessors > 0) ? si.dwNumberOfProcessors : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下路径规范化用 _fullpath。
|
||||||
|
*/
|
||||||
|
bool cocoon_realpath(const char *path, char *resolved, size_t resolved_size) {
|
||||||
|
if (!path || !resolved || resolved_size == 0) return false;
|
||||||
|
char *r = _fullpath(NULL, path, 0);
|
||||||
|
if (r) {
|
||||||
|
strncpy(resolved, r, resolved_size - 1);
|
||||||
|
resolved[resolved_size - 1] = '\0';
|
||||||
|
free(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下创建目录。
|
||||||
|
*/
|
||||||
|
int cocoon_mkdir(const char *path) {
|
||||||
|
return _mkdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 错误码统一映射:
|
||||||
|
* WSAEWOULDBLOCK → EAGAIN
|
||||||
|
* WSAECONNRESET → ECONNRESET
|
||||||
|
* 其他保持原样。
|
||||||
|
*/
|
||||||
|
int cocoon_get_last_error(void) {
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
switch (err) {
|
||||||
|
case WSAEWOULDBLOCK: return EAGAIN;
|
||||||
|
case WSAEINTR: return EINTR;
|
||||||
|
case WSAECONNRESET: return ECONNRESET;
|
||||||
|
case WSAECONNABORTED: return ECONNABORTED;
|
||||||
|
default: return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows 下错误描述用 FormatMessage 获取。
|
||||||
|
* 注意:线程不安全,仅用于日志输出。
|
||||||
|
*/
|
||||||
|
static __thread char g_err_buf[256];
|
||||||
|
|
||||||
|
const char *cocoon_strerror(int err) {
|
||||||
|
if (err == 0) return "成功";
|
||||||
|
|
||||||
|
DWORD fm = FormatMessageA(
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, (DWORD)err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
g_err_buf, sizeof(g_err_buf), NULL);
|
||||||
|
|
||||||
|
if (fm == 0) {
|
||||||
|
snprintf(g_err_buf, sizeof(g_err_buf), "未知错误码 %d", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去除尾部换行符 */
|
||||||
|
size_t len = strlen(g_err_buf);
|
||||||
|
while (len > 0 && (g_err_buf[len - 1] == '\n' || g_err_buf[len - 1] == '\r')) {
|
||||||
|
g_err_buf[--len] = '\0';
|
||||||
|
}
|
||||||
|
return g_err_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* COCOON_PLATFORM_WINDOWS */
|
||||||
408
platform.h
Normal file
408
platform.h
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
/**
|
||||||
|
* platform.h - 跨平台抽象层头文件
|
||||||
|
*
|
||||||
|
* 封装 POSIX 与 Windows 的系统差异,提供统一的跨平台 API。
|
||||||
|
* 所有公共 API 均使用中文注释,遵循 Doxygen 风格。
|
||||||
|
*
|
||||||
|
* 设计原则:
|
||||||
|
* 1. 最小化系统调用次数(高性能)
|
||||||
|
* 2. Windows 下优先使用原生 API,拒绝兼容性层带来的额外开销
|
||||||
|
* 3. 零拷贝优先:sendfile 在 Windows 下用 TransmitFile 或大块 read+send 循环
|
||||||
|
* 4. 错误码统一:POSIX errno 与 Windows WSAGetLastError() 映射为统一错误码
|
||||||
|
*
|
||||||
|
* @author xfy
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COCOON_PLATFORM_H
|
||||||
|
#define COCOON_PLATFORM_H
|
||||||
|
|
||||||
|
/* ========== 平台检测与系统头文件 ========== */
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define COCOON_PLATFORM_WINDOWS
|
||||||
|
|
||||||
|
/* 精简 Windows 头文件,减少编译时间 */
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <winsock2.h> /**< Winsock 2(必须在 windows.h 之前) */
|
||||||
|
#include <ws2tcpip.h> /**< IPv6 扩展 */
|
||||||
|
#include <windows.h> /**< Windows 核心 API */
|
||||||
|
#include <io.h> /**< _open/_read/_close 等 */
|
||||||
|
#include <direct.h> /**< _mkdir */
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
/* Windows 下缺失的 POSIX 宏定义 */
|
||||||
|
#ifndef S_ISREG
|
||||||
|
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||||
|
#endif
|
||||||
|
#ifndef S_ISDIR
|
||||||
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* MinGW 已提供 dirent.h,MSVC 需要自行实现 */
|
||||||
|
#ifdef __MINGW32__
|
||||||
|
#include <dirent.h>
|
||||||
|
#define COCOON_HAS_DIRENT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define COCOON_PLATFORM_POSIX
|
||||||
|
|
||||||
|
#include <sys/socket.h> /**< socket/bind/listen/accept */
|
||||||
|
#include <netinet/in.h> /**< sockaddr_in */
|
||||||
|
#include <netinet/tcp.h> /**< TCP_NODELAY */
|
||||||
|
#include <arpa/inet.h> /**< htons/ntohs */
|
||||||
|
#include <unistd.h> /**< close/read/write */
|
||||||
|
#include <fcntl.h> /**< fcntl/open */
|
||||||
|
#include <signal.h> /**< signal/sigaction */
|
||||||
|
#include <dirent.h> /**< opendir/readdir */
|
||||||
|
#include <sys/sendfile.h> /**< sendfile(Linux 零拷贝) */
|
||||||
|
#define COCOON_HAS_DIRENT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
/* ========== 跨平台类型定义 ========== */
|
||||||
|
|
||||||
|
#ifdef COCOON_PLATFORM_WINDOWS
|
||||||
|
/**
|
||||||
|
* cocoon_socket_t - 跨平台 socket 描述符类型
|
||||||
|
*
|
||||||
|
* Windows 下为 SOCKET(UINT_PTR),POSIX 下为 int。
|
||||||
|
*/
|
||||||
|
typedef SOCKET cocoon_socket_t;
|
||||||
|
#define COCOON_INVALID_SOCKET INVALID_SOCKET /**< 无效 socket 值 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_t - 跨平台文件描述符类型
|
||||||
|
*
|
||||||
|
* Windows 下通过 _open/_close 管理,仍为 int。
|
||||||
|
*/
|
||||||
|
typedef int cocoon_file_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socklen_t - socket 地址长度类型
|
||||||
|
*/
|
||||||
|
typedef int cocoon_socklen_t;
|
||||||
|
|
||||||
|
#else
|
||||||
|
typedef int cocoon_socket_t;
|
||||||
|
#define COCOON_INVALID_SOCKET (-1)
|
||||||
|
typedef int cocoon_file_t;
|
||||||
|
typedef socklen_t cocoon_socklen_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ========== socket 生命周期 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_init - 初始化 socket 子系统
|
||||||
|
*
|
||||||
|
* Windows 下调用 WSAStartup;POSIX 下无操作。
|
||||||
|
*
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_socket_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_cleanup - 清理 socket 子系统
|
||||||
|
*
|
||||||
|
* Windows 下调用 WSACleanup;POSIX 下无操作。
|
||||||
|
*/
|
||||||
|
void cocoon_socket_cleanup(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_nonblock - 设置 socket 为非阻塞模式
|
||||||
|
*
|
||||||
|
* POSIX: fcntl(fd, F_SETFL, O_NONBLOCK)
|
||||||
|
* Windows: ioctlsocket(fd, FIONBIO, &mode)
|
||||||
|
*
|
||||||
|
* @param fd socket 描述符
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_socket_nonblock(cocoon_socket_t fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_close - 关闭 socket
|
||||||
|
*
|
||||||
|
* POSIX: close(fd)
|
||||||
|
* Windows: closesocket(fd)
|
||||||
|
*
|
||||||
|
* @param fd socket 描述符
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_socket_close(cocoon_socket_t fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_shutdown - 双向关闭 socket 连接
|
||||||
|
*
|
||||||
|
* 用于唤醒阻塞在 coco_read 的协程,触发连接超时清理。
|
||||||
|
*
|
||||||
|
* @param fd socket 描述符
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_socket_shutdown(cocoon_socket_t fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_send - 向 socket 发送数据(高性能)
|
||||||
|
*
|
||||||
|
* POSIX: write(fd, buf, len)
|
||||||
|
* Windows: send(fd, buf, len, 0)
|
||||||
|
*
|
||||||
|
* 自动处理 EAGAIN/EWOULDBLOCK/EINTR,调用者应自行循环。
|
||||||
|
*
|
||||||
|
* @param fd socket 描述符
|
||||||
|
* @param buf 数据缓冲区
|
||||||
|
* @param len 数据长度
|
||||||
|
* @return 实际发送字节数,-1 错误(设置 errno)
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_socket_send(cocoon_socket_t fd, const char *buf, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_socket_recv - 从 socket 接收数据
|
||||||
|
*
|
||||||
|
* POSIX: read(fd, buf, len)
|
||||||
|
* Windows: recv(fd, buf, len, 0)
|
||||||
|
*
|
||||||
|
* @param fd socket 描述符
|
||||||
|
* @param buf 接收缓冲区
|
||||||
|
* @param len 最大接收长度
|
||||||
|
* @return 实际接收字节数,0 表示对端关闭,-1 错误
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_socket_recv(cocoon_socket_t fd, void *buf, size_t len);
|
||||||
|
|
||||||
|
/* ========== 文件操作 API ========== */
|
||||||
|
|
||||||
|
/* 跨平台 stat 类型定义:Windows 用 _stat64(64位文件大小),POSIX 用 struct stat */
|
||||||
|
#ifdef COCOON_PLATFORM_WINDOWS
|
||||||
|
typedef struct _stat64 cocoon_stat_t;
|
||||||
|
#else
|
||||||
|
typedef struct stat cocoon_stat_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_open - 以只读模式打开文件
|
||||||
|
*
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 文件描述符,-1 失败
|
||||||
|
*/
|
||||||
|
cocoon_file_t cocoon_file_open(const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_read - 从文件读取数据
|
||||||
|
*
|
||||||
|
* @param fd 文件描述符
|
||||||
|
* @param buf 缓冲区
|
||||||
|
* @param count 读取长度
|
||||||
|
* @return 实际读取字节数,0 表示 EOF,-1 错误
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_file_read(cocoon_file_t fd, void *buf, size_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_seek - 移动文件指针
|
||||||
|
*
|
||||||
|
* @param fd 文件描述符
|
||||||
|
* @param offset 偏移量
|
||||||
|
* @param whence 起始位置(SEEK_SET/SEEK_CUR/SEEK_END)
|
||||||
|
* @return 新的文件位置,-1 错误
|
||||||
|
*/
|
||||||
|
int64_t cocoon_file_seek(cocoon_file_t fd, int64_t offset, int whence);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_close - 关闭文件
|
||||||
|
*
|
||||||
|
* @param fd 文件描述符
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_file_close(cocoon_file_t fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_stat - 获取文件元数据
|
||||||
|
*
|
||||||
|
* @param path 文件路径
|
||||||
|
* @param st 输出 stat 结构体(POSIX struct stat / Windows struct _stat)
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_file_stat(const char *path, cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_fstat - 通过文件描述符获取元数据
|
||||||
|
*
|
||||||
|
* @param fd 文件描述符
|
||||||
|
* @param st 输出 stat 结构体
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_file_fstat(cocoon_file_t fd, cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_stat_isreg - 判断是否为普通文件
|
||||||
|
*
|
||||||
|
* @param st stat 结构体指针
|
||||||
|
* @return true 是普通文件
|
||||||
|
*/
|
||||||
|
bool cocoon_stat_isreg(const cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_stat_isdir - 判断是否为目录
|
||||||
|
*
|
||||||
|
* @param st stat 结构体指针
|
||||||
|
* @return true 是目录
|
||||||
|
*/
|
||||||
|
bool cocoon_stat_isdir(const cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_stat_size - 获取文件大小
|
||||||
|
*
|
||||||
|
* @param st stat 结构体指针
|
||||||
|
* @return 文件大小(字节)
|
||||||
|
*/
|
||||||
|
int64_t cocoon_stat_size(const cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_stat_mtime - 获取文件修改时间
|
||||||
|
*
|
||||||
|
* @param st stat 结构体指针
|
||||||
|
* @return 修改时间戳(秒)
|
||||||
|
*/
|
||||||
|
time_t cocoon_stat_mtime(const cocoon_stat_t *st);
|
||||||
|
|
||||||
|
/* ========== 高性能文件发送(零拷贝替代) ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_file_send - 将文件内容发送到 socket
|
||||||
|
*
|
||||||
|
* Linux: 优先使用 sendfile 零拷贝(内核态传输,无用户态内存拷贝)
|
||||||
|
* Windows/其他: 使用 read+send 循环,64KB 大块缓冲区,
|
||||||
|
* 最大化单次系统调用吞吐量,减少用户态/内核态切换
|
||||||
|
*
|
||||||
|
* @param sock 目标 socket
|
||||||
|
* @param file 源文件描述符
|
||||||
|
* @param offset 文件起始偏移量
|
||||||
|
* @param count 发送字节数
|
||||||
|
* @return 实际发送字节数,-1 错误
|
||||||
|
*/
|
||||||
|
ssize_t cocoon_file_send(cocoon_socket_t sock, cocoon_file_t file,
|
||||||
|
int64_t offset, size_t count);
|
||||||
|
|
||||||
|
/* ========== 目录遍历 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_dir_iter_t - 目录遍历迭代器
|
||||||
|
*
|
||||||
|
* POSIX: 包装 DIR*,一次性 opendir。
|
||||||
|
* Windows: 包装 FindFirstFile/FindNextFile,避免多次系统调用。
|
||||||
|
*/
|
||||||
|
typedef struct cocoon_dir_iter {
|
||||||
|
#ifdef COCOON_PLATFORM_WINDOWS
|
||||||
|
HANDLE handle; /**< FindFirstFile 句柄 */
|
||||||
|
WIN32_FIND_DATAA data; /**< 当前文件数据 */
|
||||||
|
bool first; /**< 首次调用标记 */
|
||||||
|
char path_buf[4096]; /**< 完整搜索路径(含通配符) */
|
||||||
|
#else
|
||||||
|
DIR *dir; /**< POSIX 目录句柄 */
|
||||||
|
#endif
|
||||||
|
char name[256]; /**< 当前文件名(输出) */
|
||||||
|
bool is_dir; /**< 当前项是否为目录 */
|
||||||
|
} cocoon_dir_iter_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_dir_open - 打开目录进行遍历
|
||||||
|
*
|
||||||
|
* @param iter 迭代器结构体(由调用者分配)
|
||||||
|
* @param path 目录路径
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_dir_open(cocoon_dir_iter_t *iter, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_dir_next - 获取下一个目录项
|
||||||
|
*
|
||||||
|
* @param iter 迭代器
|
||||||
|
* @return 0 成功,1 遍历结束,-1 错误
|
||||||
|
*/
|
||||||
|
int cocoon_dir_next(cocoon_dir_iter_t *iter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_dir_close - 关闭目录迭代器
|
||||||
|
*
|
||||||
|
* @param iter 迭代器
|
||||||
|
*/
|
||||||
|
void cocoon_dir_close(cocoon_dir_iter_t *iter);
|
||||||
|
|
||||||
|
/* ========== 信号处理 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_signal_setup - 注册优雅关闭信号/事件处理
|
||||||
|
*
|
||||||
|
* POSIX: signal(SIGINT/SIGTERM, handler)
|
||||||
|
* Windows: SetConsoleCtrlHandler(Ctrl+C / Ctrl+Break / 关闭事件)
|
||||||
|
*
|
||||||
|
* @param handler 信号处理函数(POSIX 下 sig 参数为信号编号)
|
||||||
|
*/
|
||||||
|
void cocoon_signal_setup(void (*handler)(int));
|
||||||
|
|
||||||
|
/* ========== 系统信息 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_cpu_count - 获取 CPU 逻辑核心数
|
||||||
|
*
|
||||||
|
* POSIX: sysconf(_SC_NPROCESSORS_ONLN)
|
||||||
|
* Windows: GetSystemInfo → dwNumberOfProcessors
|
||||||
|
*
|
||||||
|
* @return 逻辑 CPU 核心数(至少返回 1)
|
||||||
|
*/
|
||||||
|
uint32_t cocoon_cpu_count(void);
|
||||||
|
|
||||||
|
/* ========== 路径处理 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_realpath - 获取绝对路径
|
||||||
|
*
|
||||||
|
* POSIX: realpath()
|
||||||
|
* Windows: _fullpath()(MinGW)或 GetFullPathNameA()
|
||||||
|
*
|
||||||
|
* @param path 输入路径
|
||||||
|
* @param resolved 输出缓冲区
|
||||||
|
* @param resolved_size 缓冲区大小
|
||||||
|
* @return true 成功,false 失败
|
||||||
|
*/
|
||||||
|
bool cocoon_realpath(const char *path, char *resolved, size_t resolved_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_mkdir - 创建目录
|
||||||
|
*
|
||||||
|
* POSIX: mkdir(path, 0755)
|
||||||
|
* Windows: _mkdir(path)(默认权限)
|
||||||
|
*
|
||||||
|
* @param path 目录路径
|
||||||
|
* @return 0 成功,-1 失败
|
||||||
|
*/
|
||||||
|
int cocoon_mkdir(const char *path);
|
||||||
|
|
||||||
|
/* ========== 错误处理 API ========== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_get_last_error - 获取最后一次系统错误码
|
||||||
|
*
|
||||||
|
* 统一映射:
|
||||||
|
* - POSIX socket 错误:直接返回 errno
|
||||||
|
* - Windows socket 错误:WSAGetLastError() → 映射到 POSIX errno 等价码
|
||||||
|
* - 文件操作错误:直接返回 errno / GetLastError() 映射
|
||||||
|
*
|
||||||
|
* @return 错误码(与 POSIX errno 兼容)
|
||||||
|
*/
|
||||||
|
int cocoon_get_last_error(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cocoon_strerror - 将错误码转为可读的字符串
|
||||||
|
*
|
||||||
|
* @param err 错误码
|
||||||
|
* @return 错误描述字符串(线程不安全,仅用于日志输出)
|
||||||
|
*/
|
||||||
|
const char *cocoon_strerror(int err);
|
||||||
|
|
||||||
|
#endif /* COCOON_PLATFORM_H */
|
||||||
Loading…
x
Reference in New Issue
Block a user