From 2ed101256414ea5274e85e6f3d4c011b46442ac4 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 5 Apr 2022 16:00:37 +0200 Subject: [PATCH] curl_version_info: add CURL_VERSION_THREADSAFE_INIT This flag can be used to make sure that curl_global_init() is thread-safe. This can be useful for libraries that can't control what other dependencies are doing with Curl. Closes #8680 --- configure.ac | 17 ++++++ docs/libcurl/curl_version_info.3 | 3 + docs/libcurl/symbols-in-versions | 1 + include/curl/curl.h | 8 ++- lib/version.c | 4 ++ packages/OS400/curl.inc.in | 2 + src/tool_help.c | 1 + tests/data/Makefile.inc | 2 +- tests/libtest/Makefile.inc | 6 +- tests/libtest/lib3026.c | 101 +++++++++++++++++++++++++++++++ 10 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 tests/libtest/lib3026.c diff --git a/configure.ac b/configure.ac index 009f32c321..ce66b5592f 100644 --- a/configure.ac +++ b/configure.ac @@ -4258,6 +4258,23 @@ if test ${ac_cv_sizeof_curl_off_t} -gt 4; then fi fi +if test "$tst_atomic" = "yes"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES threadsafe-init" +else + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ + #include + ]],[[ + #if (WINVER < 0x600) && (_WIN32_WINNT < 0x600) + #error + #endif + ]]) + ],[ + SUPPORT_FEATURES="$SUPPORT_FEATURES threadsafe-init" + ],[ + ]) +fi + dnl replace spaces with newlines dnl sort the lines dnl replace the newlines back to spaces diff --git a/docs/libcurl/curl_version_info.3 b/docs/libcurl/curl_version_info.3 index ec8990c9cd..b1bb5b130f 100644 --- a/docs/libcurl/curl_version_info.3 +++ b/docs/libcurl/curl_version_info.3 @@ -204,6 +204,9 @@ libcurl was built with support for SSPI. This is only available on Windows and makes libcurl use Windows-provided functions for Kerberos, NTLM, SPNEGO and Digest authentication. It also allows libcurl to use the current user credentials without the app having to pass them on. (Added in 7.13.2) +.IP CURL_VERSION_THREADSAFE_INIT +libcurl was built with thread-safety support (Atomic or SRWLOCK) to protect +curl initialisation. (Added in 7.84.0) .IP CURL_VERSION_TLSAUTH_SRP libcurl was built with support for TLS-SRP (in one or more of the built-in TLS backends). (Added in 7.21.4) diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index d7c88ccde2..d8de1607c5 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -172,6 +172,7 @@ CURL_VERSION_PSL 7.47.0 CURL_VERSION_SPNEGO 7.10.8 CURL_VERSION_SSL 7.10 CURL_VERSION_SSPI 7.13.2 +CURL_VERSION_THREADSAFE_INIT 7.84.0 CURL_VERSION_TLSAUTH_SRP 7.21.4 CURL_VERSION_UNICODE 7.72.0 CURL_VERSION_UNIX_SOCKETS 7.40.0 diff --git a/include/curl/curl.h b/include/curl/curl.h index cb26d24d50..13f01a0178 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -2607,8 +2607,10 @@ CURL_EXTERN void curl_free(void *p); * * curl_global_init() should be invoked exactly once for each application that * uses libcurl and before any call of other libcurl functions. - * - * This function is not thread-safe! + + * This function is thread-safe if CURL_VERSION_THREADSAFE_INIT is set in the + * curl_version_info_data.features flag (fetch by curl_version_info()). + */ CURL_EXTERN CURLcode curl_global_init(long flags); @@ -3025,6 +3027,8 @@ typedef struct curl_version_info_data curl_version_info_data; #define CURL_VERSION_UNICODE (1<<27) /* Unicode support on Windows */ #define CURL_VERSION_HSTS (1<<28) /* HSTS is supported */ #define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */ +#define CURL_VERSION_THREADSAFE_INIT (1<<30) /* curl_global_init/cleanup() are + thread-safe */ /* * NAME curl_version_info() diff --git a/lib/version.c b/lib/version.c index e37253df2a..eb1bcb3a7d 100644 --- a/lib/version.c +++ b/lib/version.c @@ -29,6 +29,7 @@ #include "vssh/ssh.h" #include "quic.h" #include "curl_printf.h" +#include "easy_lock.h" #ifdef USE_ARES # if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ @@ -450,6 +451,9 @@ static curl_version_info_data version_info = { #endif #if defined(USE_GSASL) | CURL_VERSION_GSASL +#endif +#if defined(GLOBAL_INIT_IS_THREADSAFE) + | CURL_VERSION_THREADSAFE_INIT #endif , NULL, /* ssl_version */ diff --git a/packages/OS400/curl.inc.in b/packages/OS400/curl.inc.in index 33f316d1c8..a2be30dc21 100644 --- a/packages/OS400/curl.inc.in +++ b/packages/OS400/curl.inc.in @@ -148,6 +148,8 @@ d c X'10000000' d CURL_VERSION_GSASL... d c X'20000000' + d CURL_VERSION_THREADSAFE_INIT... + d c X'40000000' * d CURL_HTTPPOST_FILENAME... d c X'00000001' diff --git a/src/tool_help.c b/src/tool_help.c index 7998532828..64db44c792 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -110,6 +110,7 @@ static const struct feat feats[] = { {"alt-svc", CURL_VERSION_ALTSVC}, {"HSTS", CURL_VERSION_HSTS}, {"gsasl", CURL_VERSION_GSASL}, + {"threadsafe-init",CURL_VERSION_THREADSAFE_INIT}, }; static void print_category(curlhelp_t category) diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 1a4d4eeeff..2196e2fd97 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -245,4 +245,4 @@ test2200 test2201 test2202 test2203 test2204 test2205 \ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \ -test3024 test3025 +test3024 test3025 test3026 diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 06ad0e8505..2f9aa2f91c 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -63,7 +63,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \ lib1915 lib1916 lib1917 lib1918 lib1919 \ lib1933 lib1934 lib1935 lib1936 lib1937 lib1938 lib1939 lib1940 \ lib1945 lib1946 \ - lib3010 lib3025 + lib3010 lib3025 lib3026 chkdecimalpoint_SOURCES = chkdecimalpoint.c ../../lib/mprintf.c \ ../../lib/curl_ctype.c ../../lib/dynbuf.c ../../lib/strdup.c @@ -747,3 +747,7 @@ lib3010_CPPFLAGS = $(AM_CPPFLAGS) lib3025_SOURCES = lib3025.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib3025_LDADD = $(TESTUTIL_LIBS) lib3025_CPPFLAGS = $(AM_CPPFLAGS) + +lib3026_SOURCES = lib3026.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib3026_LDADD = $(TESTUTIL_LIBS) +lib3026_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/libtest/lib3026.c b/tests/libtest/lib3026.c new file mode 100644 index 0000000000..954b419171 --- /dev/null +++ b/tests/libtest/lib3026.c @@ -0,0 +1,101 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, 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. + * + ***************************************************************************/ +#include "test.h" + +#include "testutil.h" +#include "warnless.h" + +#ifdef HAVE_PTHREAD_H +#include +#include + +#define NUM_THREADS 1000 + +static void *run_thread(void *ptr) +{ + CURLcode *result = ptr; + + *result = curl_global_init(CURL_GLOBAL_ALL); + if(*result == CURLE_OK) + curl_global_cleanup(); + + return NULL; +} + +int test(char *URL) +{ + CURLcode results[NUM_THREADS]; + pthread_t tids[NUM_THREADS]; + unsigned tid_count = NUM_THREADS, i; + int test_failure = 0; + curl_version_info_data *ver; + (void) URL; + + ver = curl_version_info(CURLVERSION_NOW); + if((ver->features & CURL_VERSION_THREADSAFE_INIT) == 0) { + fprintf(stderr, "%s:%d Have pthread but the " + "CURL_VERSION_THREADSAFE_INIT feature flag is not set\n", + __FILE__, __LINE__); + return -1; + } + + for(i = 0; i < tid_count; i++) { + int res = pthread_create(&tids[i], NULL, run_thread, &results[i]); + if(res) { + fprintf(stderr, "%s:%d Couldn't create thread, errno %d\n", + __FILE__, __LINE__, res); + tid_count = i; + test_failure = -1; + goto cleanup; + } + } + +cleanup: + for(i = 0; i < tid_count; i++) { + pthread_join(tids[i], NULL); + if(results[i] != CURLE_OK) { + fprintf(stderr, "%s:%d thread[%u]: curl_global_init() failed," + "with code %d (%s)\n", __FILE__, __LINE__, + i, (int) results[i], curl_easy_strerror(results[i])); + test_failure = -1; + } + } + + return test_failure; +} + +#else /* without pthread, this test doesn't work */ +int test(char *URL) +{ + curl_version_info_data *ver; + (void)URL; + + ver = curl_version_info(CURLVERSION_NOW); + if((ver->features & CURL_VERSION_THREADSAFE_INIT) != 0) { + fprintf(stderr, "%s:%d No pthread but the " + "CURL_VERSION_THREADSAFE_INIT feature flag is set\n", + __FILE__, __LINE__); + return -1; + } + return 0; +} +#endif