Some checks failed
CI / build (push) Failing after 1m0s
- 新增 throttle.c/throttle.h:Token Bucket 双层限速实现 - 支持 per-connection(单连接限速)和 global(服务器总带宽限速) - 使用 fd 映射表自动注册/清理,侵入性最小 - cocoon.h: 配置结构体新增 throttle_conn_rate、throttle_global_rate - server.c: 连接创建时自动初始化 per-connection throttle;server_destroy 释放 global throttle - static.c: send_all() 自动查表限速 - platform.c: cocoon_file_send() 限速启用时回退到 read+write 循环,逐 chunk 限速 - proxy.c: send_all_fd() 加入限速 - config.c: 解析 JSON 配置中的 throttle 对象(conn_rate / global_rate) - config.h: 更新 config_merge 签名 - Makefile: 将 throttle.c 加入 SRCS 和所有单元测试编译规则 编译零警告,109 项集成测试通过(6 个失败为已知环境反向代理 404 问题)
635 lines
17 KiB
C
635 lines
17 KiB
C
/**
|
||
* platform.c - 跨平台抽象层实现
|
||
*
|
||
* 高性能实现原则:
|
||
* - sendfile 替代方案使用 64KB 大块缓冲区,最大化单次 I/O 吞吐量
|
||
* - Windows 目录遍历用 FindFirstFile 一次性获取,减少系统调用次数
|
||
* - 错误码统一映射,避免跨平台条件分支污染业务代码
|
||
* - socket 关闭统一用 cocoon_socket_close,防止 Windows 下误用 close()
|
||
*
|
||
* @author xfy
|
||
*/
|
||
|
||
#include "platform.h"
|
||
#include "throttle.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
|
||
/* ========== POSIX 实现 ========== */
|
||
#ifdef COCOON_PLATFORM_POSIX
|
||
|
||
#include <fcntl.h> /* O_NONBLOCK */
|
||
#include <unistd.h> /* close, read, write, lseek, dup2 */
|
||
#include <dirent.h> /* opendir, readdir, closedir */
|
||
#include <errno.h> /* errno, EAGAIN, EINTR */
|
||
#include <sys/sendfile.h> /* sendfile */
|
||
#include <signal.h> /* sigaction */
|
||
#include <sys/socket.h> /* send */
|
||
#include <poll.h> /* poll */
|
||
|
||
/**
|
||
* 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 下使用 poll() 等待 socket 可读或超时。
|
||
*/
|
||
int cocoon_socket_poll_readable(cocoon_socket_t fd, int timeout_ms) {
|
||
struct pollfd pfd = { .fd = fd, .events = POLLIN };
|
||
int ret = poll(&pfd, 1, timeout_ms);
|
||
if (ret > 0) return 1;
|
||
if (ret == 0) return 0;
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* 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 缓冲区。
|
||
* 如果限速启用,使用 read+write 循环以便控制发送速率。
|
||
*/
|
||
ssize_t cocoon_file_send(cocoon_socket_t sock, cocoon_file_t file,
|
||
int64_t offset, size_t count) {
|
||
/* 检查限速 */
|
||
cocoon_throttle_t *t = throttle_lookup(sock);
|
||
|
||
if (!t) {
|
||
/* 无限速,优先使用 sendfile 零拷贝 */
|
||
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) {
|
||
/* 应用限速 */
|
||
if (t) {
|
||
size_t chunk = (size_t)n - buf_sent;
|
||
uint64_t wait_usec = throttle_consume(t, chunk);
|
||
if (wait_usec > 0) {
|
||
struct timespec ts = {
|
||
.tv_sec = (time_t)(wait_usec / 1000000),
|
||
.tv_nsec = (long)((wait_usec % 1000000) * 1000)
|
||
};
|
||
nanosleep(&ts, NULL);
|
||
continue;
|
||
}
|
||
}
|
||
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 下使用 select() 等待 socket 可读或超时。
|
||
*/
|
||
int cocoon_socket_poll_readable(cocoon_socket_t fd, int timeout_ms) {
|
||
fd_set fds;
|
||
FD_ZERO(&fds);
|
||
FD_SET(fd, &fds);
|
||
struct timeval tv = { timeout_ms / 1000, (timeout_ms % 1000) * 1000 };
|
||
int ret = select(0, &fds, NULL, NULL, timeout_ms < 0 ? NULL : &tv);
|
||
if (ret > 0) return 1;
|
||
if (ret == 0) return 0;
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
|
||
cocoon_throttle_t *t = throttle_lookup(sock);
|
||
|
||
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) {
|
||
/* 应用限速 */
|
||
if (t) {
|
||
size_t to_send = (size_t)n - buf_sent;
|
||
uint64_t wait_usec = throttle_consume(t, to_send);
|
||
if (wait_usec > 0) {
|
||
struct timespec ts = {
|
||
.tv_sec = (time_t)(wait_usec / 1000000),
|
||
.tv_nsec = (long)((wait_usec % 1000000) * 1000)
|
||
};
|
||
nanosleep(&ts, NULL);
|
||
continue;
|
||
}
|
||
}
|
||
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 */
|