From c0a6e4270f89d599d5e43908de0b28e4b06bb0a1 Mon Sep 17 00:00:00 2001 From: Yamagi Burmeister Date: Tue, 11 Dec 2018 14:11:30 +0100 Subject: [PATCH] Load libcurl.so at runtime. Loading libcurl at runtime instead of linking it at compile time makes things a lot easier and more reliable on Windows. On other platform libcurl can be installed as optional dependency instead as an hard one. --- Makefile | 6 +- src/client/cl_http.c | 85 ++++++++----- src/client/curl/header/qcurl.h | 70 +++++++++++ src/client/curl/qcurl.c | 221 +++++++++++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 33 deletions(-) create mode 100644 src/client/curl/header/qcurl.h create mode 100644 src/client/curl/qcurl.c diff --git a/Makefile b/Makefile index c57b9eb9..15919fc6 100755 --- a/Makefile +++ b/Makefile @@ -352,6 +352,10 @@ build/client/%.o: %.c release/yquake2.exe : LDFLAGS += -mwindows +ifeq ($(WITH_CURL),yes) +release/quake2 : CFLAGS += -DUSE_CURL +endif + ifeq ($(WITH_OPENAL),yes) release/yquake2.exe : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"openal32.dll"' endif @@ -379,7 +383,6 @@ release/quake2 : CFLAGS += -Wno-unused-result ifeq ($(WITH_CURL),yes) release/quake2 : CFLAGS += -DUSE_CURL -release/quake2 : LDFLAGS += -lcurl endif ifeq ($(WITH_OPENAL),yes) @@ -690,6 +693,7 @@ CLIENT_OBJS_ := \ src/client/cl_screen.o \ src/client/cl_tempentities.o \ src/client/cl_view.o \ + src/client/curl/qcurl.o \ src/client/input/sdl.o \ src/client/menu/menu.o \ src/client/menu/qmenu.o \ diff --git a/src/client/cl_http.c b/src/client/cl_http.c index 6612cbcf..c332170b 100644 --- a/src/client/cl_http.c +++ b/src/client/cl_http.c @@ -27,6 +27,7 @@ #include #include "header/client.h" +#include "curl/header/qcurl.h" #ifdef USE_CURL @@ -66,7 +67,7 @@ static size_t CL_HTTP_Recv(void *ptr, size_t size, size_t nmemb, void *stream) { double length = 0; - curl_easy_getinfo(dl->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); + qcurl_easy_getinfo(dl->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); // Mkay, the remote file should be at least one byte long. // Since this is used for paclists only we assume that the @@ -198,33 +199,33 @@ static void CL_StartHTTPDownload (dlqueue_t *entry, dlhandle_t *dl) // Setup and configure the CURL part of our download handle. if (!dl->curl) { - dl->curl = curl_easy_init(); + dl->curl = qcurl_easy_init(); } Com_sprintf(dl->URL, sizeof(dl->URL), "%s%s", cls.downloadServer, escapedFilePath); - curl_easy_setopt(dl->curl, CURLOPT_ENCODING, ""); + qcurl_easy_setopt(dl->curl, CURLOPT_ENCODING, ""); if (dl->file) { - curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file); - curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL); + qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file); + qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL); } else { - curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl); - curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv); + qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl); + qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv); } - curl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string); - curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5); - curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl); - curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version")); - curl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer); - curl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL); + qcurl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string); + qcurl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5); + qcurl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl); + qcurl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version")); + qcurl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer); + qcurl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL); - if (curl_multi_add_handle (multi, dl->curl) != CURLM_OK) + if (qcurl_multi_add_handle(multi, dl->curl) != CURLM_OK) { Com_Printf("HTTP download: cURL error\n"); dl->queueEntry->state = DLQ_STATE_DONE; @@ -473,7 +474,7 @@ static void CL_FinishHTTPDownload(void) do { // Get a message from CURL. - CURLMsg *msg = curl_multi_info_read(multi, &msgs_in_queue); + CURLMsg *msg = qcurl_multi_info_read(multi, &msgs_in_queue); if (!msg) { @@ -551,7 +552,7 @@ static void CL_FinishHTTPDownload(void) { case CURLE_HTTP_RETURNED_ERROR: case CURLE_OK: - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); + qcurl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); if (responseCode == 404) { @@ -564,7 +565,7 @@ static void CL_FinishHTTPDownload(void) } // ...and remove it from the CURL multihandle. - curl_multi_remove_handle(multi, dl->curl); + qcurl_multi_remove_handle(multi, dl->curl); Com_Printf("HTTP download: %s - File Not Found\n", dl->queueEntry->quakePath); @@ -597,7 +598,7 @@ static void CL_FinishHTTPDownload(void) } // ...and the handle from CURLs mutihandle. - curl_multi_remove_handle (multi, dl->curl); + qcurl_multi_remove_handle(multi, dl->curl); // Special case: We're already aborting HTTP downloading, // so we can't just kill everything. Otherwise we'll get @@ -614,7 +615,7 @@ static void CL_FinishHTTPDownload(void) break; default: - Com_Printf ("HTTP download: cURL error - %s\n", curl_easy_strerror(result)); + Com_Printf ("HTTP download: cURL error - %s\n", qcurl_easy_strerror(result)); i = strlen(dl->queueEntry->quakePath); @@ -625,7 +626,7 @@ static void CL_FinishHTTPDownload(void) } // ...and the handle from CURLs mutihandle. - curl_multi_remove_handle (multi, dl->curl); + qcurl_multi_remove_handle (multi, dl->curl); continue; break; @@ -651,7 +652,7 @@ static void CL_FinishHTTPDownload(void) } // Remove the file fo CURLs multihandle. - curl_multi_remove_handle (multi, dl->curl); + qcurl_multi_remove_handle (multi, dl->curl); } while (msgs_in_queue > 0); @@ -736,7 +737,11 @@ static void CL_StartNextHTTPDownload(void) */ void CL_InitHTTPDownloads (void) { - curl_global_init (CURL_GLOBAL_NOTHING); + // We're initializing the cURL backend here because + // currently we're the only user. As soon as there + // are other users this must be moved up into the + // global client intialization. + qcurlInit(); } /* @@ -771,8 +776,11 @@ void CL_HTTP_Cleanup(qboolean fullShutdown) if (dl->curl) { if (multi) - curl_multi_remove_handle (multi, dl->curl); - curl_easy_cleanup (dl->curl); + { + qcurl_multi_remove_handle(multi, dl->curl); + } + + qcurl_easy_cleanup(dl->curl); dl->curl = NULL; } @@ -782,14 +790,17 @@ void CL_HTTP_Cleanup(qboolean fullShutdown) // Cleanup CURL multihandle. if (multi) { - curl_multi_cleanup(multi); + qcurl_multi_cleanup(multi); multi = NULL; } // Shutdown CURL. if (fullShutdown) { - curl_global_cleanup (); + // This must be moved up into the generic + // client shutdown as soon as there're + // other users of the cURL backend. + qcurlShutdown(); httpDown = true; } } @@ -807,6 +818,16 @@ void CL_SetHTTPServer (const char *URL) dlqueue_t *last = NULL; dlqueue_t *q = &cls.downloadQueue; + // This code abuses the download server setting to + // determine if HTTP downloads are possible. So if + // we don't set a download server, no downloads are + // queued and run. :) + if (!qcurlInitialized) + { + cls.downloadServer[0] = 0; + return; + } + CL_HTTP_Cleanup(false); // Cleanup download queues. @@ -841,7 +862,7 @@ void CL_SetHTTPServer (const char *URL) Com_Error(ERR_DROP, "HTTP download: Still have old handle?!"); } - multi = curl_multi_init(); + multi = qcurl_multi_init(); } /* @@ -981,10 +1002,10 @@ qboolean CL_PendingHTTPDownloads(void) */ void CL_RunHTTPDownloads(void) { - int newHandleCount; - CURLMcode ret; + int newHandleCount; + CURLMcode ret; - // No HTTP server given. + // No HTTP server given or not initialized. if (!cls.downloadServer[0]) { return; @@ -993,7 +1014,7 @@ void CL_RunHTTPDownloads(void) // Kick CURL into action. do { - ret = curl_multi_perform(multi, &newHandleCount); + ret = qcurl_multi_perform(multi, &newHandleCount); if (newHandleCount < handleCount) { diff --git a/src/client/curl/header/qcurl.h b/src/client/curl/header/qcurl.h new file mode 100644 index 00000000..ce410edf --- /dev/null +++ b/src/client/curl/header/qcurl.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 Yamagi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ======================================================================= + * + * CURL backend for Quake II. + * + * ======================================================================= + */ + +#ifdef USE_CURL + +#ifndef QCURL_H +#define QCURL_H + +// -------- + +#include +#include "../../../common/header/common.h" + +// -------- + +// True if cURL is initialized. +extern qboolean qcurlInitialized; + +// Function pointers to cURL. +extern void (*qcurl_easy_cleanup)(CURL *curl); +extern CURL *(*qcurl_easy_init)(void); +extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +extern const char *(*qcurl_easy_strerror)(CURLcode); + +extern void (*qcurl_global_cleanup)(void); +extern CURLcode (*qcurl_global_init)(long flags); + +extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle); +extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +extern CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue); +extern CURLM *(*qcurl_multi_init)(void); +extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles); +extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle); + +// -------- + +// Loads and initialized cURL. +qboolean qcurlInit(void); + +// Shuts cURL down and unloads it. +void qcurlShutdown(void); + +// -------- + +#endif // QCURL_H +#endif // USE_CURL diff --git a/src/client/curl/qcurl.c b/src/client/curl/qcurl.c new file mode 100644 index 00000000..490db29d --- /dev/null +++ b/src/client/curl/qcurl.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2018 Yamagi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ======================================================================= + * + * CURL backend for Quake II. + * + * ======================================================================= + */ + +#ifdef USE_CURL + +// -------- + +#include + +#include "header/qcurl.h" +#include "../../common/header/common.h" + +// -------- + +// True if cURL is initialized. +qboolean qcurlInitialized; + +// Pointer to the dynamic library. +static void *curlhandle; + +// CVars +static cvar_t *cl_libcurl; + +// Function pointers to cURL. +void (*qcurl_easy_cleanup)(CURL *curl); +CURL *(*qcurl_easy_init)(void); +CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +const char *(*qcurl_easy_strerror)(CURLcode); + +void (*qcurl_global_cleanup)(void); +CURLcode (*qcurl_global_init)(long flags); + +CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle); +CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue); +CURLM *(*qcurl_multi_init)(void); +CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles); +CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle); + +// -------- + +/* + * Load libcurl, connect the function pointer + * and call cURLs global init function. + */ +qboolean qcurlInit(void) +{ + Com_Printf("------- curl initialization -------\n"); + + assert(!qcurlInitialized && "cURL already initialized?!"); + + // Most systems have only one distinct name for the + // libcurl, loading that one will be successfull in + // at least 99% of all cases. But Linux, the mother + // of chaos, is more complicated. Debian / Ubuntu + // alone has 6 possible names... So: + // - Let's try whatever the user has written into + // the cl_libcurl cvar first. + // - If that fails try all possible names until we + // find a working one. + // - If we found one put it into cl_libcurl, so we + // don't need to iterate trought the whole list + // the next time the game is started. + // - If we found none error out. +#ifdef __APPLE__ + const char *libcurl[] = { "libcurl.dylib", NULL }; +#elif __linux__ + const char *libcurl[] = { "libcurl.so", "libcurl.so.3", "libcurl.so.4", "libcurl-gnutls.so.3", + "libcurl-gnutls.so.4", "libcurl-nss.so.3", "libcurl-nss.so.4", NULL }; +#elif _WIN32 + const char *libcurl[] = { "curl.dll", NULL }; +#else + const char *libcurl[] = { "libcurl.so", NULL }; +#endif + + // Mkay, let's try to find a working libcurl. + cl_libcurl = Cvar_Get("cl_libcurl", (char *)libcurl[0], CVAR_ARCHIVE); + + Com_Printf("LoadLibrary(%s)\n", cl_libcurl->string); + Sys_LoadLibrary(cl_libcurl->string, NULL, &curlhandle); + + if (!curlhandle) + { + Com_Printf("Loading %s failed!\n", cl_libcurl->string); + + for (int i = 0; libcurl[i] != NULL; i++) + { + // Tried that one already. + if (!strcmp(cl_libcurl->string, libcurl[i])) + { + continue; + } + + Com_Printf("LoadLibrary(%s)\n", libcurl[i]); + Sys_LoadLibrary(libcurl[i], NULL, &curlhandle); + + if (!curlhandle) + { + // No luck with this one. + Com_Printf("Loading %s failed!\n", libcurl[i]); + continue; + } + else + { + // Got one! + Cvar_Set("cl_libcurl", (char *)libcurl[i]); + break; + } + } + + if (!curlhandle) + { + goto error; + } + } + + // libcurl loaded sucessfully, connect the pointers. + #define CONCURL(var, sym) do { \ + var = Sys_GetProcAddress(curlhandle, sym); \ + if (!var) goto error; \ + } while(0) + + CONCURL(qcurl_easy_cleanup, "curl_easy_cleanup"); + CONCURL(qcurl_easy_init, "curl_easy_init"); + CONCURL(qcurl_easy_getinfo, "curl_easy_getinfo"); + CONCURL(qcurl_easy_setopt, "curl_easy_setopt"); + CONCURL(qcurl_easy_strerror, "curl_easy_strerror"); + + CONCURL(qcurl_global_cleanup, "curl_global_cleanup"); + CONCURL(qcurl_global_init, "curl_global_init"); + + CONCURL(qcurl_multi_add_handle, "curl_multi_add_handle"); + CONCURL(qcurl_multi_cleanup, "curl_multi_cleanup"); + CONCURL(qcurl_multi_info_read, "curl_multi_info_read"); + CONCURL(qcurl_multi_init, "curl_multi_init"); + CONCURL(qcurl_multi_perform, "curl_multi_perform"); + CONCURL(qcurl_multi_remove_handle, "curl_multi_remove_handle"); + + #undef CONCURL + + // And finally the global cURL initialization. + qcurl_global_init(CURL_GLOBAL_NOTHING); + qcurlInitialized = true; + + Com_Printf("------------------------------------\n\n"); + + return true; + +error: + qcurlShutdown(); + + Com_Printf("------------------------------------\n\n"); + + return false; +} + +/* + * Calls cURLs global shutdown funtion, + * unloads libcurl and set the function + * pointers to NULL. + */ +void qcurlShutdown(void) +{ + if (qcurlInitialized) + { + Com_Printf("Shutting down curl.\n"); + + qcurl_global_cleanup(); + qcurlInitialized = false; + } + + qcurl_easy_cleanup = NULL; + qcurl_easy_init = NULL; + qcurl_easy_getinfo = NULL; + qcurl_easy_setopt = NULL; + qcurl_easy_strerror = NULL; + + qcurl_global_cleanup = NULL; + qcurl_global_init = NULL; + + qcurl_multi_add_handle = NULL; + qcurl_multi_cleanup = NULL; + qcurl_multi_info_read = NULL; + qcurl_multi_init = NULL; + qcurl_multi_perform = NULL; + qcurl_multi_remove_handle = NULL; + + if (curlhandle) + { + Sys_FreeLibrary(curlhandle); + curlhandle = NULL; + } +} + +// -------- + +#endif // USE_CURL