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.
This commit is contained in:
Yamagi Burmeister 2018-12-11 14:11:30 +01:00
parent cd1b67e489
commit c0a6e4270f
4 changed files with 349 additions and 33 deletions

View file

@ -352,6 +352,10 @@ build/client/%.o: %.c
release/yquake2.exe : LDFLAGS += -mwindows release/yquake2.exe : LDFLAGS += -mwindows
ifeq ($(WITH_CURL),yes)
release/quake2 : CFLAGS += -DUSE_CURL
endif
ifeq ($(WITH_OPENAL),yes) ifeq ($(WITH_OPENAL),yes)
release/yquake2.exe : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"openal32.dll"' release/yquake2.exe : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"openal32.dll"'
endif endif
@ -379,7 +383,6 @@ release/quake2 : CFLAGS += -Wno-unused-result
ifeq ($(WITH_CURL),yes) ifeq ($(WITH_CURL),yes)
release/quake2 : CFLAGS += -DUSE_CURL release/quake2 : CFLAGS += -DUSE_CURL
release/quake2 : LDFLAGS += -lcurl
endif endif
ifeq ($(WITH_OPENAL),yes) ifeq ($(WITH_OPENAL),yes)
@ -690,6 +693,7 @@ CLIENT_OBJS_ := \
src/client/cl_screen.o \ src/client/cl_screen.o \
src/client/cl_tempentities.o \ src/client/cl_tempentities.o \
src/client/cl_view.o \ src/client/cl_view.o \
src/client/curl/qcurl.o \
src/client/input/sdl.o \ src/client/input/sdl.o \
src/client/menu/menu.o \ src/client/menu/menu.o \
src/client/menu/qmenu.o \ src/client/menu/qmenu.o \

View file

@ -27,6 +27,7 @@
#include <ctype.h> #include <ctype.h>
#include "header/client.h" #include "header/client.h"
#include "curl/header/qcurl.h"
#ifdef USE_CURL #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; 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. // Mkay, the remote file should be at least one byte long.
// Since this is used for paclists only we assume that the // 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. // Setup and configure the CURL part of our download handle.
if (!dl->curl) 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); 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) if (dl->file)
{ {
curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file); qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL); qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
} }
else else
{ {
curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl); qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv); qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv);
} }
curl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string); qcurl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string);
curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1); qcurl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5); qcurl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl); qcurl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version")); qcurl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version"));
curl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer); qcurl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer);
curl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL); 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"); Com_Printf("HTTP download: cURL error\n");
dl->queueEntry->state = DLQ_STATE_DONE; dl->queueEntry->state = DLQ_STATE_DONE;
@ -473,7 +474,7 @@ static void CL_FinishHTTPDownload(void)
do do
{ {
// Get a message from CURL. // 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) if (!msg)
{ {
@ -551,7 +552,7 @@ static void CL_FinishHTTPDownload(void)
{ {
case CURLE_HTTP_RETURNED_ERROR: case CURLE_HTTP_RETURNED_ERROR:
case CURLE_OK: case CURLE_OK:
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); qcurl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
if (responseCode == 404) if (responseCode == 404)
{ {
@ -564,7 +565,7 @@ static void CL_FinishHTTPDownload(void)
} }
// ...and remove it from the CURL multihandle. // ...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); 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. // ...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, // Special case: We're already aborting HTTP downloading,
// so we can't just kill everything. Otherwise we'll get // so we can't just kill everything. Otherwise we'll get
@ -614,7 +615,7 @@ static void CL_FinishHTTPDownload(void)
break; break;
default: 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); i = strlen(dl->queueEntry->quakePath);
@ -625,7 +626,7 @@ static void CL_FinishHTTPDownload(void)
} }
// ...and the handle from CURLs mutihandle. // ...and the handle from CURLs mutihandle.
curl_multi_remove_handle (multi, dl->curl); qcurl_multi_remove_handle (multi, dl->curl);
continue; continue;
break; break;
@ -651,7 +652,7 @@ static void CL_FinishHTTPDownload(void)
} }
// Remove the file fo CURLs multihandle. // 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); } while (msgs_in_queue > 0);
@ -736,7 +737,11 @@ static void CL_StartNextHTTPDownload(void)
*/ */
void CL_InitHTTPDownloads (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 (dl->curl)
{ {
if (multi) 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; dl->curl = NULL;
} }
@ -782,14 +790,17 @@ void CL_HTTP_Cleanup(qboolean fullShutdown)
// Cleanup CURL multihandle. // Cleanup CURL multihandle.
if (multi) if (multi)
{ {
curl_multi_cleanup(multi); qcurl_multi_cleanup(multi);
multi = NULL; multi = NULL;
} }
// Shutdown CURL. // Shutdown CURL.
if (fullShutdown) 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; httpDown = true;
} }
} }
@ -807,6 +818,16 @@ void CL_SetHTTPServer (const char *URL)
dlqueue_t *last = NULL; dlqueue_t *last = NULL;
dlqueue_t *q = &cls.downloadQueue; 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); CL_HTTP_Cleanup(false);
// Cleanup download queues. // Cleanup download queues.
@ -841,7 +862,7 @@ void CL_SetHTTPServer (const char *URL)
Com_Error(ERR_DROP, "HTTP download: Still have old handle?!"); Com_Error(ERR_DROP, "HTTP download: Still have old handle?!");
} }
multi = curl_multi_init(); multi = qcurl_multi_init();
} }
/* /*
@ -984,7 +1005,7 @@ void CL_RunHTTPDownloads(void)
int newHandleCount; int newHandleCount;
CURLMcode ret; CURLMcode ret;
// No HTTP server given. // No HTTP server given or not initialized.
if (!cls.downloadServer[0]) if (!cls.downloadServer[0])
{ {
return; return;
@ -993,7 +1014,7 @@ void CL_RunHTTPDownloads(void)
// Kick CURL into action. // Kick CURL into action.
do do
{ {
ret = curl_multi_perform(multi, &newHandleCount); ret = qcurl_multi_perform(multi, &newHandleCount);
if (newHandleCount < handleCount) if (newHandleCount < handleCount)
{ {

View file

@ -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 <curl/curl.h>
#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

221
src/client/curl/qcurl.c Normal file
View file

@ -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 <curl/curl.h>
#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