From 0e5a2ec129795b900f3298397af0f41028c8660e Mon Sep 17 00:00:00 2001 From: Mikachu2333 Date: Wed, 28 Oct 2026 22:56:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20UTF-8/UTF-16=20?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=EF=BC=9A=E6=96=87=E4=BB=B6=E3=80=81=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E4=B8=8E=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=94=B9=E8=B5=B0=E5=AE=BD=E5=AD=97=E7=AC=A6=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/xy.h | 286 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 230 insertions(+), 56 deletions(-) diff --git a/lib/xy.h b/lib/xy.h index 7e4286c85..e7af12031 100644 --- a/lib/xy.h +++ b/lib/xy.h @@ -113,6 +113,7 @@ xy = #include #include + #include #elif defined(__linux__) || defined(__linux) #define XY_Build_On_Linux 1 @@ -860,6 +861,212 @@ xy_str_next_nonempty_line (const char *str) +/****************************************************** + * UTF-8 / UTF-16 与系统调用封装 + * + * chsrc 内部统一使用 UTF-8。Windows 上 CRT 的文件、命令与环境变量接口 + * 都按 ANSI 代码页解释 char*,非 ASCII 路径会失败或乱码,因此字符串在 + * 跨出 C 运行时的每一处都先转为宽字符。 + ******************************************************/ +#ifdef XY_Build_On_Windows +/** + * @brief UTF-8 -> Windows 宽字符 + * + * @memory SAFE + * return caller-free + */ +static wchar_t * +_xy_win_wstr (const char *utf8) +{ + if (!utf8) + return NULL; + + int wlen = MultiByteToWideChar (CP_UTF8, 0, utf8, -1, NULL, 0); + if (wlen <= 0) + return NULL; + + wchar_t *wstr = xy_malloc0 ((size_t) wlen * sizeof (wchar_t)); + if (0 == MultiByteToWideChar (CP_UTF8, 0, utf8, -1, wstr, wlen)) + { + free (wstr); + return NULL; + } + return wstr; +} + +/** + * @brief Windows 宽字符 -> UTF-8 + * + * @memory SAFE + * return caller-free + */ +static char * +_xy_win_utf8 (const wchar_t *wstr) +{ + if (!wstr) + return NULL; + + int len = WideCharToMultiByte (CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (len <= 0) + return NULL; + + char *utf8 = xy_malloc0 ((size_t) len); + if (0 == WideCharToMultiByte (CP_UTF8, 0, wstr, -1, utf8, len, NULL, NULL)) + { + free (utf8); + return NULL; + } + return utf8; +} +#endif + + +/** + * @brief 以 UTF-8 路径打开文件 + * + * @memory SAFE + * return caller-free + */ +static FILE * +xy_fopen (const char *path, const char *mode) +{ +#ifdef XY_Build_On_Windows + wchar_t *wpath = _xy_win_wstr (path); + wchar_t *wmode = _xy_win_wstr (mode); + FILE *f = (wpath && wmode) ? _wfopen (wpath, wmode) : NULL; + free (wmode); + free (wpath); + return f; +#else + return fopen (path, mode); +#endif +} + + +/** + * @brief 删除文件 + * + * @return 同 remove() + */ +static int +xy_file_remove (const char *path) +{ +#ifdef XY_Build_On_Windows + wchar_t *wpath = _xy_win_wstr (path); + int ret = wpath ? _wremove (wpath) : -1; + free (wpath); + return ret; +#else + return remove (path); +#endif +} + + +/** + * @brief 路径是否存在(文件或目录) + */ +static bool +xy_path_exist (const char *path) +{ +#ifdef XY_Build_On_Windows + wchar_t *wpath = _xy_win_wstr (path); + bool exist = (NULL != wpath) && (INVALID_FILE_ATTRIBUTES != GetFileAttributesW (wpath)); + free (wpath); + return exist; +#else + return 0 == access (path, 0); /* F_OK */ +#endif +} + + +/** + * @brief 路径是否为目录 + */ +static bool +xy_path_is_dir (const char *path) +{ +#ifdef XY_Build_On_Windows + wchar_t *wpath = _xy_win_wstr (path); + DWORD attr = wpath ? GetFileAttributesW (wpath) : INVALID_FILE_ATTRIBUTES; + free (wpath); + return (INVALID_FILE_ATTRIBUTES != attr) && (attr & FILE_ATTRIBUTE_DIRECTORY); +#else + DIR *d = opendir (path); + if (!d) + return false; + closedir (d); + return true; +#endif +} + + +/** + * @brief 执行命令 + * + * @return 同 system() + */ +static int +xy_system (const char *cmd) +{ +#ifdef XY_Build_On_Windows + wchar_t *wcmd = _xy_win_wstr (cmd); + int status = wcmd ? _wsystem (wcmd) : -1; + free (wcmd); + return status; +#else + return system (cmd); +#endif +} + + +/** + * @brief 以管道执行命令 + * + * @return 同 popen(),需要以 pclose() 关闭 + */ +static FILE * +xy_popen (const char *cmd, const char *mode) +{ +#ifdef XY_Build_On_Windows + wchar_t *wcmd = _xy_win_wstr (cmd); + wchar_t *wmode = _xy_win_wstr (mode); + FILE *f = (wcmd && wmode) ? _wpopen (wcmd, wmode) : NULL; + free (wmode); + free (wcmd); + return f; +#else + return popen (cmd, mode); +#endif +} + + +/** + * @brief 读取环境变量 + * + * @note Windows 上 getenv() 把环境块按 ANSI 代码页转换,非 ASCII 值会乱码, + * 这里改用 _wgetenv() 再转为 UTF-8 + * + * @memory SAFE + * return caller-free + */ +static char * +xy_env_get (const char *name) +{ +#ifdef XY_Build_On_Windows + wchar_t *wname = _xy_win_wstr (name); + if (!wname) + return NULL; + + wchar_t *wval = _wgetenv (wname); + free (wname); + return wval ? _xy_win_utf8 (wval) : NULL; +#else + const char *val = getenv (name); + return val ? xy_strdup (val) : NULL; +#endif +} + + /** * @brief 读取文件内容并返回字符串,失败时返回空字符串 * @@ -873,7 +1080,7 @@ xy_str_next_nonempty_line (const char *str) static char * xy_file_read (const char *path) { - FILE *fp = fopen (path, "rb"); + FILE *fp = xy_fopen (path, "rb"); if (!fp) return xy_strdup (""); @@ -1126,7 +1333,7 @@ xy_run_iter_lines (const char *cmd, unsigned long n, bool (*func) (const char const int size = 512; char *buf = (char *) malloc (size); - FILE *stream = popen (cmd, "r"); + FILE *stream = xy_popen (cmd, "r"); if (stream == NULL) { _xy_internal_warn ("xy_run_iter_lines(): popen() failed"); @@ -1186,7 +1393,7 @@ xy_run_get_status (char *cmd) { char * command = xy_quiet_cmd (cmd); - int status = system (command); + int status = xy_system (command); free (command); return status; } @@ -1210,7 +1417,7 @@ xy_run_get_stdout (const char *cmd, char **output) int cap = 8192; /* 假如1行100个字符,大约支持80行输出 */ char *buf = (char *) xy_malloc0 (cap); - FILE *stream = popen (cmd, "r"); + FILE *stream = xy_popen (cmd, "r"); if (stream == NULL) { _xy_internal_warn ("xy_run_get_stdout(): popen() failed"); @@ -1272,11 +1479,12 @@ xy_os_depend_str (const char *str_for_win, const char *str_for_unix) static char * _xy_os_home () { - char *home = NULL; - if (xy.on_windows) - home = getenv ("USERPROFILE"); - else - home = getenv ("HOME"); + /* 进程级缓存:返回值生命周期与进程相同,调用者无需释放 */ + static char *home = NULL; + if (home) + return home; + + home = xy_env_get (xy.on_windows ? "USERPROFILE" : "HOME"); return home; } @@ -1294,12 +1502,12 @@ static char * _xy_win_documents () { #ifdef XY_Build_On_Windows - char documents_path[MAX_PATH]; - HRESULT result = SHGetFolderPathA (NULL, CSIDL_MYDOCUMENTS, NULL, + wchar_t documents_path[MAX_PATH]; + HRESULT result = SHGetFolderPathW (NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documents_path); if (SUCCEEDED (result)) - return xy_strdup (documents_path); + return _xy_win_utf8 (documents_path); return xy_2strcat (xy_os_home, "\\Documents"); #else @@ -1360,7 +1568,7 @@ _xy_win_powershellv5_profile () #define xy_fishrc "~/.config/fish/config.fish" /** - * @note Windows上,`path` 不要夹带变量名,因为最终 access() 不会帮你转换 + * @note Windows上,`path` 不要夹带变量名,因为最终不会帮你转换 * * @memory SAFE */ @@ -1376,8 +1584,7 @@ xy_file_exist (const char *path) check_path = expanded_path; } - // 0 即 F_OK - bool result = (0 == access (check_path, 0)) ? true : false; + bool result = xy_path_exist (check_path); if (expanded_path) free (expanded_path); return result; } @@ -1393,50 +1600,17 @@ xy_dir_exist (const char *path) { char *allocated_dir = NULL; const char *dir = path; - if (xy.on_windows) - { - if (xy_str_start_with (path, "~")) - { - allocated_dir = xy_2strcat (xy_os_home, path + 1); - dir = allocated_dir; - } - } - if (xy.on_windows) - { -#ifdef XY_Build_On_Windows - // 也可以用 opendir() #include - DWORD attr = GetFileAttributesA (dir); - - bool result = false; - if (attr == INVALID_FILE_ATTRIBUTES) - { - // Q: 我们应该报错吗? - result = false; - } - else if (attr & FILE_ATTRIBUTE_DIRECTORY) - { - result = true; - } - else - { - result = false; - } - if (allocated_dir) free (allocated_dir); - return result; -#endif - } - else + if (xy_str_start_with (path, "~")) { - char *tmp_cmd = xy_2strcat ("test -d ", dir); - int status = system (tmp_cmd); - free (tmp_cmd); - bool result = (0==status); - if (allocated_dir) free (allocated_dir); - return result; + allocated_dir = xy_2strcat (xy_os_home, path + 1); + dir = allocated_dir; } - return false; + bool result = xy_path_is_dir (dir); + + if (allocated_dir) free (allocated_dir); + return result; } /** @@ -1617,7 +1791,7 @@ _xy_detect_os () } /* 最后判断 BSD */ - fp = popen ("uname -s", "r"); + fp = xy_popen ("uname -s", "r"); if (fp) { char buf[256]; From 2285a11a9d7fd5976c88deb48d3cec888dad6cc8 Mon Sep 17 00:00:00 2001 From: Mikachu2333 Date: Wed, 28 Oct 2026 22:56:01 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8E=E5=91=BD=E4=BB=A4=E6=89=A7=E8=A1=8C=E6=94=B9=E8=B5=B0?= =?UTF-8?q?=E5=AE=BD=E5=AD=97=E7=AC=A6=E5=B0=81=E8=A3=85=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=9D=9E=20ASCII=20=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/framework/OS.c | 52 +++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/framework/OS.c b/src/framework/OS.c index c4033a38a..6e33d9fae 100644 --- a/src/framework/OS.c +++ b/src/framework/OS.c @@ -132,7 +132,7 @@ query_program_exist (char *check_cmd, char *prog_name, int mode) { char *which = check_cmd; - int status = system (which); + int status = xy_system (which); // char buf[32] = {0}; sprintf(buf, "错误码: %d", status); @@ -326,7 +326,7 @@ chsrc_run (const char *cmd, int run_option) return; // Dry Run 此时立即结束,并不真正执行 } - int status = system (cmd); + int status = xy_system (cmd); bool use_yellow_for_error = (run_option & RunOpt_Dont_Abort_On_Failure) != 0; if (0==status) @@ -383,7 +383,7 @@ chsrc_run_directly (const char *cmd) { return 0; // Dry Run 此时立即结束,并不真正执行 } - int status = system (cmd); + int status = xy_system (cmd); return status; } @@ -406,18 +406,19 @@ chsrc_make_tmpfile (char *filename, char *postfix, bool loud, char **tmpfilepath #ifdef XY_Build_On_Windows /** - * Windows 上使用 GetTempPath 和 GetTempFileName 创建临时文件 - * 这是 Windows API 推荐的标准方法 + * Windows 上使用 GetTempPathW 和 GetTempFileNameW 创建临时文件 * * 由于 GetTempFileName 不支持自定义后缀,我们需要: * 1. 使用 GetTempFileName 生成唯一的临时文件名 - * 2. 将其重命名为带有正确后缀的文件名(PowerShell 需要 .ps1 后缀) + * 2. 重建带正确后缀的文件名(PowerShell 需要 .ps1 后缀) + * + * 全程使用宽字符 API,临时目录含非 ASCII 字符时也能正常工作 */ - char temp_path[MAX_PATH]; - char temp_filename[MAX_PATH]; + wchar_t temp_path[MAX_PATH]; + wchar_t temp_filename[MAX_PATH]; /* 获取系统临时目录 */ - DWORD ret = GetTempPathA (MAX_PATH, temp_path); + DWORD ret = GetTempPathW (MAX_PATH, temp_path); if (ret == 0 || ret > MAX_PATH) { char *msg = CHINESE ? "无法获取系统临时目录" : "Unable to get system temp directory"; @@ -426,7 +427,7 @@ chsrc_make_tmpfile (char *filename, char *postfix, bool loud, char **tmpfilepath } /* 生成唯一的临时文件名 (会自动创建文件) */ - ret = GetTempFileNameA (temp_path, "chsrc_", 0, temp_filename); + ret = GetTempFileNameW (temp_path, L"chsrc_", 0, temp_filename); if (ret == 0) { char *msg = CHINESE ? "无法生成临时文件名" : "Unable to generate temporary filename"; @@ -434,13 +435,22 @@ chsrc_make_tmpfile (char *filename, char *postfix, bool loud, char **tmpfilepath exit (Exit_ExternalError); } - tmpfile = xy_strcat (4, temp_filename, "_", filename, postfix); + char *temp_filename_utf8 = _xy_win_utf8 (temp_filename); + if (!temp_filename_utf8) + { + char *msg = CHINESE ? "无法转换临时文件名" : "Unable to convert temporary filename"; + chsrc_error2 (msg); + exit (Exit_ExternalError); + } + + tmpfile = xy_strcat (4, temp_filename_utf8, "_", filename, postfix); + free (temp_filename_utf8); /* 删除 GetTempFileName 自动创建的文件 */ - DeleteFileA (temp_filename); + DeleteFileW (temp_filename); /* 创建带有正确后缀的文件 */ - f = fopen (tmpfile, "w+"); + f = xy_fopen (tmpfile, "w+"); if (!f) { @@ -523,7 +533,7 @@ chsrc_run_as_bash_file (const char *script_content) println (faint(script_content)); char *cmd = xy_2strcat ("bash ", tmpfile); chsrc_run (cmd, RunOpt_Dont_Abort_On_Failure); - remove (tmpfile); + xy_file_remove (tmpfile); free (tmpfile); /* 释放 tmpfile 路径内存 */ } @@ -544,7 +554,7 @@ chsrc_run_as_sh_file (const char *script_content) println (faint(script_content)); char *cmd = xy_2strcat ("sh ", tmpfile); chsrc_run (cmd, RunOpt_Dont_Abort_On_Failure); - remove (tmpfile); + xy_file_remove (tmpfile); free (tmpfile); } @@ -564,7 +574,7 @@ chsrc_run_as_pwsh_file (const char *script_content) println (faint(script_content)); char *cmd = xy_2strcat ("pwsh ", tmpfile); chsrc_run (cmd, RunOpt_Dont_Abort_On_Failure); - remove (tmpfile); + xy_file_remove (tmpfile); free (tmpfile); } @@ -585,7 +595,7 @@ chsrc_run_as_powershellv5_file (const char *script_content) // -ExecutionPolicy Bypass char *cmd = xy_2strcat ("powershell -File ", tmpfile); chsrc_run (cmd, RunOpt_Dont_Abort_On_Failure); - remove (tmpfile); + xy_file_remove (tmpfile); free (tmpfile); } @@ -686,7 +696,7 @@ chsrc_view_env (const char *var1, ...) * 这个错误过于醒目。我们应该像在 sh 一样,默默地没有输出即可,而不是报错 */ // chsrc_run (cmd, RunOpt_Dont_Notify_On_Success|RunOpt_No_Last_New_Line|RunOpt_Dont_Abort_On_Failure); - int status = system (cmd); + int status = xy_system (cmd); if (status!=0) { xy_noop(); } } else @@ -756,7 +766,7 @@ chsrc_append_to_file (const char *str, const char *filename) char *dir = xy_parent_dir (file); chsrc_ensure_dir (dir); - FILE *f = fopen (file, "a"); + FILE *f = xy_fopen (file, "a"); if (NULL==f) { char *msg = ENGLISH ? xy_2strcat ("Unable to open file to write: ", file) @@ -812,7 +822,7 @@ chsrc_prepend_to_file (const char *str, const char *filename) char *file_content = xy_file_read (file); char *content = xy_2strcat (str, file_content); - FILE *f = fopen (file, "w"); + FILE *f = xy_fopen (file, "w"); if (f) { @@ -842,7 +852,7 @@ chsrc_overwrite_file (const char *str, const char *filename) char *dir = xy_parent_dir (file); chsrc_ensure_dir (dir); - FILE *f = fopen (file, "w"); + FILE *f = xy_fopen (file, "w"); if (NULL==f) { char *msg = ENGLISH ? xy_2strcat ("Unable to open file to overwrite: ", file) From 3810c323014befba01f9acd85b473d468c5e06ca Mon Sep 17 00:00:00 2001 From: Mikachu2333 Date: Wed, 28 Oct 2026 22:56:02 +0800 Subject: [PATCH 3/3] =?UTF-8?q?Cargo=20=E4=B8=8E=20uv=20=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E5=AE=BD=E5=AD=97=E7=AC=A6=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= =?UTF-8?q?/=E6=96=87=E4=BB=B6=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recipe/lang/Python/uv/common.h | 3 ++- src/recipe/lang/Rust/Cargo.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/recipe/lang/Python/uv/common.h b/src/recipe/lang/Python/uv/common.h index 6028beefa..6b332fa4b 100644 --- a/src/recipe/lang/Python/uv/common.h +++ b/src/recipe/lang/Python/uv/common.h @@ -55,7 +55,7 @@ pl_uv_find_uv_config (bool mkdir) if (xy.on_windows) { // config path on Windows - char *appdata = getenv ("APPDATA"); + char *appdata = xy_env_get ("APPDATA"); if (!appdata) { @@ -64,6 +64,7 @@ pl_uv_find_uv_config (bool mkdir) } char *config_dir = xy_path_join (appdata, "uv"); + free (appdata); if (mkdir) { chsrc_ensure_dir (config_dir); diff --git a/src/recipe/lang/Rust/Cargo.c b/src/recipe/lang/Rust/Cargo.c index ab04a03f8..4ed23e7bf 100644 --- a/src/recipe/lang/Rust/Cargo.c +++ b/src/recipe/lang/Rust/Cargo.c @@ -80,7 +80,7 @@ static char * pl_rust_cargo_config_file (void) { char *file = NULL; - char *cargo_home = getenv ("CARGO_HOME"); + char *cargo_home = xy_env_get ("CARGO_HOME"); if (cargo_home && *cargo_home) { file = xy_path_join (cargo_home, "config.toml"); @@ -89,6 +89,7 @@ pl_rust_cargo_config_file (void) { file = xy_normalize_path ("~/.cargo/config.toml"); } + free (cargo_home); return file; } @@ -133,7 +134,7 @@ pl_rust_cargo_getsrc (char *option) void pl_write_rust_config (const char *path, const char *url) { - remove (path); + xy_file_remove (path); char *content = RAWSTR_pl_rust_cargo_config; content = xy_str_gsub (content, "@url@", url); chsrc_overwrite_file (content, path);