From 6d0ee7b17b0c37c034a78314eda4148104187689 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 20 Dec 2025 01:44:46 +0100 Subject: [PATCH] curlx: add `curlx_rename()`, fix to support long filenames on Windows Move existing `Curl_rename()` `rename()` wrapper from lib to curlx/fopen, and make it a curlx macro/function. To allow using the local worker function to fixup long filenames on Windows. Then fix the Windows-specific rename implementation to support long filenames. This operation may happen when using a cookie jar, HSTS cache or alt-svc cache, via libcurl or the curl tool. Before this patch, when passing a long filename to the above options, a `.tmp` file was left on the disk without renaming it to the filename passed to curl. There was also 1 second delay for each attempted rename operation. Also: - checksrc: ban raw `rename()` and `MoveFileEx*()` functions. - Note: `Curl_rename()` returned 1 on failure before this patch, while `curlx_rename()` returns -1 after, to match POSIX `rename()`. Refs: https://learn.microsoft.com/windows/win32/api/winbase/nf-winbase-movefileexa https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation Ref: #20040 Closes #20042 --- docs/internals/CODE_STYLE.md | 4 +++ lib/Makefile.inc | 2 -- lib/altsvc.c | 3 +- lib/cookie.c | 3 +- lib/curlx/fopen.c | 67 ++++++++++++++++++++++++++++++++++++ lib/curlx/fopen.h | 3 ++ lib/hsts.c | 3 +- lib/rename.c | 67 ------------------------------------ lib/rename.h | 29 ---------------- scripts/checksrc.pl | 4 +++ 10 files changed, 81 insertions(+), 104 deletions(-) delete mode 100644 lib/rename.c delete mode 100644 lib/rename.h diff --git a/docs/internals/CODE_STYLE.md b/docs/internals/CODE_STYLE.md index 9a2709885a..ee5bdbb562 100644 --- a/docs/internals/CODE_STYLE.md +++ b/docs/internals/CODE_STYLE.md @@ -375,12 +375,16 @@ This is the full list of functions generally banned. localtime malloc mbstowcs + MoveFileEx + MoveFileExA + MoveFileExW msnprintf mvsnprintf open printf realloc recv + rename send snprintf socket diff --git a/lib/Makefile.inc b/lib/Makefile.inc index cdd5587ec8..bc2bdb0c36 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -237,7 +237,6 @@ LIB_CFILES = \ psl.c \ rand.c \ ratelimit.c \ - rename.c \ request.c \ rtsp.c \ select.c \ @@ -366,7 +365,6 @@ LIB_HFILES = \ psl.h \ rand.h \ ratelimit.h \ - rename.h \ request.h \ rtsp.h \ select.h \ diff --git a/lib/altsvc.c b/lib/altsvc.c index df6b2a05f6..d40b9d0e46 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -35,7 +35,6 @@ #include "parsedate.h" #include "sendf.h" #include "curlx/warnless.h" -#include "rename.h" #include "strdup.h" #include "curlx/inet_pton.h" #include "curlx/strparse.h" @@ -379,7 +378,7 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data, break; } curlx_fclose(out); - if(!result && tempstore && Curl_rename(tempstore, file)) + if(!result && tempstore && curlx_rename(tempstore, file)) result = CURLE_WRITE_ERROR; if(result && tempstore) diff --git a/lib/cookie.c b/lib/cookie.c index 0310fb0c6d..05a49f2d5e 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -37,7 +37,6 @@ #include "curl_get_line.h" #include "curl_memrchr.h" #include "parsedate.h" -#include "rename.h" #include "strdup.h" #include "llist.h" #include "bufref.h" @@ -1534,7 +1533,7 @@ static CURLcode cookie_output(struct Curl_easy *data, if(!use_stdout) { curlx_fclose(out); out = NULL; - if(tempstore && Curl_rename(tempstore, filename)) { + if(tempstore && curlx_rename(tempstore, filename)) { error = CURLE_WRITE_ERROR; goto error; } diff --git a/lib/curlx/fopen.c b/lib/curlx/fopen.c index c1399135b3..ac2552a1c3 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/fopen.c @@ -44,6 +44,7 @@ int curlx_fseek(void *stream, curl_off_t offset, int whence) #include /* for _SH_DENYNO */ #include "multibyte.h" +#include "timeval.h" #ifdef CURLDEBUG /* @@ -436,6 +437,72 @@ int curlx_win32_stat(const char *path, struct_stat *buffer) return result; } +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \ + !defined(CURL_DISABLE_ALTSVC) +/* rename() on Windows does not overwrite, so we cannot use it here. + MoveFileEx() will overwrite and is usually atomic, however it fails + when there are open handles to the file. */ +int curlx_win32_rename(const char *oldpath, const char *newpath) +{ + int res = -1; /* fail */ + +#ifdef UNICODE + TCHAR *tchar_oldpath = curlx_convert_UTF8_to_wchar(oldpath); + TCHAR *tchar_newpath = curlx_convert_UTF8_to_wchar(newpath); +#else + const TCHAR *tchar_oldpath = oldpath; + const TCHAR *tchar_newpath = newpath; +#endif + + if(tchar_oldpath && tchar_newpath) { + const int max_wait_ms = 1000; + struct curltime start; + + TCHAR *oldpath_fixed = NULL; + TCHAR *newpath_fixed = NULL; + const TCHAR *target_oldpath; + const TCHAR *target_newpath; + + if(fix_excessive_path(tchar_oldpath, &oldpath_fixed)) + target_oldpath = oldpath_fixed; + else + target_oldpath = tchar_oldpath; + + if(fix_excessive_path(tchar_newpath, &newpath_fixed)) + target_newpath = newpath_fixed; + else + target_newpath = tchar_newpath; + + start = curlx_now(); + + for(;;) { + timediff_t diff; + /* !checksrc! disable BANNEDFUNC 1 */ + if(MoveFileEx(target_oldpath, target_newpath, + MOVEFILE_REPLACE_EXISTING)) { + res = 0; /* success */ + break; + } + diff = curlx_timediff_ms(curlx_now(), start); + if(diff < 0 || diff > max_wait_ms) { + break; + } + Sleep(1); + } + + CURLX_FREE(oldpath_fixed); + CURLX_FREE(newpath_fixed); + } + +#ifdef UNICODE + curlx_free(tchar_oldpath); + curlx_free(tchar_newpath); +#endif + + return res; +} +#endif + #undef CURLX_MALLOC #undef CURLX_FREE diff --git a/lib/curlx/fopen.h b/lib/curlx/fopen.h index bc10812007..4cda50df1c 100644 --- a/lib/curlx/fopen.h +++ b/lib/curlx/fopen.h @@ -48,15 +48,18 @@ FILE *curlx_win32_fopen(const char *filename, const char *mode); FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fh); int curlx_win32_stat(const char *path, struct_stat *buffer); int curlx_win32_open(const char *filename, int oflag, ...); +int curlx_win32_rename(const char *oldpath, const char *newpath); #define CURLX_FOPEN_LOW(fname, mode) curlx_win32_fopen(fname, mode) #define CURLX_FREOPEN_LOW(fname, mode, fh) curlx_win32_freopen(fname, mode, fh) #define curlx_stat(fname, stp) curlx_win32_stat(fname, stp) #define curlx_open curlx_win32_open +#define curlx_rename curlx_win32_rename #else #define CURLX_FOPEN_LOW fopen #define CURLX_FREOPEN_LOW freopen #define curlx_stat(fname, stp) stat(fname, stp) #define curlx_open open +#define curlx_rename rename #endif #ifdef CURLDEBUG diff --git a/lib/hsts.c b/lib/hsts.c index 2dd22a8568..e757574f51 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -35,7 +35,6 @@ #include "curl_get_line.h" #include "sendf.h" #include "parsedate.h" -#include "rename.h" #include "curl_share.h" #include "strdup.h" #include "curlx/strparse.h" @@ -362,7 +361,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, break; } curlx_fclose(out); - if(!result && tempstore && Curl_rename(tempstore, file)) + if(!result && tempstore && curlx_rename(tempstore, file)) result = CURLE_WRITE_ERROR; if(result && tempstore) diff --git a/lib/rename.c b/lib/rename.c deleted file mode 100644 index c2f979a16f..0000000000 --- a/lib/rename.c +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if (!defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES)) || \ - !defined(CURL_DISABLE_ALTSVC) - -#include "curlx/multibyte.h" -#include "curlx/timeval.h" -#include "rename.h" - -/* return 0 on success, 1 on error */ -int Curl_rename(const char *oldpath, const char *newpath) -{ -#ifdef _WIN32 - /* rename() on Windows does not overwrite, so we cannot use it here. - MoveFileEx() will overwrite and is usually atomic, however it fails - when there are open handles to the file. */ - const int max_wait_ms = 1000; - struct curltime start = curlx_now(); - TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar(oldpath); - TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar(newpath); - for(;;) { - timediff_t diff; - if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) { - curlx_free(tchar_oldpath); - curlx_free(tchar_newpath); - break; - } - diff = curlx_timediff_ms(curlx_now(), start); - if(diff < 0 || diff > max_wait_ms) { - curlx_free(tchar_oldpath); - curlx_free(tchar_newpath); - return 1; - } - Sleep(1); - } -#else - if(rename(oldpath, newpath)) - return 1; -#endif - return 0; -} - -#endif diff --git a/lib/rename.h b/lib/rename.h deleted file mode 100644 index 04440820c5..0000000000 --- a/lib/rename.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef HEADER_CURL_RENAME_H -#define HEADER_CURL_RENAME_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -int Curl_rename(const char *oldpath, const char *newpath); - -#endif /* HEADER_CURL_RENAME_H */ diff --git a/scripts/checksrc.pl b/scripts/checksrc.pl index 978959008f..33cd4bb246 100755 --- a/scripts/checksrc.pl +++ b/scripts/checksrc.pl @@ -92,12 +92,16 @@ my %banfunc = ( "localtime" => 1, "malloc" => 1, "mbstowcs" => 1, + "MoveFileEx" => 1, + "MoveFileExA" => 1, + "MoveFileExW" => 1, "msnprintf" => 1, "mvsnprintf" => 1, "open" => 1, "printf" => 1, "realloc" => 1, "recv" => 1, + "rename" => 1, "send" => 1, "snprintf" => 1, "socket" => 1,